npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

tc-context

v1.0.1

Published

TwinCAT ADS Communication Library for creating an active TwinCAT Context, with automatic symbol and type mapping

Downloads

7

Readme

tc-context - TwinCAT ADS Javascript Library

version TwinCAT npm version License

Unofficial Node.JS Library for TwinCAT ADS Communication, designed to simplify connection to a Beckhoff PLC, and automatically generating a Symbol Map ADS Protocol from Beckhoff for ease of read/write and subscribe data operations. The tc-context library achieves this by fetching all the type data and initial symbol data at the moment connection and caching the memory locations of each individual symbol, as well as their parent symbol relationship. This grants the possibility to write partial structures directly into TwinCAT Function_Blocks and Structures, subscribing to complex objects, and clearing multiple Namespaces, all with single-line instructions at minimal run-time cost.

This library is made possible, thanks to the ads-client library by Jussi Isotalo [email protected]. If you are in search for a smaller library, focusing on ADS communication, make sure to check theads-client library.

List of Features

  • Generation of a full Symbol Map, from a Beckhoff PLC
  • Reading/writing/clearing/subscribing to non-structured and non-array types
  • Reading/writing/clearing/subscribing to structured types
  • Writing only to explicit members of structured types
  • Reading/writing/clearing/subscribing to array types
  • Reading/Writing/clearing/subscribing to sub-arrays dimension size[n-1] from an initial array of dimension size[n]
  • Invoking Methods from the Beckhoff PLC Side
  • Beckhoff PLC Code Change detection and Symbol Map re-generation
  • Input value validation for all types, including sub-ranged integers

For detailed information, see the official documentation.

Table of contents

Quick Look

Below is a quick example of creating a TcContext for a Beckhoff PLC located at localhost, and performing read/write/clear operations. For more complex operations, and behavior definitions, see individual chapters on the subject matter.

MAIN(PRG)

PROGRAM MAIN
VAR

    booleanValue : BOOL := TRUE;
    numericValue : INT := 10;
    structuredValue : Foo;
    arrayValue : ARRAY [0..9] OF STRING := ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j']

END_VAR

Foo(FB)

FUNCTION_BLOCK Foo
VAR

    realValue : REAL;
    stringValue : STRING := 'hello world';

END_VAR

index.js

const { TcContext } = require('tc-context');

TcContext.create().then(async context => {

    //Reading the program's 'MAIN' Namespace
    let result = await context.$.MAIN.$get
    /**
     *  result : { 
     *      booleanValue : true, 
     *      numericValue : 10, 
     *      structuredValue : { 
     *          realValue : 0,
     *          stringValue : 'hello world'    
     *      },
     *      arrayValue : ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j']
     *  }
     */

    //Clearing all members of a Structured Value
    await context.$.MAIN.structuredValue.$clear()
    result = await context.$.MAIN.structuredValue.$get;
    /**
     *  result : { realValue : 0, stringValue : '' }
     */   

    //Setting a Numeric value
    await context.$.MAIN.numericValue.$set(5);
    result = await context.$.MAIN.numericValue.$get;
    /**
     *  result : 5
     */   

    //Setting an Array
    await context.$.MAIN.arrayValue.$set(['1', '2', '3']);
    result = await context.$.MAIN.arrayValue.$get;
    /**
     *  result : ['1', '2', '3', 'd', 'e', 'f', 'g', 'h', 'j']
     */  

    //Setting a program's Namespace values
    const set = {
         booleanValue : false,
         structuredValue : {
             realValue : 15,
             stringValue : 'i am alive'
         }

    }
    await context.$.MAIN.$set(set)
    result  = await context.$.MAIN.arrayValue.$get;
    /**
     *  result : { 
     *      booleanValue : false, 
     *      numericValue : 5, 
     *      structuredValue : { 
     *          realValue : 15,
     *          stringValue : 'i am alive'    
     *      },
     *      arrayValue : ['1', '2', '3', 'd', 'e', 'f', 'g', 'h', 'j']
     *  }
     */

    //Clearing Namespace
    await context.$.MAIN.$clear();
    result = await context.$.MAIN.arrayValue.$get;
    /**
     *  result : { 
     *      booleanValue : false, 
     *      numericValue : 0, 
     *      structuredValue : { 
     *          realValue : 0,
     *          stringValue : ''    
     *      },
     *      arrayValue : ['', '', '', '', '', '', '', '', '']
     *  }
     */

    //Disconnecting Context and killing bindings
    await context.kill()

})

Installation

Run the following command to install package in your project:

npm install tc-context

Include the module in js:

const { TcContext } = require('tc-context')

Connection Setup

The tc-context library uses the ads-client library for connecting and communicating with a Beckhoff PLC over the ADS Protocol. Because of that, the connection setup configuration is equal to the connection setup of the ads-client, since the supplied settings are routed directly to the ads-client.

See ads-client for detailed information on different types of possible connections.

Creating TcContext Instance

const { TcContext } = require('tc-context')

//Connecting to a Localhost Beckhoff PLC
TcContext.create().then(async context => {
    
    //Result contains the currently active context
    //Perform operation with the created context....

    //When done, kill the context
    await context.kill();

})

In order to create a new TcContext Object, a call to the TcContext.create() functions must be made. By default, if no arguments are passed to the TcContext.create() function, it is assumed to that the connection that will be made is to the localhost located PLC. Explicit settings can be provided as the first argument to the TcContext.create() function.

NOTE: The TcContext object must be explicitly killed at the end of its use, through the TcContext.kill() method call. This will clean up all subscription handles, termination the connection, and clear the generated map, thus ensuring no memory leaks.

const { TcContext } = require('tc-context')

const settings = {
  targetAmsNetId: '192.168.1.120.1.1',
  targetAdsPort: 851
  //And more....
}

//Connecting to a Localhost Beckhoff PLC
TcContext.create(settings).then(async context => {
    
    //Result contains the currently active context for 192.168.1.120.1.1:851
    //Perform operation with the created context....

    //When done, kill the context
    await context.kill();

})

The settings are routed to the ads-client Client.connect() method directly, without any modification. Hence, for more detailed and up-to-date information, on different connection patters, see the official documentation of ads-client.

TcContext Components

The TcContext Object is composed of 3 Components :

  • TcSymbolRegistry - Storage for all the Symbol Maps created based based on the data gathered by TcCom', passed through the TcTypeRegistry. This property can be accessed by the TcContext.symbols property, and the registered symbol maps can be accessed by the TcContext.symbols.namespaces property. The TcSymbolNamespace represents the Programs and any Global Variable Lists present in the PLC.Note: Because access to the symbol namespaces is a common operation, a shortcut is implemented in the form of TcContext.$, which corresponds to TcContext.symbols.namespaces
  • TcTypeRegistry - Storage for all the processed types, which are gathered by TcCom Component. This component can be accessed through TcContext.types property, and the registered types can be queried by TcContext.types.has(<type name>).Note: Type names are case sensitive.
  • TcCom - Component responsible for communication and data passing between TcContext and the PLC. This component can be accessed through TcContext.COM property.

TcSymbols

Upon a successful TcContext creation, a full TcSymbol map is created, mirroring what is currently loaded in the Beckhoff PLC. The create TcSymbolNamespaces can be accessed either through TcContext.symbols.namespaces or TcContext.$ properties. In order to avoid naming conflicts with symbols declared in the PLC, all public methods provided by the TcSymbol Object begin with ``$` symbol.

IMPORTANT: Type safety is important and there is no implicit type conversion. This is to ensure, that whatever is written is well defined by both ends of the system. In case of type mismatch, an exception TcBindingInvalidTypeException is raised and no operation is performed.

MAIN(PRG)

PROGRAM MAIN
VAR

    booleanValue : BOOL;
    numericValue : INT;
    rangeValue : BYTE(50..100);
    stringValue : STRING(50);
    enumValue : MyEnum;
    arrayValue : ARRAY[0..4] OF Foo;

END_VAR

Foo(FB)

FUNCTION_BLOCK Foo
VAR

    {attribute 'Default' := '22.3'}
    realValue : REAL;
    {attribute 'ReadOnly'}
    stringValue : STRING;

END_VAR

MyEnum(ENUM)

TYPE MyEnum :
(
	member1 := 0,
	member2 := 1,
	member3 := 2
);
END_TYPE

The example above would produce a TcContext of the following type:

  • TcContext.$.MAIN - the MAIN Program
    • .booleanValue - symbol of type TcBooleanSymbol : TcSymbol
    • .numericValue - symbol of type TcNumericSymbol : TcSymbol
    • .rangeValue - symbol of type TcNumericSymbol : TcSymbol with explicit value borders at [50-100], and a write of a value outside these borders raises the TcBindingOutOfRangeException exception
    • .stringValue - symbol of type TcStringSymbol : TcSymbol, with a max length of [50] characters, and any attempt to write a string of [50+] length raises the TcBindingOutOfRangeException exception
    • .enumValue - symbol of type TcEnumSymbol : TcSymbol, which only accepts input strings from ['MyEnum.member1', 'MyEnum.member2', 'MyEnum.member3'] list, and any write of value outside that list raises the TcBindingOutOfRangeException exception
    • .arrayValue - symbol of type TcArraySymbol : TcSymbol, which represents an array of size [5] of type Foo type Function_Block
      • [0-4] - symbols of type TcStructureSymbol : TcSymbol
        • .realValue - symbol of type TcNumericSymbol : TcSymbol, with an explicit default value of 22.3, which will be set, when ``TcSymbol.$clear()` is called
        • .stringValue - symbol of type TcStringSymbol : TcSymbol, which is set to ReadOnly, thus any call to TcSymbol.$clear() or TcSymbol.$set() results in an exception TcBindingReadOnlyException, as well as a call to .stringValue parent TcSymbol.$clear() method will ignore this symbol
const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    //Exception TcBindingOutOfRangeException
    await context.$.MAIN.rangeValue.$set(10) 

    //MAIN.enumValue is set to 1
    await context.$.MAIN.enumValue.$set('MyEnum.member2')

    //Exception TcBindingReadOnlyException
    await context.$.MAIN.arrayValue[0].stringValue.$set('hello world') 

    //For [0-4] all the .realValue members are set to 22.3 
    //while .stringValue member is left untouched
    await context.$.MAIN.arrayValue.$clear() 

    //Explicit TcContext kill call
    await context.kill();

})

The tc-context library currently supports all of the TwinCAT Data Types, with the exception of:

  • Pointers
  • References
  • Interfaces
  • Arrays of the above mentioned types

NOTE: The TcContext Object filters out unsupported members, when generating Symbol Maps. If the end result of a Structure, Funtion_Block or Array, is a Symbol with no Children, no mapping will be created for that Object as well.

MAIN(PRG)

PROGRAM MAIN
VAR

    booleanValue : BOOL;
    filteredFB : Foo;
    filteredArr : ARRAY[0..9] OF REFERENCE TO Foo;

END_VAR

Foo(FB)

FUNCTION_BLOCK Foo
VAR

    ptr: POINTER TO INT;
    ref : REFERENCE TO BYTE;

END_VAR

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    //booleanValue is defined
    const booleanValue = context.$.MAIN.booleanValue 

    //filteredFB is undefined
    const filteredFB = context.$.MAIN.filteredFB 

    //filteredArr is undefined
    const filteredArr = context.$.MAIN.filteredArr 

    //Explicit TcContext kill call
    await context.kill();

})

Read, Write and Clear TcSymbol Operations

MAIN(PRG)

PROGRAM MAIN
VAR

    booleanValue : BOOL;
    numericValue : INT;
    stringValue : STRING(50);
    enumValue : MyEnum;

END_VAR

MyEnum(ENUM)

TYPE MyEnum :
(
	member1 := 0,
	member2 := 1,
	member3 := 2
);
END_TYPE

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    //Returns the current value of MAIN.booleanValue
    const booleanValue = await context.$.MAIN.booleanValue.$get 

    //Sets the value of MAIN.booleanValue
    await context.$.MAIN.booleanValue.$set(true); 

    //Sets the value of MAIN.enumValue to MyEnum.member2 (1) and then read it back
    const enumValue = await context.$.MAIN.enumValue.$set('MyEnum.member2')
        .then(() => context.$.MAIN.enumValue.$get ) 

    if (enumValue === 'MyEnum.member2') {
        //Clears the MAIN.enumValue to MyEnum.member1 (0)
        await context.$.MAIN.enumValue.$clear() 
    }

    //Explicit TcContext kill call
    await context.kill();

})

The TcSymbol Object supports the following data operations:

  • $set(val) - method for writing a value to the Binding of the TcSymbol Object.

    • when Promise is fulfilled, it returns the value, which was successfully written to the TcSymbol.
    • when Promise is rejected, returns a TcException Type Object, with information regarding the error
  • $get - property for reading the value of the TcSymbol Object Binding.

    • when Promise is fulfilled, it returns the value, which was successfully read from the TcSymbol.
    • when Promise is rejected, returns a TcException Type Object, with information regarding the error
  • $clear() - method for clearing the Binding of the TcSymbol based on either its default value, or the explicitly specified Default Attribute value

    • when Promise is fulfilled, clearing has successfully completed
    • when Promise is rejected, returns a TcException Type Object, with information regarding the error

Subscribing to TcSymbol Changes

MAIN(PRG)

PROGRAM MAIN
VAR

    booleanValue : BOOL;
    numericValue : INT;
    stringValue : STRING(50);
    foo : Foo;

END_VAR

Foo(FB)

FUNCTION_BLOCK Foo
VAR

    booleanValue : BOOL;
    arrayValue : ARRAY[0..9] OF INT;

END_VAR

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    //Activate on-change detection
    await context.$.MAIN.numericValue.$subscribe()
    await context.$.MAIN.foo.$subscribe()
    
    //Attach event handlers
    context.$.MAIN.numericValue.$on('changed', (e) => {
        console.log(`Boolean Value has changed to ${e.data}`);
    })

    context.$.MAIN.foo.$on('changed', (e) => {
        console.log(`{ 
            booleanValue : ${e.data.booleanValue}, 
            arrayValue : ${e.data.arrayValue.toString()}
        }`)
    })


    //When no longer change detection is needed, disable it
    await context.$.MAIN.foo.$unsubscribe();

    //Subscribe to an array.
    await context.$.MAIN.foo.arrayValue.$subscribe();
    context.$.MAIN.foo.arrayValue.$on('changed', (e) => {
        console.log(e.data.toString())
    })

    //Explicit TcContext kill call will also 
    //automatically unsubscribe all active change subscriptions
    await context.kill();

})

TcSymbol Objects are capable of subscribing to a data changed event of type TcSymbolChangeEvent. When subscribed, if the PLC symbol changes its value, any handlers, which were attached, will be invoked. To subscribe TcSymbol.$subscribe() must be called, and to disable change detection TcSymbol.$unsubscribe() is called. Handlers are added to the TcSymbol by way of calling TcSymbol.$on(...) and TcSymbol.$once(...) and are removed through TcSymbol.$off(...). When unsubscribing, event handlers are not removed, they are simply ignored.

When subscribing to Structures,Function_Blocks and Arrays, any change that happens to the data of that symbol, will also result in the changed event. This includes change in symbol that are not supported.

MAIN(PRG)

PROGRAM MAIN
VAR

    foo : Foo;

END_VAR

Foo(FB)

FUNCTION_BLOCK Foo
VAR

    booleanValue : BOOL := FALSE;
    ptr : POINTER TO STRING;
    bar : Bar

END_VAR

Bar(FB)

FUNCTION_BLOCK Bar
VAR

    numericValue : INT := 5;

END_VAR

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    //Activate on-change detection
    await context.$.MAIN.foo.$subscribe()
    
    //Attach event handlers
    context.$.MAIN.foo.$on('changed', (e) => {
        console.log(e.data);
    }

    //Hypothetical scenario of indefinite run-time

    //When hypothetical scenario is finished
    await context.kill();

})

In the example above, a TcSymbol.$subscribe() is activated on MAIN.foo, which would mean, any change happening in MAIN.foo will invoke the changed event. When MAIN.foo.booleanValue changes from FALSE to TRUE, the TcEvent.data will contain an output:

{
    booleanValue : true,
    bar : {
        numericValue : 5
    }
}

Following the previous scenario, if MAIN.foo.bar.numericValue has a change from 5 to 10, this will also produce the changed event, with the following output:

{
    booleanValue : true,
    bar : {
        numericValue : 10
    }
}

Lastly, even though MAIN.foo.ptr is not supported, and a binding to it is not created, it is still part of the data layout of MAIN.foo Symbol. It is because of this a change to the pointer value (not what its pointing to), will also produce a changed event, with the following output (The 2 previous examples have happened) :

{
    booleanValue : true,
    bar : {
        numericValue : 10
    }
}

Explicit Sampling Rate

MAIN(PRG)

PROGRAM MAIN
VAR

    numericValue : INT;

END_VAR

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    //Subscribe with a sampling rate of 1s
    await context.$.MAIN.numericValue.$subscribe(1000)
    
    //Attach event handlers
    context.$.MAIN.numericValue.$on('changed', (e) => {
        console.log(e.data);
    }

    //When hypothetical scenario is finished
    await context.kill();

})

For Symbols, the value of which changes rapidly, and detection of the change each time would either be too costly or unneeded, it is possible to explicitly set the sampling rate in milliseconds, as the argument for TcSymbol.$subscribe(). This will keep any changes of the Symbol from emitting changed event between the sampling period.

Structured TcSymbols

MAIN(PRG)

PROGRAM MAIN
VAR

    foo : Foo;

END_VAR

Foo(FB)

FUNCTION_BLOCK Foo
VAR

    booleanValue : BOOL;
    numericValue : INT;
    stringValue : STRING;
    bar : Bar

END_VAR

Bar(FB)

FUNCTION_BLOCK Bar
VAR

    numericValue : INT;
    booleanValue : BOOL;

END_VAR

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    await context.$.MAIN.foo.$set({ booleanValue : true, numericValue : 10});
    let result = await context.$.MAIN.foo.$get;
    /**
     * result : {
     *      booleanValue : true
     *      numericValue : 10
     *      stringValue : ''
     *      bar : {
     *          numericValue : 0
     *          booleanValue : false
     *      }
     * }
     */

    await context.$.MAIN.foo.$set({ 
        stringValue : 'hello world'
        bar : {
            numericValue : 15,
            booleanValue : true
        }    
    })
    result = await context.$.MAIN.foo.$get;
    /**
     * result : {
     *      booleanValue : true
     *      numericValue : 10
     *      stringValue : 'hello world'
     *      bar : {
     *          numericValue : 15
     *          booleanValue : true
     *      }
     * }
     */

    //Writing to a non existent child, will result in an exception
    await context.$.MAIN.foo.$set({ nonExistent : 56.6 })
        .catch(err => console.log(err));
    /**
     * TcBindingOutOfRangeException : ....
     */

    context.$.MAIN.foo.bar.$each(symbol => {
        console.log(symbol.$binding.path)
    })
    /**
     * Output :
     *  MAIN.foo.bar.numericValue
     *  MAIN.foo.bar.booleanValue
     */

    await context.$.MAIN.foo.$clear();
    result = await context.$.MAIN.foo.$get;
    /**
     * result : {
     *      booleanValue : false
     *      numericValue : 0
     *      stringValue : ''
     *      bar : {
     *          numericValue : 0
     *          booleanValue : false
     *      }
     * }
     */
    
    await context.kill();

})

TcSymbol Objects are capable of operating on Structures and Function_Blocks. For TcContext both Structures and Function_Blocks are treated equally as structures of data. It is possible to write only explicit parts of a TcSymbol symbol, bound to a Structure or Function_Block

Calling TcSymbol.$clear() will clear all the members of the Structure or Function_Block, with the only exception of ignoring any child marked with {attribute 'ReadOnly'}. By calling TcSymbol.$get property, a full data-map of all nested-children is returned, with the exception of symbol marked with {attribute 'Ignore'}

When writing to a child, which is not part of TcSymbol, the operation will result in an exception of type TcBindingOutOfRangeException.

Method call on Structured TcSymbols

MAIN(PRG)

PROGRAM MAIN
VAR

    foo : Foo;

END_VAR

Foo(FB)

FUNCTION_BLOCK Foo
VAR

    booleanValue : BOOL;

END_VAR

{attribute 'TcRpcEnable'}
METHOD basicTask : INT
VAR_INPUT
    lhs : INT,
    rhs : INT
END_VAR

basicTask := lhs + rhs;

{attribute 'TcRpcEnable'}
METHOD complexTask : BOOL
VAR_INPUT
    lhs : INT;
    rhs : INT;
END_VAR
VAR_OUTPUT
	sum : INT;
	diff : INT;
END_VAR

complexTask := TRUE;
sum := lhs + rhs;
diff := lhs - rhs;

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    const basicTask = await context.$.MAIN.foo.basicTask({ lhs : 10, rhs : 15 });
    /**
     * basicTask : {
     *      result : 25
     * }
     */

    const complexTask = await context.$.MAIN.foo.complexTask({ lhs : 15, rhs : 10 });
    /**
     * basicTask : {
     *      result : 25
     *      outputs : {
     *          sum : 25,
     *          diff : 5
     *      }
     * }
     */

    await context.kill();

})

It is possible to invoke RPC Methods, marked with {attribute 'TcRpcEnable'}, on the PLC Side through the TcContext. Upon completion, a Javascript Object is returned with a field result, which contains the return value of the method call. If the method has VAR_OUTPUT variables, the return of the method call will also contain outputs field, which store the values.

NOTE: As of now, no type checking is performed when passing values to the method call, it is a direct route to the ads-client. See official documentation for the limitations of Rpc Method calls. This is important when dealing with enumerators. When writing and reading enumerators directly, their type is included, however when passing them to a Rpc Method, the type name of the enum must be omitted.

If the method call fails, exceptions of type TcComIsInvalidException or TcComMethodCallException are raised.

Unions and TcSymbols

MAIN(PRG)

PROGRAM MAIN
VAR

    myUnion : MyUnion;

END_VAR

MyUnion(UNION)

TYPE DUT :
UNION

    a : LREAL;
    b : LINT;
    c : WORD;

END_UNION
END_TYPE

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    let result = await context.$.MAIN.myUnion.$get
    /**
     * result : {
     *      a : 0,
     *      b : 0,
     *      c : 0
     *  }
     */

    await context.$.MAIN.myUnion.c.$set(10)
    let result = await context.$.MAIN.myUnion.$get
    /**
     * result : {
     *      a : 4.94065645841247E-323,
     *      b : 10,
     *      c : 10
     *  }
     */

    await context.kill();

})

UNIONs are considered Structured symbols in the tc-context library, and are accessed and manipulated the same way as Structured TcSymbols.

Array TcSymbols

MAIN(PRG)

PROGRAM MAIN
VAR

    arr : ARRAY[0..4] OF BOOLEAN;

END_VAR

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    await context.$.MAIN.arr.$set([true, true, true]);
    let result = await context.$.MAIN.arr.$get;
    /**
     * result : [true, true, true, false, false]
     */

    await context.$.MAIN.arr.$clear();
    result = await context.$.MAIN.arr.$get;
    /**
     * result : [false, false, false, false, false]
     */   
    
    await context.$.MAIN.arr[3].$set(true);
    result = await context.$.MAIN.arr[3].$get;
    /**
     * result : true
     */
    
    result = await context.$.MAIN.arr.$get;
    /**
     * result : [false, false, true, false, false]
     */   

    context.$.MAIN.arr.$each(symbol => {
        console.log(symbol.$binding.name)
    })
    /**
     * Output :
     *  arr[0]
     *  arr[1]
     *  arr[2]
     *  arr[3]
     *  arr[4]
     */

    await context.kill();

})

The TcContextis capable of generating a TcSymbol for TwinCAT array types, allowing for reading, writing, clearing and subscribing operations on the array as a whole, as well as accessing individual members through the subscript operator. NOTE: The indexes of TcSymbol array are synchronized with the TwinCAT array indexes. This means the starting index of a TcSymbol of an array is that of the starting symbol of the TwinCAT array.

MAIN(PRG)

PROGRAM MAIN
VAR

    arr1 : ARRAY[0..4] OF INT;
    arr2 : ARRAY[1..5] OF INT;

END_VAR

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    console.log({ 
        startIndex : context.$.MAIN.arr1.$startIndex,
        length : context.$.MAIN.arr1.$length
    })
    /**
     * Output : { startIndex : 0, length : 5 }
     */

    console.log({ 
        startIndex : context.$.MAIN.arr2.$startIndex,
        length : context.$.MAIN.arr2.$length
    })
    /**
     * Output : { startIndex : 1, length : 5 }
     */

    context.$.MAIN.arr1.$each((symbol, index) => {
        console.log({ index, name : symbol : symbol.$binding.key})
    })
    /**
     * Output :
     *  { index : 0, symbol : arr1[0]}
     *  { index : 1, symbol : arr1[1]}
     *  { index : 2, symbol : arr1[2]}
     *  { index : 3, symbol : arr1[3]}
     *  { index : 4, symbol : arr1[4]}
     */

    context.$.MAIN.arr2.$each((symbol, index) => {
        console.log({ index, name : symbol : symbol.$binding.key})
    })
    /**
     * Output :
     *  { index : 1, symbol : arr1[1]}
     *  { index : 2, symbol : arr1[2]}
     *  { index : 3, symbol : arr1[3]}
     *  { index : 4, symbol : arr1[4]}
     *  { index : 5, symbol : arr1[5]}
     */

    //elem is undefined
    const elem = context.$.MAIN.arr2[0];

    await context.kill();

})

Multidimensional Array

MAIN(PRG)

PROGRAM MAIN
VAR

    arr1 : ARRAY[0..2, 0..2] OF INT;
    arr2 : ARRAY[0..2] OF ARRAY[0..2] OF INT;

END_VAR

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    let result = await context.$.MAIN.arr1.$get;
    /**
     * result : [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
     */

    result = await context.$.MAIN.arr1[1].$get;
    /**
     * result : [0, 0, 0]
     */

    await context.$.MAIN.arr2[2].$set([1, 2, 3])    
    result = await context.$.MAIN.arr2.$get;
    /**
     * result : [[0, 0, 0], [0, 0, 0], [1, 2, 3]]
     */

    await context.$.MAIN.arr1[1][1].$set(10);
    result = await context.$.MAIN.arr1[1].$get;
    /**
     * result : [0, 10, 0]
     */

    result = await context.$.MAIN.arr2.$clear()
        .then(() => context.$.MAIN.arr2.$get)
    /**
     * result : [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
     */

    result = await context.$.MAIN.arr1[1].$clear()
        .then(() => context.$.MAIN.arr1.$get)
    /**
     * result : [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
     */

    await context.kill();

})

TwinCAT allows for 2 different ways of declaring Multidimensional Arrays :

  • ARRAY[dimension1, dimension2, ...., dimensionN] OF <type>
  • ARRAY[dimension1] OF ARRAY[dimension2] OF ..... ARRAY[dimensionN] of <type>

Regardless of the case, the TcContext treats both of these multidimensional arrays as multidimensional Javascript Arrays, and will process them accordingly. This allows for TcSymbol.$get, TcSymbol.$set(), TcSymbol.$clear() and TcSymbol.$subscribe() operations on any of the dimension levels.

TcSymbol Types

MAIN(PRG)

PROGRAM MAIN
VAR

    numericValue : INT[10..50];
    stringValue : STRING(10);
    foo : Foo;

END_VAR

Foo(FB)

FUNCTION_BLOCK Foo
VAR

    arrayValue : ARRAY[0..4] OF int;
    enumValue : MyEnum;

END_VAR

MyEnum(ENUM)

TYPE MyEnum :
(
	member1 := 0,
	member2 := 1,
	member3 := 2
);
END_TYPE

index.js

const { TcContext, TcSymbols } = require('tc-context')

TcContext.create().then(async context => {

    if (context.$.MAIN.numericValue instanceof TcSymbols.TcNumericSymbol) {
        console.log({ 
            min : context.$.MAIN.numericValue.$lowerBorder 
            max : context.$.MAIN.numericValue.$upperBorder    
        })
    }
    /**
     * Output :
     * { min : 10, max : 50 }
     */
    
    if (context.$.MAIN.stringValue instanceof TcSymbols.TcStringSymbol) {
        console.log({ length : context.$.MAIN.stringValue.$length })
    }
    /**
     * Output :
     * { length : 10 }
     */

    if (context.$.MAIN.foo instanceof TcSymbols.TcStructureSymbol) {
        context.$.MAIN.foo.$each(symbol => {
            if (symbol instanceof TcSymbols.TcArraySymbol) {
                console.log({ length : symbol.$length })

            } else if (symbol instanceof TcSymbols.TcEnumSymbol) {
                console.log({ fields : symbol.$fields })
            }
        })
    }
    /**
     * Output :
     * { length : 5 }
     * { fields : ['MyEnum.member1', 'MyEnum.member2', 'MyEnum.member3']}
     */

    //Explicit TcContext kill call
    await context.kill();

})

TcSymbol provides the base foundation for TcContext Symbol Map creation. Each of the TcSymbols, has a concrete type, depending on the PLC Symbol Type. The concrete TcSymbol performs type checks and range checks, as well as exposes additional, type specific, functionality. The concrete types of TcSymbol and their unique functionality is described below.

There is no implicit type conversion done. All symbols must be provided with the exact type of value, when performing TcSymbol.$set() operations. The reason is to ensure there is no ambiguity when operating on the PLC.

TcBooleanSymbol

TcBooleanSymbol represents the BOOL Data Type of the PLC, and is the most similar to the base TcSymbol. The only difference is, that it performs type checking abd throwing an exception of type TcBindingInvalidTypeException, if during the TcSymbol.$set() call, the value passed is not of type boolean.

TcNumericSymbol

TcNumericSymbol represents all the numeric Data Type Symbols of the PLC, and exposes additional symbol information, in the form of TcNumericSymbol.$upperBorder and TcNumericSymbol.$lowerBorder.

When performing a TcSymbol.$set(), the input value is check to be of type number, and that the value is within the specified range. Failure to comply results in exceptions of type TcBindingInvalidTypeException and TcBindingOutOfRangeException respectfully.

TcStringSymbol

TcStringSymbol represents STRING and WSTRING Data Types of the PLC, and exposes the TcStringSymbol.$length property, which states the maximum length of a string, that can be written to it.

TcStringSymbol performs type checking on the the passed argument to TcSymbol.$set() to make sure it is of type string and does not exceed the specified length. Failure to comply results in exceptions of type TcBindingInvalidTypeException and TcBindingOutOfRangeException respectfully.

TcEnumSymbol

TcEnumSymbol represents the ENUM Data Types of the PLC and provides the TcEnumSymbol.$fields property. This property provides a list of strings of all the acceptable inputs to the `TcSymbol.

The TcEnumSymbol performs type checking on the the passed argument to TcSymbol.$set() to make sure it is of type string and is part of the TcEnumSymbol.$fields list. Failure to comply results in exceptions of type TcBindingInvalidTypeException and TcBindingOutOfRangeException respectfully.

IMPORTANT: Alias to ENUM is treated as its own unique type. Be aware of this.

MAIN(PRG)

PROGRAM MAIN
VAR

    enumValue : MyEnum;

END_VAR

MyEnum(ENUM)

TYPE MyEnum :
(
	member1 := 0,
	member2 := 1,
	member3 := 2
);
END_TYPE

index.js

const { TcContext, TcEnumSymbol } = require('tc-context');

TcContext.create().then(async context => {


    if (context.$.MAIN.enumValue instanceof TcSymbols.TcEnumSymbol) {
        console.log(context.$.MAIN.enumValue.$fields)
    }
    /**
     * Output :
     * ['MyEnum.member1', 'MyEnum.member2', 'MyEnum.member3']
     */

    await context.$.MAIN.enumValue.$set('MyEnum.member3');
    let result = await context.$.MAIN.enumValue.$get;
    /**
     * result : 'MyEnum.member3'
     */

    await context.$.MAIN.enumValue.$set('WrongValue')
        .catch(err => console.log(err))
    /**
     * Output :
     *  TcBindingOutOfRangeException
     */

    await context.kill()

})

TcStructureSymbol

TcStructureSymbol represents Structure and Function_Block Data Types of the PLC. As of now, no distinction between the two types is made from the pointer of view of TcContext.

The TcStructureSymbol performs a type check, to make sure any argument that is passed to TcSymbol.$set() is a plain Javascript Object, otherwise a TcBindingInvalidTypeException exception is raised.

Additionally, TcStructureSymbol provides TcStructureSymbol.$each() function, which iterates over all of its children TcSymbols.

TcArraySymbol

TcArraySymbol represents ARRAY[...] OF <type> Data Type of the PLC. Similarly to the TcStructureSymbol, the TcArraySymbol also provides a TcArraySymbol.$each() function, which iterates over all of its children TcSymbols.

Due to TwinCAT allowing to explicitly specify the starting index of an array, that start index can be read through the TcArraySymbol.$startIndex property. Lastly, TcArraySymbol.$length allows to read the length of the TcArraySymbol array.

When writing to TcSymbol.$set(), the TcArraySymbol will perform type checking on the input argument, to make sure it is of type array, as well as that the written array does not exceed the length of the TcArraySymbol. Failure to comply results in exceptions of type TcBindingInvalidTypeException and TcBindingOutOfRangeException respectfully.

Invalidated TcSymbol

In a situation the where the TcContext was reinitialized, any previously stored TcSymbol becomes invalidated. One way to managed invalidation, is by assigning a callback to TcSymbol.$onInvalidated() method. This method will be called, when the TcContext invalidates the TcSymbol. Invalidation in a Symbol Map happens from the bottom-up, where children are invalidated before their parents are.

TcSymbol Attributes

Beckhoff PLC Symbol can be marked with special attributes, which are processed by TcContext. These attributes alter the behavior of some TcSymbols and impose additional rules.

TcSymbol Default Attribute

All Primitive Data Types (Not Structures, Function_Blocks or Arrays) support the Default Attribute. This attribute informs what value to write upon a call to TcSymbol.$clear(). If no value is specified, the value that is written, is that of a an initial value (false for boolean, 0 for numbers and empty string for string).

Structures, Function_Blocks and Arrays are not affected by the Default attribute.

MAIN(PRG)

PROGRAM MAIN
VAR

    {attribute 'Default' := 'true'}
    booleanValue : BOOL;
    {attribute 'Default' := '5'}
    numericValue : INT;
    {attribute 'Default' := 'hello world'}
    stringValue : STRING(50);
    {attribute 'Default' := 'member3'}
    enumValue : MyEnum;

END_VAR

MyEnum(ENUM)

TYPE MyEnum :
(
	member1 := 0,
	member2 := 1,
	member3 := 2
);
END_TYPE

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    //MAIN.booleanValue is set to true
    await context.$.MAIN.booleanValue.$clear() 

    //MAIN.numericValue is set to 5
    await context.$.MAIN.numericValue.$clear() 

    //MAIN.stringValue is set to 'hello world'
    await context.$.MAIN.stringValue.$clear() 

    //MAIN.enumValue is set to 'MyEnum.member3'
    await context.$.MAIN.enumValue.$clear() 

    //Explicit TcContext kill call
    await context.kill();

})

TcSymbol ReadOnly Attribute

All Bindable Types support the ReadOnly Attribute. The ReadOnly attributes ensures that no write operation can be made to the TcSymbol Object, either through the TcSymbol.$set() method or TcSymbol.$clear(). If a write operation is made to a ReadOnly Object, a TcBindingReadOnlyException Exception is thrown.

If TcStructureSymbol or TcArraySymbol is marked as ReadOnly, then all of their children TcSymbols are marked as ReadOnly as well. When calling TcSymbol.$clear() on TcStructureSymbol Object that is not ReadOnly, but has children marked as ReadOnly, those children will be ignored during the TcSymbol.$clear() operation.

MAIN(PRG)

PROGRAM MAIN
VAR

    {attribute 'ReadOnly'}
    booleanValue : BOOL;
    {attribute 'ReadOnly'}
    numericValue : INT;
    {attribute 'ReadOnly'}
    readOnlyFoo : Foo;
    normalFoo : Foo;

END_VAR

Foo(FB)

FUNCTION_BLOCK Foo
VAR

    {attribute 'ReadOnly'}
    stringValue: STRING := 'hello world';
    numericValue : BYTE := 100;

END_VAR

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    //MAIN.booleanValue will throw exception
    await context.$.MAIN.booleanValue.$clear().catch(err => {
        //MAIN.booleanValue is ReadOnly. This block will execute
    })

    //MAIN.numericValue will throw exception
    await context.$.MAIN.numericValue.$clear().catch(err => {
        //MAIN.numericValue is ReadOnly. This block will execute
    })

    //MAIN.foo will throw exception
    await context.$.MAIN.foo.$clear().catch(err => {
        //MAIN.foo is ReadOnly. This block will execute
    })

    //This will not raise an exception, however MAIN.normalFoo.stringValue will be left untouched
    const result = await context.$.MAIN.normalFoo.$clear().then(() => context.$.MAIN.normalFoo.$get);
    /**
     * result : {
     *      stringValue : 'hello world'
     *      numericValue : 0
     * }
     */

    //Explicit TcContext kill call
    await context.kill();

})

TcSymbol Ignore Attribute

All bindable types support the Ignore Attribute. The Ignore attributes ensures that no TcSymbol generation takes place by the TcContext. The Ignore attribute, when applied to TcStructureSymbol and TcArraySymbol, will also be applied to its children. If the end result of a TcStructureSymbol is an Object with no children, then that TcStructureSymbol will also be ignored.

MAIN(PRG)

PROGRAM MAIN
VAR

    booleanValue : BOOL;
    numericValue : INT;
    foo : Foo;

END_VAR

Foo(FB)

FUNCTION_BLOCK Foo
VAR

    {attribute 'Ignore'}
    stringValue: STRING := 'hello world';
    byteValue : BYTE := 100;

END_VAR

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    //booleanValue is defined 
    const booleanValue = context.$.MAIN.booleanValue;

    //numericValue is defined
    const numericValue = context.$.MAIN.numericValue;

    //byteValue is defined 
    const byteValue = context.$.MAIN.foo.byteValue;

    //stringValue is undefined
    const stringValue = context.$.MAIN.foo.stringValue;

    //Explicit TcContext kill call
    await context.kill();

})

TcSymbol Event Alias Attribute

It is possible to apply an alias to the events produced by TcSymbols. This way it is possible to narrow-down event handling, based on the specified alias.

MAIN(PRG)

PROGRAM MAIN
VAR

    foo : Foo;

END_VAR

Foo(FB)

FUNCTION_BLOCK Foo
VAR

    {attribute 'onSet' := 'stringSet'}
    stringValue: STRING := 'hello world';

END_VAR

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    //Listen for the aliased event
    context.$.MAIN.foo.stringValue.$on('stringSet', (e) => {
        console.log('set was aliased to stringSet : ', e.data);
    })

    //Explicit TcContext kill call
    await context.kill();

})

When aliasing a TcSymbol's event, the default event name will be replaced by the provided name, based on the Attribute Parameter. These attributes are:

  • onSet - will replace the set event
  • onGet - will replace the get event
  • onClear - will replace the cleared event
  • onChange - will replace the changed event.

TcEvents

All events emitted by the components of TcContext inherit from TcEvent. The TcEvent in TcContext are designed to propagate the emitted event up from the component , all the way to the TcContext Object.

MAIN(PRG)

PROGRAM MAIN
VAR

    booleanValue : BOOL;
    numericValue : INT;

END_VAR

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    context.$.MAIN.booleanValue.$on('set', () => {
        console.log('set was called on MAIN.booleanValue')
    })

    context.on('set', () => {
        console.log('event propagated to context, where it was also caught')
    })

    await context.$.MAIN.booleanValue.$set(true);
    /**
     * When set completed output :
     * 'set was called on MAIN.booleanValue'
     * 'context also caught the event'
     */

    //Explicit TcContext kill call
    await context.kill();

})

If the TcEvent has been handled, and the propagation of the event is no longer wanted, a call to TcEvent.stopPropagation() function will stop any propagation up towards TcContext.

MAIN(PRG)

PROGRAM MAIN
VAR

    foo : Foo;

END_VAR

Foo(FB)

FUNCTION_BLOCK Foo
VAR

    {attribute 'onChange' := 'booleanChanged'}
    booleanValue: BOOL;

END_VAR

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {

    context.$.on('booleanChanged', (e) => {
        console.log('caught booleanChanged event')
        e.stopPropagation()
    })

    context.on('booleanChanged', () => {
        //This will never happen, because event stopped Propagated
    })

    //Explicit TcContext kill call
    await context.kill();

})

TcEvent List

List of all events produced by the tc-context library.

Component | Event | Event Type | Description -|-|-|- TcComponent | killed | TcContextKilledEvent | When the current TcContext is killed and no longer is valid | reinitialized | TcContextReinitializedEvent | When the current TcContext was rebuild and is once again valid TcCom| connected | TcComConnectedEvent | When the current TcContext established the initial connection to the PLC | disconnected | TcComDisconnectedEvent | When the current connection to the PLC has been closed | sourceChanged | TcComSourceChangedEvent | When the PLC Code base changes during an active connection | connectionLost | TcComConnectionLostEvent | When the TcCom looses connection to the Target PLC | reconnected | TcComReconnectedEvent | When the TcCom reestablishes the connection to the Target PLC TcSymbol | set | TcSymbolSetEvent | When a .$set() operation was completed | get | TcSymbolGetEvent | When a .$get operation was completed | cleared | TcSymbolClearedEvent | When a .$clear() operation was completed | changed | TcSymbolChangedEvent | When the symbol value changed post .$subscribe() operation TcSymbolRegistry | created | TcSymbolRegistryCreatedEvent | When the Symbol Map has been created | destroyed | TcSymbolRegistryDestroyedEvent | When the Symbol Map has been destroyed TcTypeRegistry | created | TcTypeRegistryCreatedEvent | When the Type Map has been created | destroyed | TcTypeRegistryDestroyedEvent | When the Type Map has been destroyed

TcEvent Hierarchy

Base | Component | Concrete -|-|- TcEvent | TcContextEvent | TcContextReinitializedEvent || TcContextKilledEvent | TcSymbolEvent | TcSymbolGetEvent || TcSymbolSetEvent || TcSymbolClearedEvent || TcSymbolChangedEvent | TcComEvent | TcComConnectedEvent || TcComDisconnectedEvent || TcComSourceChangedEvent | TcTypeRegistryEvent | TcTypeRegistryCreatedEvent || TcTypeRegistryDestroyedEvent | TcSymbolRegistryEvent | TcSymbolRegistryCreatedEvent || TcSymbolRegistryDestroyedEvent

Understanding TcContext Lifecycle

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {
    
    //Attach listeners for kill event...
    context.on('killed', () => console.log('context was killed'));

    //TcContext manipulation code.....

    //Explicit TcContext kill call
    await context.kill();

    //TcContext is no longer valid after this point, and 
    //any symbols produced by it should no longer be used...

})

/**
 * Expected output:
 * 
 * context was killed
 */

During normal operation, a TcContext is valid from the moment it was created through TcContext.create() call, upon until a call to TcContext.kill() is made. When TcContext.kill()completes its task, a killed event of type TcContextKilledEvent is emitted, which can be captured.

This holds true if no PLC-side code changes have been made from TcContext's creation moment. If Activation of new configuration, or any Code Change takes place after TcContext.create() was called, then that change in TcContext will be detected. When detected, the TcContext will invalidate all the created TcSymbols, remove all subscriptions, clear Type and Symbol maps, kill itself, and then it will build a new map, based on the new symbols types and locations, which are present in the PLC.

MAIN(PRG)

PROGRAM MAIN
VAR

    booleanValue : BOOL := TRUE;

END_VAR

index.js

const { TcContext } = require('tc-context')

TcContext.create().then(async context => {
    
    //Attach listeners for kill and reinitialized events...
    context.on('killed', () => console.log('context was killed'));
    context.on('reinitialized', () => console.log('context was reinitialized'));

    //Storing the symbol for use.
    const booleanValue = context.$.MAIN.booleanValue; 

    /**
     * Hypothetical scenario...
     * Some code that uses the context cyclically for undefined period of time
     * without yet reaching the .kill() call.
     * 
     * During this hypothetical time - the PLC Configuration was changed
     */

    //The line below is reached only after the PLC Configuration was changed 
    //in the above hypothetical scenario
    await booleanValue.$set(true)
        .catch(err => console.log(err));
    
/**
 * Expected output:
 * 
 * context was killed
 * context was reinitialized
 * TcBindingIsInvalidException : 
 *      Attempting to read an Invalidated TcBinding[\<symbol name\>] ( \<symbol path\> )...
 */
})

As seen above, if after TcContext was created, changes to code of the PLC have been made, the TcContext will detect it, and perform the TcContext.kill() operation automatically, upon completion of which, the killed event of type TcContextKilledEvent will be raised. Afterwards, the TcContext will reconnect to the PLC, and a generation and build of new Type and Symbol Maps is executed. When completed the TcContext will raise the reinitialized event of type TcContextReinitializedEvent. Any attempt to perform operations on any previously stored Invalidated TcSymbols (such as booleanValue in the example above), will result in an exception of type TcBindingIsInvalidException

These events can be used, as means of updating any dependencies, which utilize the created TcSymbols by the TcContext and thus ensuring stability.

NOTE: The reason for this approach, is because upon TcContext creation, a scan is performed, which caches all the Types and Symbols, as well as their memory locations in the PLC. These memory locations are used as a means of reading, writing, clearing and subscribing to symbols, simple or complex. When an online change is performed, there are no guaranties that the location of the previously cached symbols is same, and even if an update to it can be made, the symbol itself could be of different type. All this would result in undefined behavior.

MAIN(PRG)

PROGRAM MAIN
VAR

    booleanValue : BOOL := TRUE;

END_VAR

index.js

const { TcContext } = require('tc-context')

//Example of class which uses a symbol for its operation
class Foo {
    
    //Bind the symbol at construction
    constructor(symbol) { this.__symbol = symbol; }

    //Means to update the binding of the symbol
    bind(symbol) { 
        console.log('setting new symbol')
        this.__symbol = symbol 
    }

    //Method which uses the symbol
    async printState() { 
        if (this.__symbol) {
            const val = await this.__symbol.$get;
            return (val) ? 'Symbol is on' : 'Symbol is off'
        }
    }
}

TcContext.create().then(async context => {
    
    //Create the object which is dependent on the TcContext's symbol
    const bar = new Foo(context.$.MAIN.booleanValue);

    //When invalidated set the binding to null
    context.$.MAIN.booleanValue.$onInvalidated(() => {
        console.log('symbol was invalidated')
        bar.bind(null);
    })

    context.on('reinitialized', () => { 
        console.log('context was reinitialized')
        bar.bind(context.$.MAIN.booleanValue);
    })

    /**
     * Hypothetical scenario...
     * Some code that uses the context cyclically for undefined period of time
     * without yet reaching the .kill() call.
     * 
     * During this hypothetical time - the PLC Configuration was changed
     */

    //The line below is reached only after the PLC Configuration was 
    //changed in the above hypothetical scenario
    await bar.printState()    
    await context.kill()
})

/**
 * Expected output:
 * 
 * symbol was invalidated
 * setting new symbol 
 * context was reinitialized
 * setting new symbol
 * Symbol is on
 * symbol was invalidated
 * setting new symbol
 */

The example above illustrates how any components, which depends on TcSymbol, can use the emitted events to refresh its bindings, thus ensuring defined behavior.

TcExceptions

Base | Component | Concrete | Description -|-|-|- TcException | TcBindingException | TcBindingIsInvalidException | When operations are made on a TcSymbol of a killed TcContext || TcBindingInvalidTypeException | When input type does not match TcSymbol type || TcBindingOutOfRangeException | When input data length is outside the boundaries of TcSymbol || TcBindingReadOnlyException | When write commands are called on a Read-only TcSymbol | TcComException | TcComBusyException | Connection to a PLC has already been made || TcComConnectException | When an error occurred with establishing connection || TcComIsInvalidException | When operations are made on a TcCom of a killed TcContext || TcComDisconnectException | When an error occurred with disconnecting from Target || TcComChangeDetectionException | When an error occurred with establishing Code Change Monitoring || TcComUnsubscribeException | When an error occurred with unsubscribing from TwinCAT Symbol || TcComFromRawException | When an error occurred when transforming Data from Raw || TcComToRawException | When an error occurred when transforming Data to Raw || TcComSubscribeException | When an error occurred with subscribing to a TwinCAT Symbol || TcComDataWriteException | When an error occurred during data writing to TwinCAT || TcComDataReadException | When an error occurred during data reading from TwinCAT || TcComTypeQueryException | When an error occurred with querying Type Data from TwinCAT || TcComSymbolQueryException | When an error occurred with querying Symbol Data from TwinCAT || TcComMethodCallException | When an error occurred with invoking a RPC Method over ADS

Documentation

Detailed documentation of the TcContext code base itself can be found under the /docs/ folder in this repository.

Acknowledgments

License

Licensed under MIT License.

Copyright (c) 2020 Dmitrij Trifanov [email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.