UNPKG

s7webserverapi

Version:

Unofficial Simatic-S7-Webserver JSON-RPC-API Client for S7-1200/1500 PLCs

170 lines (169 loc) 5.92 kB
import { Subject } from "rxjs"; /** * Prefix-Trie implementation for storing subscribers. * Since we call the PLC/HMI-Vars with a flattened-key e.g. "test.key.value" but we also are able to subscribe to an object instead of a leaf value, we store the subscribers in a trie. * * With that we call all subscribers of a key, when either the key itself or a children changes. * * @export * @class SubscriberTrie * @typedef {SubscriberTrie} * @template FlattenKeyType */ export class SubscriberTrie { /** * Creates an instance of SubscriberTrie. * * @constructor */ constructor() { this.root = new SubscriberTrieNode(); } parseFlattenedKey(flattenedKey) { return flattenedKey.split('.').map(key => { // Check if the key is a number and convert it if so return isNaN(Number(key)) ? key : Number(key); }); } /** * returns the subscriber at that key, if it exists. * * @param {FlattenKeys<FlattenKeyType>} keyString * @returns {Subject<{value:PLCConnectorDataTypes, changedKey: string}> | undefined} */ get(keyString) { let currentNode = this.root; const keyArray = this.parseFlattenedKey(keyString); for (const element of keyArray) { if (!currentNode.children.has(element)) { return; } currentNode = currentNode.children.get(element); } return currentNode.subscriber; } getSubscriptionCountMap() { const map = new Map(); // Go through all the children of the root and get the subscriber count for (const [key, value] of this.root.children) { this.getSubscriptionCountMapRecursive(value, key, map); } return map; } getSubscriptionCountMapRecursive(value, key, map) { if (value.subscriberCount > 0) { map.set(key, value.subscriberCount); } for (const [childKey, childValue] of value.children) { this.getSubscriptionCountMapRecursive(childValue, key + "." + childKey, map); } } incrementSubscriberCount(key) { let currentNode = this.root; const keyArray = this.parseFlattenedKey(key); for (const element of keyArray) { if (!currentNode.children.has(element)) { return; } currentNode = currentNode.children.get(element); } currentNode.subscriberCount++; } decrementSubscriberCount(key) { let currentNode = this.root; const keyArray = this.parseFlattenedKey(key); for (const element of keyArray) { if (!currentNode.children.has(element)) { return; } currentNode = currentNode.children.get(element); } currentNode.subscriberCount--; } /** * Checks if a subscriber at the key exists. * Important: It checks for the subscriber, not if the key exists in the trie. * * @param {FlattenKeys<FlattenKeyType>} key * @returns {boolean} */ has(key) { let currentNode = this.root; const keyArray = this.parseFlattenedKey(key); for (const element of keyArray) { if (!currentNode.children.has(element)) { return false; } currentNode = currentNode.children.get(element); } return currentNode.subscriber !== undefined; } /** * Inserts a new key into the trie and automatically creates a subscriber. * Every key only has one subscriber. * * @param {FlattenKeys<FlattenKeyType>} key */ insert(key) { let currentNode = this.root; const keyArray = this.parseFlattenedKey(key); for (const element of keyArray) { if (!currentNode.children.has(element)) { currentNode.children.set(element, new SubscriberTrieNode()); } currentNode = currentNode.children.get(element); } // finally add the subscriber to the last node currentNode.subscriber = new Subject(); } /** * Notifies all subscriber of the key with all its parent-prefixes. * * E.g if we call notifySubscriber("test.key.value"); We call the subscribers for test, for test.key and for test.key.value (if they exist) * * The data key is put into this function as a helper, because we need to notify the subscriber with the respective data. * The data-object has to follow the strucutre of the trie or rather of the key that is notified. * * If the key is "test.key.value" and the data is {test: {key: {value:1}}}. test.key is notified with {value: 1} and test.key.value is notified with 1 * * * @param {FlattenKeys<FlattenKeyType>} key * @param {PLCConnectorKeys<PLCConnectorDataTypes>} data */ notifySubscriber(key, data) { let currentNode = this.root; let reference = data; const keyArray = this.parseFlattenedKey(key); const residualKey = key; for (const element of keyArray) { if (!currentNode.children.has(element)) { return; } currentNode = currentNode.children.get(element); if (reference[element] == undefined) { return; } reference = reference[element]; currentNode.subscriber?.next({ value: JSON.parse(JSON.stringify(reference)), changedKey: residualKey }); } } } /** * Node of the subscriber trie * * @export * @class SubscriberTrieNode * @typedef {SubscriberTrieNode} * @template FlattenKeyType */ export class SubscriberTrieNode { /** * Creates an instance of SubscriberTrieNode. * * @constructor */ constructor() { this.subscriberCount = 0; this.children = new Map(); } }