UNPKG

tc-context

Version:

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

355 lines (354 loc) 16.7 kB
"use strict"; // tc-type-registry.ts /** * Module containing the Type Registry, responsible for fetching the ADS Type Data through the {@link TcContext} Component, processing it * and building a Type Map, which can be used later for Symbol Generation. * * * 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.TcTypeRegistry = void 0; //----IMPORTS... const debug_1 = __importDefault(require("debug")); const tc_com_1 = require("./tc-com"); const tc_type_1 = require("./tc-type"); const tc_event_1 = require("./tc-event"); /** * Class responsible for creating and managing the TwinCAT Type Map, which is fetched * from the TwinCAT's ADS layer. */ class TcTypeRegistry extends tc_event_1.TcEmitter { /** * Constructor, which uses the {@link TcContext}'s {@link TcCom} Object for ADS Communication * * @param context - Parent {@link TcContext}, of whom `TcTypeRegistry` is a part of, and whom to propagate events to * @param debug - If enabled, will produce debug information */ constructor(context, debug = false) { super(context); /** * Will store the created Type Map * @internal */ this.__map = {}; /** * @internal */ this.__log = debug_1.default(`TcContext::TcTypeRegistry`); this.__context = context; this.__log.enabled = debug; } ; /** * Creates the Type Map, by querying data through {@link TcContext}'s {@link TcCom} Object and processing it. * TcCom must be in a valid state before the Type Map creation can be made. * * @throws {@link TcComIsInvalidException} - TcCom has not been initialized before creating Type Map * @throws {@link TcComTypeQueryException} - Failed to query Type Data * @throws {@link TcComToRawException} - Error occurred when transforming value to raw data * */ async create() { this.__log(`create() : Starting creation of Type Registry`); const adsTypeDataMap = await this.__context.COM.types(); for (let [, adsTypeData] of Object.entries(adsTypeDataMap)) { await this.__adsTypeRegister(adsTypeData, adsTypeDataMap); } this.__log(`create() : Finished creation of Type Registry`); this.emit('created', new tc_event_1.TcTypeRegistryCreatedEvent(this.__context, this, this.__map)); } /** * Destroys the previously created Type Map */ destroy() { this.__log(`destroy() : Destroying Type Registry`); this.__map = {}; this.emit('destroyed', new tc_event_1.TcTypeRegistryDestroyedEvent(this.__context, this)); } ; /** * Registers a {@link TcType} created Object as part of the Type Map, under the provided name * * @param name - The Type Name of the created `TcType` Object * @param type - The created `TcType` Object to register with the Type Map * */ register(name, type) { this.__log(`register() : Registering Type ${name}`); this.__map[name] = type; } /** * Returns a {@link TcType} of the provided name from the Type Map. If Type does not exist * in the Type Map, returns undefined * * @param name - The name of the `TcType` to get * * @return - Either that Type Data under the provided name, or `undefined` if Type does not exist */ has(name) { return this.__map[name]; } on(eventName, listener) { super.on(eventName, listener); return this; } /** * Internal function for processing and registering a `TcType` based on the ADS Type Data collected * previously. If Type is not bindable, it is ignored and not registered. * * @param adsTypeData - The ADS Type Data to process and potentially register * @param adsTypeDataMap - The full map of ADS Type Data, for parent/child analysis * * @throws {@link TcComIsInvalidException} - Attempting to use an invalidated `TcCom` Object * @throws {@link TcComToRawException} - Error occurred when transforming value to raw data * * @return - Either the newly registered `TcType`, or `undefined`, if registration failed */ async __adsTypeRegister(adsTypeData, adsTypeDataMap) { /** * The nature of this function is recursive. Because of that check if the Type has already been registered. * Filter out types which are unsupported, and process it. If the processing results in `undefined`, skip the registration * and delete the ADS Type Data from the ADS Type Data map, to prevent unescape loops. */ const has = this.has(adsTypeData.name); if (!has) { if (!this.__adsTypeIsPointerOrReference(adsTypeData)) { if (!adsTypeDataMap[adsTypeData.name.toLowerCase()]) { return; } const tcType = await this.__adsTypeProcess(adsTypeData, adsTypeDataMap); if (tcType) { this.register(adsTypeData.name, tcType); return tcType; } else this.__adsTypeDeleteEntry(adsTypeData, adsTypeDataMap); } else this.__adsTypeDeleteEntry(adsTypeData, adsTypeDataMap); } else return has; } /** * Internal function for processing ADS Type Data, after it was initially filtered. * * @param adsTypeData - The ADS Type Data to process * @param adsTypeDataMap - The full map of ADS Type Data, for parent/child analysis * * @throws {@link TcComIsInvalidException} - Attempting to use an invalidated `TcCom` Object * @throws {@link TcComToRawException} - Error occurred when transforming value to raw data * * @return - Either the created `TcType` from the ADS Type Data, or `undefined`, if the Type is not bindable */ async __adsTypeProcess(adsTypeData, adsTypeDataMap) { /** * Check if the ADS Type has a parent. If it does, then perform processing on it, and if the * result of parent processing results in success - extend the parent type with the provided ADS * Type data. * * If the ADS Type has a parent, but the parent is not registrable - this ADS Type is also not registrable. * * If the ADS Type has no parent, then attempt to create the new `TcType` based on its parameters. */ const parent = await this.__adsTypeProcessParent(adsTypeData, adsTypeDataMap); if (parent) { return parent.extend(adsTypeData); } else { /** * Before processing the Type Data, check if this ADS Type has children. If those * children exist - process and register them first */ const children = await this.__adsTypeProcessChildren(adsTypeData, adsTypeDataMap); if (this.__adsTypeIsBit(adsTypeData)) { return tc_type_1.TcBooleanType.create(this.__context, adsTypeData, this.__log.enabled); } else if (this.__adsTypeIsNumber(adsTypeData)) { return tc_type_1.TcNumericType.create(this.__context, adsTypeData, this.__log.enabled); } else if (this.__adsTypeIsString(adsTypeData)) { return tc_type_1.TcStringType.create(this.__context, adsTypeData, this.__log.enabled); } else if (this.__adsTypeIsStruct(adsTypeData)) { if (children.length > 0) { return tc_type_1.TcStructType.create(this.__context, adsTypeData, children, this.__log.enabled); } } } } /** * Internal function for checking if an ADS Type Data has a parent, and if so, will attempt to * register that parent * * @param adsTypeData - The ADS Type, whose parent is evaluated * @param adsTypeDataMap - The full map of ADS Type Data, for parent/child analysis * * @throws {@link TcComIsInvalidException} - Attempting to use an invalidated `TcCom` Object * @throws {@link TcComToRawException} - Error occurred when transforming value to raw data * * @return - Either the processed parent's registered `TcType` Object, or `undefined` if that processing has failed */ async __adsTypeProcessParent(adsTypeData, adsTypeDataMap) { const parentAdsTypeData = this.__adsTypeHasParent(adsTypeData, adsTypeDataMap); if (parentAdsTypeData) { return this.__adsTypeRegister(parentAdsTypeData, adsTypeDataMap); } } /** * Internal function for checking if an ADS Type Data has children, and if so, will attempt to * register all the children associated with this ADS Type * * @param adsTypeData - The ADS Type, whose children are evaluated * @param adsTypeDataMap - The full map of ADS Type Data, for parent/child analysis * * @throws {@link TcComIsInvalidException} - Attempting to use an invalidated `TcCom` Object * @throws {@link TcComToRawException} - Error occurred when transforming value to raw data * * @return - List of registered `TcType` Children with their key. If no children were successfully registered - the list is empty */ async __adsTypeProcessChildren(adsTypeData, adsTypeDataMap) { var _a; /** * Iterate over all the potential children. * If children are not registrable, then remove their entry from the ADS Type Children list * to prevent redundant data */ const result = []; for (let i = 0; i < ((_a = adsTypeData.subItems) === null || _a === void 0 ? void 0 : _a.length); i++) { let child = adsTypeData.subItems[i]; let childTcType; if (adsTypeDataMap[child.type.toLowerCase()]) { childTcType = await this.__adsTypeRegister(adsTypeDataMap[child.type.toLowerCase()], adsTypeDataMap); } if (childTcType) { /** * Make sure to clone the child, so changes to the child don't affect * the original `TcType` */ const extended = await childTcType.clone(child); if (extended) { result.push({ key: child.name, type: extended }); } else this.__adsTypeDeleteChild(i, adsTypeData); } else this.__adsTypeDeleteChild(i, adsTypeData); } return result; } /** * Internal function for deleting ADS Type Data entry from the full map of ADS Type Data * Due to the non-linear pattern of registration, this is useful, as a means of preventing * redundant looping * * @param adsTypeData - The ADS Type Data to delete from map * @param adsTypeDataMap - The ADS Type Map from which the entry is deleted */ __adsTypeDeleteEntry(adsTypeData, adsTypeDataMap) { this.__log(`__adsTypeDeleteEntry() : Deleting entry for ${adsTypeData.name}`); delete adsTypeDataMap[adsTypeData.name.toLowerCase()]; } /** * Internal function for deleting ADS Child Type Data from ADS Type. * Due to the non-linear pattern of registration, this is useful, as a means of preventing * redundant looping * * @param index - Index of the ADS Child Type to be deleted * @param adsTypeData - The ADS Type from which the child is deleted */ __adsTypeDeleteChild(index, adsTypeData) { adsTypeData.subItems.splice(index, 1); } /** * Internal function, which checks if the ADS Type Data is of type `Structure` or `Function_Block` * @param adsTypeData - The ADS Type Data, which is checked for being of type `Structure` or `Function_Block` * * @return - Is true if ADS Type Data matched queried type */ __adsTypeIsStruct(adsTypeData) { return adsTypeData.adsDataType === tc_com_1.ADST.BIGTYPE && adsTypeData.subItems.length; } ; /** * Internal function, which checks if the ADS Type Data is of type `BOOL` * @param adsTypeData - The ADS Type Data, which is checked for being of type `BOOL` * * @return - Is true if ADS Type Data matched queried type */ __adsTypeIsBit(adsTypeData) { return adsTypeData.adsDataType === tc_com_1.ADST.BIT; } /** * Internal function, which checks if the ADS Type Data is of type `STRING` * @param adsTypeData - The ADS Type Data, which is checked for being of type `STRING` * * @return - Is true if ADS Type Data matched queried type */ __adsTypeIsString(adsTypeData) { return adsTypeData.adsDataType === tc_com_1.ADST.STRING || adsTypeData.adsDataType === tc_com_1.ADST.WSTRING; } /** * Internal function, which checks if the ADS Type Data is of type Integer or Floating Point Number * @param adsTypeData - The ADS Type Data, which is checked for being of type Integer or Floating Point Number * * @return - Is true if ADS Type Data matched queried type */ __adsTypeIsNumber(adsTypeData) { switch (adsTypeData.adsDataType) { //Numeric cases case tc_com_1.ADST.INT8: case tc_com_1.ADST.UINT8: case tc_com_1.ADST.INT16: case tc_com_1.ADST.UINT16: case tc_com_1.ADST.INT32: case tc_com_1.ADST.UINT32: case tc_com_1.ADST.INT64: case tc_com_1.ADST.UINT64: case tc_com_1.ADST.REAL32: case tc_com_1.ADST.REAL64: return true; default: return false; } } /** * Internal function, which check if the provided ADS Type Data, has a parent associated * with it * * @param adsTypeData - The ADS Type to check for parent presence * @param adsTypeDataMap - The full ADS Type Map, where the parent's existence is checked * * @return - The parents ADS Type Data if it exists, else `undefined` */ __adsTypeHasParent(adsTypeData, adsTypeDataMap) { if (adsTypeData.type !== '' && adsTypeData.type !== adsTypeData.name) { const parent = adsTypeDataMap[adsTypeData.type.toLowerCase()]; if ((parent === null || parent === void 0 ? void 0 : parent.adsDataType) === adsTypeData.adsDataType) { return parent; } } } /** * Internal function, which filters out Pointers and References * * @param adsTypeData - The ADS Type Data, which is checked for being either a Pointer or a Reference * * @return - Is true if the passed ADS Type Data is of type Pointer or Reference */ __adsTypeIsPointerOrReference(adsTypeData) { return (adsTypeData.name.startsWith('POINTER TO ') || adsTypeData.name.startsWith('REFERENCE TO ')); } } exports.TcTypeRegistry = TcTypeRegistry;