tc-context
Version:
TwinCAT ADS Communication Library for creating an active TwinCAT Context, with automatic symbol and type mapping
949 lines (948 loc) • 46.8 kB
JavaScript
"use strict";
// tc-binding.ts
/**
* Module, which contains the definitions of all types of Bindings, which act as a layer between `TcSymbol` and the
* `TcCom` Object. It manages type checking, all the communication, and event emission, as well as memory locations, which
* are associated with the `TcSymbol`
*
*
* Licensed under MIT License.
*
* Copyright (c) 2020 Dmitrij Trifanov <d.v.trifanov@gmail.com>
*
* 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.
*
* @packageDocumentation
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TcNamespaceBinding = exports.TcArrayBinding = exports.TcStructureBinding = exports.TcEnumBinding = exports.TcStringBinding = exports.TcNumericBinding = exports.TcBooleanBinding = exports.TcSymbolBinding = exports.TcBinding = void 0;
//----IMPORTS...
const debug_1 = __importDefault(require("debug"));
const check_types_1 = __importDefault(require("check-types"));
const tc_com_1 = require("./tc-com");
const tc_exception_1 = require("./tc-exception");
const tc_event_1 = require("./tc-event");
/**
* Class which acts as an abstraction layer between a `TcSymbol` and the `TcCom` layer.
* It is responsible for value conversion to Data and from it, as well as Type Checking,
* storing all the Memory location which must be read, how to execute clearing of a Symbol.
*
* By itself, the `TcBinding` also acts as a Symbol Pointer, which is used for subscribing
* for change notifications
*
* Lastly, it is also the EventEmitter for the `TcSymbol`
*
*/
class TcBinding extends tc_event_1.TcEmitter {
/**
* Constructor for a Binding with no information on memory location, but definition of its components
* and different parameters, used by derived classes
*
* @param context - The `TcContext` which owns this binding
* @param symbol - The `TcSymbol` which owns this binding
* @param parent - Parent Emitter, to whom a event will be propagate to
* @param onSet - Alias, which to use in place of 'set'
* @param onGet - Alias, which to use in place of 'get'
* @param onClear - Alias, which to use in place of 'cleared'
* @param onChange - Alias, which to use in place of 'changed'
* @param debug - If enabled, will produce debug information
*/
constructor(context, symbol, parent, onSet, onGet, onClear, onChange, debug = false) {
super(parent);
/**
* Stores an array of memory locations, which can be read
* @internal
*/
this.__readPackages = [];
/**
* Stores an array of data packages locations, which can be used to clear the `TcBinding`
* @internal
*/
this.__clearPackages = [];
/**
* @internal
*/
this.__indexGroup = 0;
/**
* @internal
*/
this.__indexOffset = 0;
/**
* @internal
*/
this.__size = 0;
/**
* @internal
*/
this.__log = debug_1.default(`TcContext::TcBinding`);
this.__context = context;
this.__symbol = symbol;
this.__isValid = true;
this.__log.enabled = debug;
this.__onSet = onSet || 'set';
this.__onGet = onGet || 'get';
this.__onClear = onClear || 'cleared';
this.__onChange = onChange || 'changed';
this.__log(`Creating TcBinding[${this.__symbol.$path}]`);
}
/**
* Index Group of this `TcBinding`
*/
get indexGroup() { return this.__indexGroup; }
;
/**
* Index Offset of this `TcBinding`
*/
get indexOffset() { return this.__indexOffset; }
;
/**
* Size of the Symbol, this `TcBinding` points to
*/
get size() { return this.__size; }
;
/**
* The `TcSymbol` owner of this `TcBinding`
*/
get symbol() { return this.__symbol; }
;
/**
* The `TcContext` owner of this `TcBinding`
*/
get context() { return this.__context; }
;
/**
* Flag, when if true, `TcBinding` is valid
*/
get isValid() { return this.__isValid; }
/**
* Flag, when if true, `TcBinding` is ReadOnly an no write operation can be invoked
*/
get readOnly() { return this.symbol.$readOnly; }
;
/**
* Performs a read of all the Memory Pointers, belonging to this `TcBinding`
*
* @throws {@link TcBindingIsInvalidException} - Attempting to use an invalid `TcBinding`
* @throws {@link TcBindingOutOfRangeException} - Failure splitting reading buffer
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComDataReadException} - Failed to read data pointers
* @throws {@link TcComFromRawException} - Failed to convert the Raw Data
*
* @return - Values, of the Target PLC Symbol, which belong to this `TcBinding`
*
*/
async read() {
this.__log(`read() : Reading from TcBinding[${this.symbol.$path}]`);
if (!this.isValid)
throw new tc_exception_1.TcBindingIsInvalidException(this.context, this, `Attempting to read an Invalidated TcBinding[${this.symbol.$path}]`);
const result = await this.context.COM.read(this.readPackages).then(dataPackages => this.fromRaw(dataPackages));
this.__log(`read() : Completed reading TcBinding[${this.symbol.$path}]`);
this.__log(result);
return result;
}
/**
* Performs a write operation by converting values to memory locations and data to send,
* which are part of this `TcBinding`
*
* @param value - The value that is to be written to the `TcBinding`
*
* @throws {@link TcBindingIsInvalidException} - Attempting to use an invalid `TcBinding`
* @throws {@link TcBindingOutOfRangeException} - Failure splitting reading buffer
* @throws {@link TcBindingReadOnlyException} - Attempting to write to a ReadOnly `TcBinding`
* @throws {@link TcBindingInvalidTypeException} - Type mismatch with one a value, that is to be written
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComDataWriteException} - Failed to write data packages
* @throws {@link TcComToRawException} - Failed to convert the Raw Data
*
* @return - The value which was written to the Target PLC Symbol, which belong to this `TcBinding`
*
*/
async write(value) {
this.__log(`write() : Writing to TcBinding[${this.symbol.$path}]`);
this.__log(value);
if (!this.isValid)
throw new tc_exception_1.TcBindingIsInvalidException(this.context, this, `Attempting to write to an Invalidated TcBinding[${this.symbol.$path}]`);
await this.toRaw(value).then(dataPackages => this.context.COM.write(dataPackages));
this.__log(`write() : Completed writing TcBinding[${this.symbol.$path}]`);
return value;
}
/**
* Clears the data of all non-ReadOnly `TcBindings`, which belong to this `TcBinding`
*
* @throws {@link TcBindingIsInvalidException} - Attempting operation on an invalidated `TcBinding`
* @throws {@link TcBindingReadOnlyException} - Attempting to clear a ReadOnly `TcBinding`
* @throws {@link TcComIsInvalidException} - Attempting operation on an invalidated `TcCom` Object
* @throws {@link TcComDataWriteException} - Failed to write data packages
*
*/
async clear() {
this.__log(`clear() : Clearing to TcBinding[${this.symbol.$path}]`);
if (!this.isValid)
throw new tc_exception_1.TcBindingIsInvalidException(this.context, this, `Attempting to clear an Invalidated TcBinding[${this.symbol.$path}]`);
if (this.readOnly)
throw new tc_exception_1.TcBindingReadOnlyException(this.context, this, `Attempting to clear a Readonly TcBinding[${this.symbol.$path}]`);
await this.context.COM.write(this.clearPackages);
this.__log(`write() : Completed clearing TcBinding[${this.symbol.$path}]`);
return;
}
/**
* Checks the input, to see if it valid and can be safely written to the Target PLC
*
* @throws {@link TcBindingIsInvalidException} - Attempting operation on an invalidated `TcBinding`
* @throws {@link TcBindingReadOnlyException} - Attempting to write to a ReadOnly `TcBinding`
*
* @param value - The value to check for validity
*/
checkInput(value) {
if (!this.isValid)
throw new tc_exception_1.TcBindingIsInvalidException(this.context, this, `Attempting to convert Data to TcDataPackage using an Invalidated TcBinding[${this.symbol.$path}]`);
if (this.readOnly)
throw new tc_exception_1.TcBindingReadOnlyException(this.context, this, `Attempting to write to a Readonly TcBinding[${this.symbol.$path}]`);
}
/**
* Emits a 'set' event, unless it was aliased to a custom name
*
* @param data - The data, to pass along with the event
*/
emitSet(data) { this.emit(this.__onSet, data); }
/**
* Emits a 'get' event, unless it was aliased to a custom name
*
* @param data - The data, to pass along with the event
*/
emitGet(data) { this.emit(this.__onGet, data); }
/**
* Emits a 'cleared' event, unless it was aliased to a custom name
*
* @param data - The data, to pass along with the event
*/
emitCleared(data) { this.emit(this.__onClear, data); }
/**
* Emits a 'changed' event, unless it was aliased to a custom name
*
* @param data - The data, to pass along with the event
*/
emitChange(data) { this.emit(this.__onChange, data); }
/**
* Performs a subscription of this `TcBinding` for monitoring value change
*
* @throws {@link TcBindingIsInvalidException} - Attempting operation on an invalidated `TcBinding`
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComSubscribeException} - Failed to subscribe to the provided pointer
*
* @param sampling - The speed at which change is detected
* @param callback - Callback, which is invoked when a change does happened
*/
async subscribe(sampling, callback) {
if (!this.isValid)
throw new tc_exception_1.TcBindingIsInvalidException(this.context, this, `Attempting to subscribe to an Invalidated TcBinding[${this.symbol.$path}]`);
if (!this.__subscription) {
this.__subscription = await this.context.COM.subscribe(sampling, this, async () => {
this.__log(`subscribe#callback() : Received change event TcBinding ${this.symbol.$path}`);
if (callback) {
const result = await this.read();
callback(result);
}
});
this.__log(`subscribe() : Successfully subscribed to TcBinding TcBinding[${this.symbol.$path}]`);
}
else
this.__log(`subscribe() : Attempting to subscribe to an already subscribed TcBinding[${this.symbol.$path}]`);
}
/**
* Unsubscribes this `TcBinding` from monitoring value changes
*
* @throws {@link TcBindingIsInvalidException} - Attempting operation on an invalidated `TcBinding`
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComUnsubscribeException} - Failed to unsubscribe the handle
*/
async unsubscribe() {
if (!this.isValid)
throw new tc_exception_1.TcBindingIsInvalidException(this.context, this, `Attempting to unsubscribe from an Invalidated TcBinding[${this.symbol.$path}]`);
if (this.__subscription) {
await this.context.COM.unsubscribe(this.__subscription);
this.__subscription = undefined;
this.__log(`unsubscribe() : Successfully unsubscribed from TcBinding TcBinding[${this.symbol.$path}]`);
}
else
this.__log(`unsubscribe() : Attempting to unsubscribe from an already unsubscribed TcBinding[${this.symbol.$path}]`);
}
/**
* Get all the Data Packages, needed to perform a clear operation
*/
get clearPackages() { return this.__clearPackages; }
;
/**
* Get all the Memory Pointers, needed to perform a read operation
*/
get readPackages() { return this.__readPackages; }
;
/**
* Invalidates the provided `TcBinding`
*
* @param binding - The `TcBinding`, which is to be invalidated
*/
static invalidate(binding) {
binding.__isValid = false;
}
}
exports.TcBinding = TcBinding;
/**
* Base class for `TcBindings` used on Target PLC Symbols. This excludes `PROGRAMS` and
* variable lists
*/
class TcSymbolBinding extends TcBinding {
/**
* Constructs a binding with information of the Symbol location, and the default Type Parameters
*
* @param symbol - The `TcSymbol` which owns this binding
* @param pointer - The memory location in the PLC, where the Symbol is located
* @param parameters - Symbol Type data
* @param parent - The parent of this Symbol, to whom events are propagated
* @param debug - If enabled, will produce debug information
*/
constructor(symbol, pointer, parameters, parent, debug = false) {
super(parameters.context, symbol, parent, parameters.onGet, parameters.onSet, parameters.onClear, parameters.onChange, debug);
this.__indexGroup = pointer.indexGroup;
this.__indexOffset = pointer.indexOffset;
this.__size = pointer.size;
}
}
exports.TcSymbolBinding = TcSymbolBinding;
/**
* Base class for `TcBindings` used on PlC Symbols, that are not structured.
* This excludes `Structures`, `Function_Blocks` and `Unions`
*
* `TcSimpleBinding` have an explicit default value
*/
class TcSimpleBinding extends TcSymbolBinding {
/**
* Constructs a binding with information of the Symbol location, and the default Type Parameters
*
* @param symbol - The `TcSymbol` which owns this binding
* @param pointer - The memory location in the PLC, where the Symbol is located
* @param parameters - Symbol Type data
* @param parent - The parent of this Symbol, to whom events are propagated
* @param debug - If enabled, will produce debug information
*/
constructor(symbol, pointer, parameters, parent, debug = false) {
super(symbol, pointer, parameters, parent, debug);
this.__defaultValue = parameters.defaultBuffer;
this.__type = parameters.name;
this.__readPackages.push({ indexGroup: pointer.indexGroup, indexOffset: pointer.indexOffset, size: pointer.size });
if (this.__defaultValue !== undefined) {
this.__clearPackages.push({ indexGroup: this.indexGroup, indexOffset: this.indexOffset, data: this.__defaultValue });
}
}
/**
* Converts Data Packages from ADS to Values
*
* @throws {@link TcBindingIsInvalidException} - Attempting to use an invalid `TcBinding`
* @throws {@link TcBindingOutOfRangeException} - Failure splitting reading buffer
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComFromRawException} - Failed to convert the Raw Data
*
* @param dataPackages - The ADS Data packages, that are to be transformed
*/
async fromRaw(dataPackages) {
this.__log(`fromRaw() : Parsing Data Package for TcBinding[${this.symbol.$path}]`);
if (!this.isValid)
throw new tc_exception_1.TcBindingIsInvalidException(this.context, this, `Attempting to convert TcDataPackage using an Invalidated TcBinding[${this.symbol.$path}]`);
if (dataPackages.length > 1)
throw new tc_exception_1.TcBindingOutOfRangeException(this.context, this, `Attempting to convert TcDataPackage length greater than 1 to a Simple TcBinding[${this.symbol.$path}]`);
const value = await this.context.COM.fromRaw(this.__type, dataPackages[0].data);
this.__log(`fromRaw() : Parsed Data Package to value ${value} for TcBinding ${this.symbol.$path}`);
return value;
}
/**
* Converts Data Packages from ADS to Values
*
* @throws {@link TcBindingIsInvalidException} - Attempting to use an invalid `TcBinding`
* @throws {@link TcBindingOutOfRangeException} - Failure splitting reading buffer
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComFromRawException} - Failed to convert the Raw Data
*
* @param dataPackages - The ADS Data packages, that are to be transformed
*/
async toRaw(value) {
this.__log(`toRaw() : Parsing value ${value} to Raw for TcBinding[${this.symbol.$path}]`);
this.checkInput(value);
const result = await this.context.COM.toRaw(this.__type, value).then(buffer => [{ indexGroup: this.indexGroup, indexOffset: this.indexOffset, data: buffer }]);
this.__log(`toRaw() : Parsed value ${value} to Raw for TcBinding[${this.symbol.$path}]`);
return result;
}
/**
* Checks the input, to see if it valid and can be safely written to the Target PLC
*
* @throws {@link TcBindingIsInvalidException} - Attempting operation on an invalidated `TcBinding`
* @throws {@link TcBindingReadOnlyException} - Attempting to write to a ReadOnly `TcBinding`
*
* @param value - The value to check for validity
*/
checkInput(value) {
super.checkInput(value);
if (!check_types_1.default.primitive(value) && !check_types_1.default.instance(value, BigInt))
throw new tc_exception_1.TcBindingInvalidTypeException(this.context, this, `Attempting to write a structured Value to a Simple TcBinding[${this.symbol.$path}]`);
}
}
/**
* Base class for `TcBindings´ used for PLC Symbols of Type `Structure`, `Function_Block` and `Union`
*/
class TcComplexBinding extends TcSymbolBinding {
constructor() {
super(...arguments);
/**
* @internal
*/
this.__childrenBindings = {};
}
/**
* Internal method, for adding a Child `TcBinding` to a specified `TcComplexBinding`
*
* @param binding - The binding, to which the child is added
* @param child - The child, which is to be added to the binding
*
* @internal
*/
static addChild(binding, child) {
binding.__addChild(child);
}
/**
* Internal method, for adding a Child `TcBinding` as part of this `TcComplexBinding`
*
* @param child - The Child that is to be added
*/
__addChild(child) {
this.__childrenBindings[child.key] = child.binding;
this.__clearPackages.push(...child.binding.clearPackages);
this.__readPackages.push(...child.binding.readPackages);
}
}
/**
* `TcBinding` for attaching to `BOOL` PLC Symbol
*/
class TcBooleanBinding extends TcSimpleBinding {
/**
* Checks the input, to see if it valid and can be safely written to the Target PLC
*
* @throws {@link TcBindingIsInvalidException} - Attempting operation on an invalidated `TcBinding`
* @throws {@link TcBindingReadOnlyException} - Attempting to write to a ReadOnly `TcBinding`
*
* @param value - The value to check for validity
*/
checkInput(value) {
super.checkInput(value);
if (!check_types_1.default.boolean(value))
throw new tc_exception_1.TcBindingInvalidTypeException(this.context, this, `Attempting to write a non-boolean Value to a Boolean TcBinding[${this.symbol.$path}]`);
}
}
exports.TcBooleanBinding = TcBooleanBinding;
/**
* `TcBinding` for attaching to Numeric PLC Symbols
*/
class TcNumericBinding extends TcSimpleBinding {
/**
* Constructs a binding with information of the Symbol location, and the default Type Parameters
*
* @param symbol - The `TcSymbol` which owns this binding
* @param pointer - The memory location in the PLC, where the Symbol is located
* @param parameters - Symbol Type data
* @param parent - The parent of this Symbol, to whom events are propagated
* @param debug - If enabled, will produce debug information
*/
constructor(symbol, pointer, parameters, parent, debug = false) {
super(symbol, pointer, parameters, parent, debug);
this.__adst = parameters.adst;
this.__upperBorder = parameters.upperBorder;
this.__lowerBorder = parameters.lowerBorder;
}
/**
* Access the maximum value, that is safe to write to Symbol
*/
get upperBorder() { return this.__upperBorder; }
;
/**
* Access the minimum value, that is safe to write to Symbol
*/
get lowerBorder() { return this.__lowerBorder; }
;
/**
* Checks the input, to see if it valid and can be safely written to the Target PLC
*
* @throws {@link TcBindingIsInvalidException} - Attempting operation on an invalidated `TcBinding`
* @throws {@link TcBindingReadOnlyException} - Attempting to write to a ReadOnly `TcBinding`
*
* @param value - The value to check for validity
*/
checkInput(value) {
super.checkInput(value);
if ((this.__adst === tc_com_1.ADST.UINT64 || this.__adst === tc_com_1.ADST.INT64) && !check_types_1.default.instance(value, BigInt))
throw new tc_exception_1.TcBindingInvalidTypeException(this.context, this, `Attempting to write a non-BigInt value to Numeric TcBinding[${this.symbol.$path}]`);
if (this.__adst !== tc_com_1.ADST.UINT64 && this.__adst !== tc_com_1.ADST.INT64 && !check_types_1.default.number(value))
throw new tc_exception_1.TcBindingInvalidTypeException(this.context, this, `Attempting to write a non-number value to Numeric TcBinding[${this.symbol.$path}]`);
if (value > this.__upperBorder || value < this.__lowerBorder)
throw new tc_exception_1.TcBindingOutOfRangeException(this.context, this, `Attempting to write value, which is outside of range [ ${this.__lowerBorder}:${this.__upperBorder} ] to Numeric TcBinding[${this.symbol.$path}]`);
}
/**
* Converts Data Packages from ADS to Values
*
* @throws {@link TcBindingIsInvalidException} - Attempting to use an invalid `TcBinding`
* @throws {@link TcBindingOutOfRangeException} - Failure splitting reading buffer
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComFromRawException} - Failed to convert the Raw Data
*
* @param dataPackages - The ADS Data packages, that are to be transformed
*/
async fromRaw(value) {
const result = await super.fromRaw(value);
return (this.__adst !== tc_com_1.ADST.UINT64) ? result : BigInt.asUintN(64, result);
}
}
exports.TcNumericBinding = TcNumericBinding;
/**
* `TcBinding` for attaching to `STRING` or `WSTRING` PLC Symbols
*/
class TcStringBinding extends TcSimpleBinding {
/**
* Constructs a binding with information of the Symbol location, and the default Type Parameters
*
* @param symbol - The `TcSymbol` which owns this binding
* @param pointer - The memory location in the PLC, where the Symbol is located
* @param parameters - Symbol Type data
* @param parent - The parent of this Symbol, to whom events are propagated
* @param debug - If enabled, will produce debug information
*/
constructor(symbol, pointer, parameters, parent, debug = false) {
super(symbol, pointer, parameters, parent, debug);
this.__length = parameters.length;
}
/**
* Access the maximum length of a string, that is safe to write to the PLC Symbol
*/
get length() { return this.__length; }
;
/**
* Checks the input, to see if it valid and can be safely written to the Target PLC
*
* @throws {@link TcBindingIsInvalidException} - Attempting operation on an invalidated `TcBinding`
* @throws {@link TcBindingReadOnlyException} - Attempting to write to a ReadOnly `TcBinding`
*
* @param value - The value to check for validity
*/
checkInput(value) {
super.checkInput(value);
if (!check_types_1.default.string(value))
throw new tc_exception_1.TcBindingInvalidTypeException(this.context, this, `Attempting to write a non-string Value to a String TcBinding[${this.symbol.$path}]`);
if (value.length > this.__length)
throw new tc_exception_1.TcBindingOutOfRangeException(this.context, this, `Attempting to write a string longer than ${this.__length} to a String TcBinding[${this.symbol.$path}]`);
}
}
exports.TcStringBinding = TcStringBinding;
/**
* `TcBinding` for attaching to `ENUM` PLC Symbols
*/
class TcEnumBinding extends TcSimpleBinding {
/**
* Constructs a binding with information of the Symbol location, and the default Type Parameters
*
* @param symbol - The `TcSymbol` which owns this binding
* @param pointer - The memory location in the PLC, where the Symbol is located
* @param parameters - Symbol Type data
* @param parent - The parent of this Symbol, to whom events are propagated
* @param debug - If enabled, will produce debug information
*/
constructor(symbol, pointer, parameters, parent, debug = false) {
super(symbol, pointer, parameters, parent, debug);
/**
* @internal
*/
this.__buffers = {};
this.__fields = parameters.fields;
this.__buffers = parameters.buffers;
}
/**
* Access the fields, which are allowed to be written to the PLC Symbol
*/
get fields() { return this.__fields; }
;
/**
* Converts Data Packages from ADS to Values
*
* @throws {@link TcBindingIsInvalidException} - Attempting to use an invalid `TcBinding`
* @throws {@link TcBindingOutOfRangeException} - Failure splitting reading buffer
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComFromRawException} - Failed to convert the Raw Data
*
* @param dataPackages - The ADS Data packages, that are to be transformed
*/
async fromRaw(value) {
const result = (await super.fromRaw(value));
return `${this.__type}.${result.name}`;
}
/**
* Converts Data Packages from ADS to Values
*
* @throws {@link TcBindingIsInvalidException} - Attempting to use an invalid `TcBinding`
* @throws {@link TcBindingOutOfRangeException} - Failure splitting reading buffer
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComFromRawException} - Failed to convert the Raw Data
*
* @param dataPackages - The ADS Data packages, that are to be transformed
*/
async toRaw(value) {
super.checkInput(value);
if (!check_types_1.default.string(value))
throw new tc_exception_1.TcBindingInvalidTypeException(this.context, this, `Attempting to write a non-enum Value to an Enum TcBinding[${this.symbol.$path}]`);
if (!this.__buffers[value])
throw new tc_exception_1.TcBindingOutOfRangeException(this.context, this, `Attempting to write a non-existent enum field to an Enum TcBinding[${this.symbol.$path}]`);
return [{ indexGroup: this.indexGroup, indexOffset: this.indexOffset, data: this.__buffers[value] }];
}
}
exports.TcEnumBinding = TcEnumBinding;
/**
* `TcBinding` for attaching to `Structures`, `Function_Blocks` or `Unions` PLC Symbols
*/
class TcStructureBinding extends TcComplexBinding {
/**
* Checks the input, to see if it valid and can be safely written to the Target PLC
*
* @throws {@link TcBindingIsInvalidException} - Attempting operation on an invalidated `TcBinding`
* @throws {@link TcBindingReadOnlyException} - Attempting to write to a ReadOnly `TcBinding`
*
* @param value - The value to check for validity
*/
checkInput(value) {
super.checkInput(value);
if (!check_types_1.default.object(value))
throw new tc_exception_1.TcBindingInvalidTypeException(this.context, this, `Attempting to write a non-structured Value to a Structured TcBinding[${this.symbol.$path}]`);
}
/**
* Converts Data Packages from ADS to Values
*
* @throws {@link TcBindingIsInvalidException} - Attempting to use an invalid `TcBinding`
* @throws {@link TcBindingOutOfRangeException} - Failure splitting reading buffer
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComFromRawException} - Failed to convert the Raw Data
*
* @param dataPackages - The ADS Data packages, that are to be transformed
*/
async fromRaw(dataPackages) {
this.__log(`fromRaw() : Parsing Data Package for Structured TcBinding[${this.symbol.$path}]`);
if (!this.isValid)
throw new tc_exception_1.TcBindingIsInvalidException(this.context, this, `Attempting to convert TcDataPackage using an Invalidated TcBinding[${this.symbol.$path}]`);
const result = {};
const promises = [];
let start = 0;
for (let [memberName, memberValue] of Object.entries(this.__childrenBindings)) {
this.__log(`fromRaw() : -> Accessing [${memberName}] Child for Value conversion of TcBinding[${this.symbol.$path}]`);
let length = memberValue.readPackages.length;
result[memberName] = undefined;
promises.push(memberValue.fromRaw(dataPackages.slice(start, start + length)).then(convertedValue => { result[memberName] = convertedValue; }));
start += length;
}
await Promise.all(promises);
this.__log(`fromRaw() : Parsed Data Package for Structured TcBinding[${this.symbol.$path}]`);
return result;
}
/**
* Converts Data Packages from ADS to Values
*
* @throws {@link TcBindingIsInvalidException} - Attempting to use an invalid `TcBinding`
* @throws {@link TcBindingOutOfRangeException} - Failure splitting reading buffer
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComFromRawException} - Failed to convert the Raw Data
*
* @param dataPackages - The ADS Data packages, that are to be transformed
*/
async toRaw(value) {
this.__log(`toRaw() : Parsing structured value to Raw for Structured TcBinding[${this.symbol.$path}]`);
this.checkInput(value);
let promises = [];
for (let [memberName, memberValue] of Object.entries(value)) {
this.__log(`toRaw() : -> accessing [${memberValue.key}] Member for Raw conversion of TcBinding[${this.symbol.$path}]`);
const child = this.__childrenBindings[memberName];
if (child) {
promises.push(child.toRaw(memberValue));
}
else
throw new tc_exception_1.TcBindingOutOfRangeException(this.context, this, `Attempting to write a non-existing Member field [${memberName}] of TcBinding[${this.symbol.$path}]`);
}
return Promise.all(promises).then(dataPackages => {
this.__log(`toRaw() : Parsed Value for Structured TcBinding[${this.symbol.$path}]`);
const result = [];
dataPackages.forEach(dataPackage => result.push(...dataPackage));
return result;
});
}
/**
* Will attempt to invoke the provided method, based on the variable path and the method name, with the
* provided arguments.
*
* As of now, no type checking is performed on the passed arguments, and this function acts as a simple through put
* to the `TcCom` Module
*
* @param path - The full path to the `Function_Block`, whose method is called
* @param method - The method name, that is to be called
* @param args - All the arguments, as an object, that are passed to the method
*
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComMethodCallException} - Failed to call the Rpc Method on the PLC Side
*
* @return - The result of the method call
*
*/
async callMethod(path, method, args) {
return this.context.COM.callMethod(path, method, args);
}
}
exports.TcStructureBinding = TcStructureBinding;
/**
* `TcBinding` for attaching to `ARRAY OF...` PLC Symbols
*/
class TcArrayBinding extends TcComplexBinding {
/**
* Constructs a binding with information of the Symbol location, and the default Type Parameters
*
* @param symbol - The `TcSymbol` which owns this binding
* @param pointer - The memory location in the PLC, where the Symbol is located
* @param parameters - Symbol Type data
* @param dimension - The dimension definition of this Array Symbol
* @param parent - The parent of this Symbol, to whom events are propagated
* @param debug - If enabled, will produce debug information
*/
constructor(symbol, pointer, parameters, dimension, parent, debug = false) {
super(symbol, pointer, parameters, parent, debug);
this.__startIndex = dimension.startIndex;
this.__length = dimension.length;
}
/**
* Access the Start Index of this Array
*/
get startIndex() { return this.__startIndex; }
;
/**
* Access the length of this Array
*/
get length() { return this.__length; }
/**
* Checks the input, to see if it valid and can be safely written to the Target PLC
*
* @throws {@link TcBindingIsInvalidException} - Attempting operation on an invalidated `TcBinding`
* @throws {@link TcBindingReadOnlyException} - Attempting to write to a ReadOnly `TcBinding`
*
* @param value - The value to check for validity
*/
checkInput(value) {
super.checkInput(value);
if (!check_types_1.default.array(value))
throw new tc_exception_1.TcBindingInvalidTypeException(this.context, this, `Attempting to write a non-array Value to an Array TcBinding[${this.symbol.$path}]`);
if (value.length > this.__length)
throw new tc_exception_1.TcBindingOutOfRangeException(this.context, this, `Attempting to write an array of length [${value.length}] to a Array of length ${this.__length} of TcBinding[${this.symbol.$path}]`);
}
/**
* Converts Data Packages from ADS to Values
*
* @throws {@link TcBindingIsInvalidException} - Attempting to use an invalid `TcBinding`
* @throws {@link TcBindingOutOfRangeException} - Failure splitting reading buffer
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComFromRawException} - Failed to convert the Raw Data
*
* @param dataPackages - The ADS Data packages, that are to be transformed
*/
async toRaw(value) {
this.__log(`toRaw() : Parsing array value to Raw for Array TcBinding[${this.symbol.$path}]`);
this.checkInput(value);
let promises = [];
value.forEach((indexedValue, index) => {
this.__log(`toRaw() : -> accessing [${index + this.__startIndex}] Index for Raw conversion of TcBinding[${this.symbol.$path}]`);
const child = this.__childrenBindings[index + this.__startIndex];
if (child) {
promises.push(child.toRaw(indexedValue));
}
else
throw new tc_exception_1.TcBindingOutOfRangeException(this.context, this, `Attempting to write a non-existing Index field [${index + this.__startIndex}] of TcBinding[${this.symbol.$path}]`);
});
this.__log(`toRaw() : Parsed Value for Array TcBinding[${this.symbol.$path}]`);
return Promise.all(promises).then(dataPackages => {
const result = [];
dataPackages.forEach(dataPackage => result.push(...dataPackage));
return result;
});
}
/**
* Converts Data Packages from ADS to Values
*
* @throws {@link TcBindingIsInvalidException} - Attempting to use an invalid `TcBinding`
* @throws {@link TcBindingOutOfRangeException} - Failure splitting reading buffer
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComFromRawException} - Failed to convert the Raw Data
*
* @param dataPackages - The ADS Data packages, that are to be transformed
*/
async fromRaw(dataPackages) {
this.__log(`fromRaw() : Parsing Data Package for Array TcBinding[${this.symbol.$path}]`);
if (!this.isValid)
throw new tc_exception_1.TcBindingIsInvalidException(this.context, this, `Attempting to convert TcDataPackage using an Invalidated TcBinding[${this.symbol.$path}]`);
const result = [];
const promises = [];
let start = 0;
for (let [memberName, memberValue] of Object.entries(this.__childrenBindings)) {
this.__log(`fromRaw() : -> accessing [${memberName}] Index for Value conversion of TcBinding[${this.symbol.$path}]`);
let length = memberValue.readPackages.length;
result[parseInt(memberName) - this.__startIndex] = undefined;
promises.push(memberValue.fromRaw(dataPackages.slice(start, start + length)).then(convertedValue => { result[parseInt(memberName) - this.__startIndex] = convertedValue; }));
start += length;
}
await Promise.all(promises);
this.__log(`fromRaw() : Parsed Data Package for Array TcBinding[${this.symbol.$path}]`);
return result;
}
}
exports.TcArrayBinding = TcArrayBinding;
/**
* `TcBinding` for attaching to `PROGRAMS` or `Variable Lists` PLC Symbols.
*
* The `TcNamespaceBinding` is unique, because it has no parent - it is the entry point,
* as well as its Memory Definition is based on the Children passed to it
*/
class TcNamespaceBinding extends TcBinding {
/**
* Constructor a namespace Binding, which will grow and adjust, as children are added to it
*
* @param context - The `TcContext` which owns this binding
* @param symbol - The `TcSymbol` which owns this binding
* @param parent - Parent Emitter, to whom a event will be propagate to
* @param debug - If enabled, will produce debug information
*/
constructor(context, symbol, parent, debug = false) {
super(context, symbol, parent, undefined, undefined, undefined, undefined, debug);
/**
* @internal
*/
this.__childrenBindings = {};
}
/**
* Converts Data Packages from ADS to Values
*
* @throws {@link TcBindingIsInvalidException} - Attempting to use an invalid `TcBinding`
* @throws {@link TcBindingOutOfRangeException} - Failure splitting reading buffer
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComFromRawException} - Failed to convert the Raw Data
*
* @param dataPackages - The ADS Data packages, that are to be transformed
*/
async fromRaw(dataPackages) {
this.__log(`fromRaw() : Parsing Data Package for Namespace TcBinding[${this.symbol.$path}]`);
if (!this.isValid)
throw new tc_exception_1.TcBindingIsInvalidException(this.context, this, `Attempting to convert TcDataPackage using an Invalidated TcBinding[${this.symbol.$path}]`);
const result = {};
const promises = [];
let start = 0;
for (let [memberName, memberValue] of Object.entries(this.__childrenBindings)) {
this.__log(`fromRaw() : -> Accessing [${memberName}] Child for Value conversion of TcBinding[${this.symbol.$path}]`);
let length = memberValue.readPackages.length;
result[memberName] = undefined;
promises.push(memberValue.fromRaw(dataPackages.slice(start, start + length)).then(convertedValue => { result[memberName] = convertedValue; }));
start += length;
}
await Promise.all(promises);
this.__log(`fromRaw() : Parsed Data Package for Namespace TcBinding[${this.symbol.$path}]`);
return result;
}
/**
* Converts Data Packages from ADS to Values
*
* @throws {@link TcBindingIsInvalidException} - Attempting to use an invalid `TcBinding`
* @throws {@link TcBindingOutOfRangeException} - Failure splitting reading buffer
* @throws {@link TcComIsInvalidException} - Attempted to use an Invalid `TcCom` Object for subscription
* @throws {@link TcComFromRawException} - Failed to convert the Raw Data
*
* @param dataPackages - The ADS Data packages, that are to be transformed
*/
async toRaw(value) {
this.__log(`toRaw() : Parsing structured value to Raw for Namespace TcBinding[${this.symbol.$path}]`);
this.checkInput(value);
let promises = [];
for (let [memberName, memberValue] of Object.entries(value)) {
this.__log(`toRaw() : -> accessing [${memberValue.key}] Member for Raw conversion of TcBinding[${this.symbol.$path}]`);
const child = this.__childrenBindings[memberName];
if (child) {
promises.push(child.toRaw(memberValue));
}
else
throw new tc_exception_1.TcBindingOutOfRangeException(this.context, this, `Attempting to write a non-existing Member field [${memberName}] of TcBinding[${this.symbol.$path}]`);
}
return Promise.all(promises).then(dataPackages => {
this.__log(`toRaw() : Parsed Value for Namespace TcBinding[${this.symbol.$path}]`);
const result = [];
dataPackages.forEach(dataPackage => result.push(...dataPackage));
return result;
});
}
/**
* Checks the input, to see if it valid and can be safely written to the Target PLC
*
* @throws {@link TcBindingIsInvalidException} - Attempting operation on an invalidated `TcBinding`
* @throws {@link TcBindingReadOnlyException} - Attempting to write to a ReadOnly `TcBinding`
*
* @param value - The value to check for validity
*/
checkInput(value) {
super.checkInput(value);
if (!check_types_1.default.object(value))
throw new tc_exception_1.TcBindingInvalidTypeException(this.context, this, `Attempting to write a non-structured Value to a Namespace TcBinding[${this.symbol.$path}]`);
}
/**
* Internal method, for adding a Child `TcSymbolBinding` as part of this `TcNamespaceBinding`
*
* The method also readjusts the indexOffset and size of this `TcNamespaceBinding`
*
* @param child - The Child that is to be added
*/
__addChild(child) {
this.__childrenBindings[child.key] = child.binding;
this.__clearPackages.push(...child.binding.clearPackages);
this.__readPackages.push(...child.binding.readPackages);
//Compute the index Offsets and Group
if (this.__indexGroup === 0) {
this.__indexGroup = child.binding.indexGroup;
}
else if (this.__indexGroup !== child.binding.indexGroup) {
throw new Error('IndexGroup of Namespace is invalid. Unsupported situation');
}
if (this.__indexOffset === 0) {
this.__indexOffset = child.binding.indexOffset;
this.__size = child.binding.size;
}
else {
if (this.__indexOffset + this.size < child.binding.indexOffset) {
this.__size = child.binding.indexOffset - this.__indexOffset + child.binding.size;
}
else if (this.__indexOffset > child.binding.indexOffset) {
this.__size = this.__indexOffset - child.binding.indexOffset + this.size;
this.__indexOffset = child.binding.indexOffset;
}
}
}
/**
* Internal method, for adding a Child `TcSymbolBinding` as part of this `TcNamespaceBinding`
*
* @param child - The Child that is to be added
*/
static addChild(binding, child) {
binding.__addChild(child);
}
}
exports.TcNamespaceBinding = TcNamespaceBinding;