Skip to main content

Beckhoff TwinCAT IEC 61131-3 parsing tools

Project description

Beckhoff TwinCAT IEC 61131-3 Lark-based Structured Text Tools

Or for short, blark. B(eckhoff)-lark. It sounded good in my head, at least.

The Grammar

The grammar uses Lark's Earley parser algorithm.

The grammar itself is not perfect. It may not reliably parse your source code or produce useful Python instances just yet.

See issues for further details.

The plan

As a fun side project, blark isn't at the top of my priority list.

Once I get around to it, I hope to:

  • Introduce user-friendly Python dataclasses for all PLC constructs
  • Create a lark Transformer to take tokenized PLC code and map them onto those dataclasses
  • Fix the grammar and improve it as I go
  • Python black-style automatic code formatter?
  • Documentation generator in markdown?
  • Syntax highlighted source code output?

Requirements

  • lark (for grammar-based parsing)
  • pytmc (for directly loading TwinCAT projects)

Installation

Installation is quick with Pip.

pip install --upgrade blark

Quickstart

  1. Preferably using non-system Python, set up an environment using, e.g., miniconda:
$ conda create -n blark-env -c conda-forge python=3.7 blark
$ conda activate blark-env
  1. Install the library (using conda or otherwise, these steps are the same)
$ pip install blark
  1. Run the parser or experimental formatter utility. Supported file types include those from TwinCAT3 projects ( .tsproj, .sln, .TcPOU, .TcGVL).
$ blark parse -vvv blark/tests/POUs/F_SetStateParams.TcPOU
iec_source
  function_declaration
    F_SetStateParams
    BOOL
    function_var_blocks
      input_declarations
        None
        var1_init_decl
          var1_list
            var1
              variable_name
                nStateRef
                None
              None
          simple_spec_init
            None
            UDINT
            None
... (clipped) ...

To interact with the Python dataclasses directly, use:

$ blark parse --interactive blark/tests/POUs/F_SetStateParams.TcPOU
# Assuming IPython is installed, the following prompt will come up:

In [1]: result.items[0].name
Out[1]: Token('IDENTIFIER', 'F_SetStateParams')

Dump out a parsed and reformatted set of source code:

$ blark format blark/tests/POUs/F_SetStateParams.TcPOU
FUNCTION F_SetStateParams : BOOL
    VAR_INPUT
        nStateRef : UDINT;
        rPosition : REAL;
        rTolerance : REAL;
        stBeamParams : ST_BeamParams;
    END_VAR
    VAR_IN_OUT
        Table : FB_LinearDeviceStateTable;
    END_VAR
    VAR
        stDeviceState : ST_DeviceState;
    END_VAR
    stDeviceState.nStateRef := nStateRef;
    stDeviceState.rPosition := rPosition;
    stDeviceState.rTolerance := rTolerance;
    stDeviceState.stReqBeamParam := stBeamParams;
    Table.A_Add(key := nStateRef, putValue := stDeviceState);
    F_SetStateParams := Table.bOk;
END_FUNCTION

It is also possible to parse the source code into a tokenized SourceCode tree:

>>> import blark
>>> blark.parse_source_code(
...     """
...                 PROGRAM ProgramName
...                 VAR_INPUT
...                     iValue : INT;
...                 END_VAR
...                 VAR_ACCESS
...                     AccessName : SymbolicVariable : TypeName READ_WRITE;
...                 END_VAR
...                 iValue := iValue + 1;
...             END_PROGRAM
... """
... )
SourceCode(items=[Program(name=Token('IDENTIFIER', 'ProgramName'), declarations=[InputDeclarations(attrs=None, items=[VariableOneInitDeclaration(variables=[DeclaredVariable(variable=SimpleVariable(name=Token('IDENTIFIER', 'iValue'), dereferenced=False), location=None)], init=TypeInitialization(indirection=None, spec=SimpleSpecification(type=Token('DOTTED_IDENTIFIER', 'INT')), value=None))]), AccessDeclarations(items=[AccessDeclaration(name=Token('IDENTIFIER', 'AccessName'), variable=SimpleVariable(name=Token('IDENTIFIER', 'SymbolicVariable'), dereferenced=False), type=DataType(indirection=None, type_name=Token('DOTTED_IDENTIFIER', 'TypeName')), direction=Token('READ_WRITE', 'READ_WRITE'))])], body=StatementList(statements=[AssignmentStatement(variables=[SimpleVariable(name=Token('IDENTIFIER', 'iValue'), dereferenced=False)], expression=BinaryOperation(left=SimpleVariable(name=Token('IDENTIFIER', 'iValue'), dereferenced=False), op=Token('ADD_OPERATOR', '+'), right=Integer(value=Token('INTEGER', '1'), type_name=None)))]))], filename=PosixPath('unknown'), raw_source='\n                PROGRAM ProgramName\n                VAR_INPUT\n                    iValue : INT;\n                END_VAR\n                VAR_ACCESS\n                    AccessName : SymbolicVariable : TypeName READ_WRITE;\n                END_VAR\n                iValue := iValue + 1;\n            END_PROGRAM\n')

Alternatively, if you only want the tree:

In [1]: import blark

In [2]: parser = blark.parse.new_parser(start="function_block_body")

In [3]: parser.parse(
    ...:     """// Default return value to TRUE
    ...: SerializeJson := TRUE;
    ...:
    ...: // Set to Root of Structure
    ...: Root();
    ...:
    ...: SerializeJson := SerializeJson AND _serializedContent.Recycle();
    ...:
    ...: // Set up Initial States for Indices
    ...: lastLevel := Current.LEVEL;
    ...: outerType := Current.JSON_TYPE;
    ...: """
    ...: )
Out[3]: Tree(...)

For some additional reference regarding this syntax, refer to the comment here on issue #20

Adding Test Cases

Presently, test cases are provided in two forms. Within the blark/tests/ directory there are POUs/ and source/ directories.

Acknowledgements

Originally based on Volker Birk's IEC 61131-3 grammar iec2xml (GitHub fork here) and A Syntactic Specification for the Programming Languages of theIEC 61131-3 Standard by Flor Narciso et al. Many aspects of the grammar have been added to, modified, and in cases entirely rewritten to better support lark grammars and transformers.

Special thanks to the blark contributors:

  • @engineerjoe440

Related, Similar, or Alternative Projects

There are a number of similar, or related projects that are available.

  • "MATIEC" - another IEC 61131-3 Structured Text parser which supports IEC 61131-3 second edition, without classes, namespaces and other fancy features. An updated version is also available on Github
  • OpenPLC Runtime Version 3 - As stated by the project:

    OpenPLC is an open-source Programmable Logic Controller that is based on easy to use software. Our focus is to provide a low cost industrial solution for automation and research. OpenPLC has been used in many research papers as a framework for industrial cyber security research, given that it is the only controller to provide the entire source code.

  • RuSTy documentation - Structured text compiler written in Rust. As stated by the project:

    RuSTy is a structured text (ST) compiler written in Rust. RuSTy utilizes the LLVM framework to compile eventually to native code.

  • IEC Checker - Static analysis tool for IEC 61131-3 logic. As described by the maintainer:

    iec-checker has the ability to parse ST source code and dump AST and CFG to JSON format, so you can process it with your language of choice.

  • TcBlack - Python black-like code formatter for TwinCAT code.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

blark-0.6.0.tar.gz (86.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

blark-0.6.0-py3-none-any.whl (74.3 kB view details)

Uploaded Python 3

File details

Details for the file blark-0.6.0.tar.gz.

File metadata

  • Download URL: blark-0.6.0.tar.gz
  • Upload date:
  • Size: 86.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.0 CPython/3.10.10

File hashes

Hashes for blark-0.6.0.tar.gz
Algorithm Hash digest
SHA256 881c6f9440fb2bd840f180cf799563ed50268205c92dd113d0ed3f152e2c062a
MD5 ae972d3213c2fd5b8f6b2e4db47a3e7e
BLAKE2b-256 2ca580e5072f23b894ae72d5d6f2b7441fb5f4ff39da64f423e80e0f554dbec9

See more details on using hashes here.

File details

Details for the file blark-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: blark-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 74.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.0 CPython/3.10.10

File hashes

Hashes for blark-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7e6e4c32634359620a03cd09aaaea0bddc260539b4804790fe44b5d42329b96e
MD5 77b3826c4adecb926d7a2d8dda72c1c2
BLAKE2b-256 d26e1ce5374dd6516164a9171f343e95cd3eefb8fb6094221020872b8fa2bb35

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page