diff --git a/dependencies/ggc/ggc.js b/dependencies/ggc/ggc.js deleted file mode 100644 index afbeb8d..0000000 --- a/dependencies/ggc/ggc.js +++ /dev/null @@ -1,526 +0,0 @@ -/** - * @file gate.js - * - * Permission is hereby granted to any person obtaining a copy of this software - * and associated documentation files (the "Software"), to use it for personal - * or non-commercial purposes, with the following restrictions: - * - * 1. **No Copying or Redistribution**: The Software or any of its parts may not - * be copied, merged, distributed, sublicensed, or sold without explicit - * prior written permission from the author. - * - * 2. **Commercial Use**: Any use of the Software for commercial purposes requires - * a valid license, obtainable only with the explicit consent of the author. - * - * 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. - * - * Ownership of this code remains solely with the original author. Unauthorized - * use of this Software is strictly prohibited. - * - * @summary A class to interact and manipulate machines with a non-euclidian curve - * @description A class to interact and manipulate machines with a non-euclidian curve - * @module ggc - * @exports ggc - * @version 2.0.0 - * @since 0.1.0 - * - * Author: - * - Rene De Ren - * Email: - * - rene@thegoldenbasket.nl - * -*/ - -//load local dependencies -const EventEmitter = require('events'); -const Logger = require('../../../generalFunctions/helper/logger'); -const { MeasurementContainer } = require('../../../generalFunctions/helper/measurements/index'); -const Interpolation = require('../../../predict/dependencies/predict/interpolation'); -//load all config modules -const defaultConfig = require('./ggcConfig.json'); -const ConfigUtils = require('../../../generalFunctions/helper/configUtils'); -//load registration utility -const ChildRegistrationUtils = require('../../../generalFunctions/helper/childRegistrationUtils'); - -class Ggc { - - - /*------------------- Construct and set vars -------------------*/ - constructor(ggcConfig = {}) { - - //basic setup - this.emitter = new EventEmitter(); // Own EventEmitter - this.configUtils = new ConfigUtils(defaultConfig); - this.config = this.configUtils.initConfig(ggcConfig); - - // Initialize measurements - this.measurements = new MeasurementContainer(); - this.interpolation = new Interpolation(); - this.child = {}; // object to hold child - this.actuators = []; // object to hold actuators - this.abortController = null; // new abort controller for aborting async tasks - - // Init after config is set - this.logger = new Logger(this.config.general.logging.enabled, this.config.general.logging.logLevel, this.config.general.name); - this.mode = this.config.mode.current; - - this.move_delay = this.config.settings.moveDelay ; //define opening delay in seconds between 2 gates - this.state = "gateGroupClosed"; //define default starting state of the gates - - //auto close - this.autoClose = true; - this.autoCloseTime = this.config.settings.autoClose; - this.autoCloseCnt = 0; - - //protection sensor - this.safetySensor = false; - this.retryDelay = this.config.settings.retryDelay; // in seconds - this.closeAttempt = 0; - this.maxCloseAttempts = this.config.settings.maxRetries ; - this.safetySensorCnt = 0; - - - //ground loop trigger - this.ground_loop = false; - this.ground_loop_start = Date.now(); - this.ground_loop_open = 10; //define time in seconds for when the ground loop should trigger a respons - - //define if something has gone through the gate - this.goneThrough = false; - - //define if the gate is closed - this.checkGateClosed = [false, false]; // gate 1 and gate 2 - - /* time controlled functions*/ - //this.sleep = ms => new Promise(res => setTimeout(res, ms)); - - this.childRegistrationUtils = new ChildRegistrationUtils(this); // Child registration utility - } - - - isValidSourceForMode(source, mode) { - const allowedSourcesSet = this.config.mode.allowedSources[mode] || []; - return allowedSourcesSet.has(source); - } - - isValidActionForMode(action, mode) { - const allowedActionsSet = this.config.mode.allowedActions[mode] || []; - return allowedActionsSet.has(action); - } - - sleep(ms, signal) { - return new Promise((resolve, reject) => { - const timer = setTimeout(resolve, ms); - // only attach abort listener if a valid signal is provided - if (signal && typeof signal.addEventListener === 'function') { - signal.addEventListener('abort', () => { - clearTimeout(timer); - reject(new Error('aborted')); - }); - } - }); - } - - // -------- Sequence Handlers -------- // - async executeSequence(name) { - - const sequence = this.config.sequences[name]; - const positions = this.actuators.map(a => a.state.getCurrentPosition()); - const states = this.actuators.map(a => a.state.getCurrentState()); - - if (!sequence || sequence.size === 0) { - this.logger.warn(`Sequence '${name}' not defined.`); - return; - } - - // Abort any prior sequence and start fresh - this.abortController?.abort(); - this.abortController = new AbortController(); - const { signal } = this.abortController; - - if ( states.some(s => s !== "operational") && name !== "stop2gates" ) { - this.logger.warn(`Actuators not operational, aborting sequence '${name}'.`); - this.handleInput("parent", "execSequence", "stop2gates"); - this.sleep(1000).then(() => { - this.handleInput("parent", "execSequence", name); - }); - return; - } - - try { - for (const action of sequence) { - - this.transitionToSequence(action); - - //If someone has already called abort(), skip the delay - if (signal.aborted) { - continue; - } - - //otherwise, wait for the delay - await this.sleep(this.move_delay * 1000, signal); - } - } catch (err) { - if (err.message === 'aborted') { - this.logger.debug(`Sequence '${name}' aborted mid-delay.`); - } else { - this.logger.error(`Error in sequence '${name}': ${err.stack}`); - } - } finally { - // Clean up so we know no sequence is running - this.abortController = null; - } - - } - - - async transitionToSequence(action) { - this.logger.debug(`Executing action: ${action}`); - const positions = this.actuators.map(a => a.state.getCurrentPosition()); - const states = this.actuators.map(a => a.state.getCurrentState()); - - // Perform actions based on the state - switch (action) { - case "openGate1": - this.logger.debug("Opening gate 1"); - this.actuators[0].handleInput("parent", "execMovement", 100); - this.checkGateClosed[0] = false; - break; - case "openGate2": - this.logger.debug("Opening gate 2"); - this.actuators[1].handleInput("parent", "execMovement", 100); - break; - case "stopGate1": - this.logger.debug("Stopping gate 1"); - // abort the delayed sleep, if any - this.abortController?.abort(); - // immediately stop actuator 1 - this.actuators[0].stop(); - break; - case "stopGate2": - this.logger.debug("Stopping gate 2"); - // abort the delayed sleep, if any - this.abortController?.abort(); - // immediately stop actuator 2 - this.actuators[1].stop(); - break; - case "closeGate1": - this.actuators[0].handleInput("parent", "execMovement", 0); - break; - case "closeGate2": - this.actuators[1].handleInput("parent", "execMovement", 0); - break; - default: - this.logger.warn(`Unknown state: ${state}`); - } - } - - async handleInput(source, action, parameter) { - - if (!this.isValidSourceForMode(source, this.mode)) { - this.logger.warn(`Invalid source ${source} for mode ${this.mode}`); - return; - } - - if (!this.isValidActionForMode(action, this.mode)) { - this.logger.warn(`Invalid action ${action} for mode ${this.mode}`); - return; - } - - switch (action) { - case 'execSequence': - this.executeSequence(parameter); - break; - case 'setMode': - this.setMode(parameter); - break; - default: - this.logger.warn(`Unknown action ${action}`); - } - - } - - groundLoopAction(){ - if(this.ground_loop){ - //keep track of time - this.ground_loop_time = Date.now() - this.ground_loop_trigger; - } - else{ - this.ground_loop_time = 0; - } - - if(this.ground_loop_time >= ( this.ground_loop_open * 1000) ){ - this.openGates(); - } - } - - updateMeasurement(variant, subType, value, position) { - this.logger.debug(`---------------------- updating ${subType} ------------------ `); - switch (subType) { - case "power": - // Update power measurement - this.updatePower(variant, value, position); - break; - default: - this.logger.error(`Type '${type}' not recognized for measured update.`); - return; - } - } - - updatePower(variant,value,position) { - switch (variant) { - case ("measured"): - // put value in measurements - this.measurements.type("power").variant(variant).position("wire").value(value); - this.eventUpdate(); - this.logger.debug(`Measured: ${value}`); - break; - - default: - this.logger.warn(`Unrecognized variant '${variant}' for update.`); - break; - } - } - - eventUpdate() { - // Gather raw data in arrays - const positions = this.actuators.map(a => a.state.getCurrentPosition()); - const states = this.actuators.map(a => a.state.getCurrentState()); - this.logger.debug(`States: ${JSON.stringify(states)}`); - this.logger.debug(`Positions: ${JSON.stringify(positions)}`); - const totPower = this.measurements.type("power").variant("measured").position("wire").getCurrentValue(); - - // Utility flags - const allOperational = states.every(s => s === "operational"); - const allAtOpen = positions.every(p => p === 100); - const allAtClosed = positions.every(p => p === 0); - const allAccelerating = states.every(s => s === "accelerating"); - const allDecelerating = states.every(s => s === "decelerating"); - const allStopped = states.every(s => s === "operational") && positions.every( p => p !== 0 && p != 100); - const onlyGateOneAccelerating = states[0] === "accelerating" && states[1] === "operational"; - const onlyGateTwoAccelerating = states[1] === "accelerating" && states[0] === "operational"; - const onlyGateOneDecelerating = states[0] === "decelerating" && states[1] === "operational"; - const onlyGateTwoDecelerating = states[1] === "decelerating" && states[0] === "operational"; - const oneOpenOneClosed = allOperational && positions.some(p => p === 0) && positions.some(p => p === 100); - - // Threshold for “spike” detection (tune as needed) - const SPIKE_THRESHOLD_1gate = 50; - const SPIKE_THRESHOLD_2gates = 100; - const lowerPositionThreshold = 10; // 10% of the total range - const upperPositionThreshold = 90; // 90% of the total range - - // When something is blocking the gate we need to reopen the gates (True means nothing is blocking) - if (!this.safetySensor) { - // always add 1 to the safety sensor counter - this.safetySensorCnt++; - - //add 1 to the autoclose counter to check weither we dont exceedd the max retries - if(this.autoClose) { - this.autoCloseCnt++; - } - //check if the safety sensor is triggered and the gates are closing - if( allDecelerating || onlyGateOneDecelerating || onlyGateTwoDecelerating) { - this.closeAttempt++; - this.handleInput("parent", "execSequence", "stop2gates"); - this.logger.debug("something is blocking the gate, stopping actuators"); - this.sleep(1000).then(() => { - this.handleInput("parent", "execSequence", "open2gates"); - }); - } - } - - // Detect if any single gate is decelerating into its stop - if( onlyGateOneDecelerating ) { - //check for power spike so we know the gate is closed - if ( totPower > SPIKE_THRESHOLD_1gate ) { - this.logger.debug("Gate 1 is decelerating into the stop (power spike)"); - //check flag for knowing if the gate is closed - this.checkGateClosed[0] = true; - this.closeAttempt = 0; - } - } - - if( allDecelerating || allAccelerating) { - if( totPower > SPIKE_THRESHOLD_2gates && ( positions.some(p => p > lowerPositionThreshold) || positions.some(p => p < upperPositionThreshold) ) ) { - this.logger.debug("Unexpected power spike detected"); - // stop the actuators - this.handleInput("parent", "execSequence", "stop2gates"); - } - } - - // Decide group state - if (allAtOpen && allOperational) { - this.state = "gateGroupOpened"; - //trigger auto close if count is smaller than max - if( this.autoClose && this.autoCloseCnt < this.maxCloseAttempts && this.safetySensorCnt > 0) { - this.sleep(this.autoCloseTime * 1000).then(() => { - this.handleInput("parent", "execSequence", "close2gates"); - //reset the safetySensor count because we are automatically closing the gates and if its bigger than 0 it means some1 passed through it - this.safetySensorCnt = 0; - }); - } - this.logger.debug("Gates are open"); - } - else if (allAtClosed && allOperational) { - this.state = "gateGroupClosed"; - //after everything was closed and the auto close is enabled we need to reset the auto close count - if(this.autoClose) { - this.autoCloseCnt = 0; - }; - this.logger.debug("Gates are closed"); - } - else if (oneOpenOneClosed) { - this.state = "oneGateOpenOneGateClosed"; - this.logger.debug("One gate open, one gate closed"); - } - else if (allAccelerating) { - this.state = "gateGroupAccelerating"; - this.logger.debug("Gates are accelerating"); - } - else if (onlyGateOneAccelerating) { - this.state = "gateOneAccelerating"; - this.logger.debug("Only gate 1 is accelerating"); - } - else if (onlyGateTwoAccelerating) { - this.state = "gateTwoAccelerating"; - this.logger.debug("Only gate 2 is accelerating"); - } - else if (allDecelerating) { - this.state = "gateGroupDecelerating"; - this.logger.debug("Gates are decelerating"); - } - else if (onlyGateOneDecelerating) { - this.state = "gateOneDecelerating"; - this.logger.debug("Only gate 1 is decelerating"); - } - else if (onlyGateTwoDecelerating) { - this.state = "gateTwoDecelerating"; - this.logger.debug("Only gate 2 is decelerating"); - } - else if (allStopped) { - this.state = "gateGroupStopped"; - this.logger.debug("Gates are stopped"); - } - else { - this.state = "unknown"; - this.logger.warn(`Unhandled combination: positions=${positions}, states=${states}`); - } - - // if the gates are operational and close but we dont see the truely closed state then we need to nudge the gate to force the close - - } - - getOutput() { - - // Improved output object generation - const output = {}; - - //build the output object - this.measurements.getTypes().forEach(type => { - this.measurements.getVariants(type).forEach(variant => { - - const downstreamVal = this.measurements.type(type).variant(variant).position("downstream").getCurrentValue(); - const upstreamVal = this.measurements.type(type).variant(variant).position("upstream").getCurrentValue(); - - if (downstreamVal != null) { - output[`downstream_${variant}_${type}`] = downstreamVal; - } - if (upstreamVal != null) { - output[`upstream_${variant}_${type}`] = upstreamVal; - } - if (downstreamVal != null && upstreamVal != null) { - const diffVal = this.measurements.type(type).variant(variant).difference().value; - output[`differential_${variant}_${type}`] = diffVal; - } - }); - }); - - //fill in the rest of the output object - output["mode"] = this.mode; - output["totPower"] = this.power; - //this.logger.debug(`Output: ${JSON.stringify(output)}`); - - return output; - } - -} // end of class - -module.exports = Ggc; - -/* -const ggcConfig = { - general: { - name: "TestGGC", - logging: { - enabled: true, - logLevel: "debug" - } - }, - settings: { - moveDelay: 3, - autoClose: 5, - retryDelay: 10, - maxRetries: 5 - } -}; - -const ggc = new Ggc(ggcConfig); -const linearActuator = require('../../../linearActuator/dependencies/linearActuator/linearActuator'); -const linActConfig = -{ - general: { - logging: { - enabled: true, - logLevel: "debug", - } - }, - settings: { - moveDelay: 3, - autoClose: 5, - retryDelay: 10, - maxRetries: 5 - } -}; -const stateConfig = { - general: { - logging: { - enabled: true, - logLevel: "debug" - } - }, - movement: { - speed: 0.1, - mode: "staticspeed" - }, - time: { - starting: 0, - warmingup: 0, - stopping: 0, - coolingdown: 0 - } -}; - -const gate1 = new linearActuator(linActConfig,stateConfig); -const gate2 = new linearActuator(linActConfig,stateConfig); - -ggc.childRegistrationUtils.registerChild(gate1,"upstream"); -ggc.childRegistrationUtils.registerChild(gate2,"downstream"); - - -//open completely 2 gates inside an async IIFE -(async () => { - await ggc.actuators[0].handleInput("parent","execSequence","startup"); - await ggc.actuators[1].handleInput("parent","execSequence","startup"); - - ggc.handleInput("parent","execSequence","open2gates"); - await ggc.sleep(5000); - ggc.handleInput("parent","execSequence","stop2gates"); - -})(); -//*/ diff --git a/dependencies/ggc/ggcConfig.json b/dependencies/ggc/ggcConfig.json deleted file mode 100644 index 430fd4e..0000000 --- a/dependencies/ggc/ggcConfig.json +++ /dev/null @@ -1,297 +0,0 @@ -{ - "general": { - "name": { - "default": "gate group control Machine", - "rules": { - "type": "string", - "description": "A human-readable name or label for this machine configuration." - } - }, - "id": { - "default": null, - "rules": { - "type": "string", - "nullable": true, - "description": "A unique identifier for this configuration. If not provided, defaults to null." - } - }, - "unit": { - "default": "m3/h", - "rules": { - "type": "string", - "description": "The default measurement unit for this configuration (e.g., 'meters', 'seconds', 'unitless')." - } - }, - "logging": { - "logLevel": { - "default": "info", - "rules": { - "type": "enum", - "values": [ - { - "value": "debug", - "description": "Log messages are printed for debugging purposes." - }, - { - "value": "info", - "description": "Informational messages are printed." - }, - { - "value": "warn", - "description": "Warning messages are printed." - }, - { - "value": "error", - "description": "Error messages are printed." - } - ] - } - }, - "enabled": { - "default": true, - "rules": { - "type": "boolean", - "description": "Indicates whether logging is active. If true, log messages will be generated." - } - } - } - }, - "functionality": { - "softwareType": { - "default": "gateGroupControl", - "rules": { - "type": "string", - "description": "Specified software type for this configuration." - } - }, - "role": { - "default": "gate controller", - "rules": { - "type": "string", - "description": "Indicates the role this configuration plays within the system." - } - } - }, - "asset": { - "uuid": { - "default": null, - "rules": { - "type": "string", - "nullable": true, - "description": "A universally unique identifier for this asset. May be null if not assigned." - } - }, - "geoLocation": { - "default": {}, - "rules": { - "type": "object", - "description": "An object representing the asset's physical coordinates or location.", - "schema": { - "x": { - "default": 0, - "rules": { - "type": "number", - "description": "X coordinate of the asset's location." - } - }, - "y": { - "default": 0, - "rules": { - "type": "number", - "description": "Y coordinate of the asset's location." - } - }, - "z": { - "default": 0, - "rules": { - "type": "number", - "description": "Z coordinate of the asset's location." - } - } - } - } - } - }, - "mode": { - "current": { - "default": "auto", - "rules": { - "type": "enum", - "values": [ - { - "value": "auto", - "description": "Machine accepts setpoints from a parent controller and runs autonomously." - }, - { - "value": "virtualControl", - "description": "Controlled via GUI setpoints; ignores parent commands." - }, - { - "value": "fysicalControl", - "description": "Controlled via physical buttons or switches; ignores external automated commands." - }, - { - "value": "maintenance", - "description": "No active control from auto, virtual, or fysical sources." - } - ], - "description": "The operational mode of the machine." - } - }, - "allowedActions":{ - "default":{}, - "rules": { - "type": "object", - "schema":{ - "auto": { - "default": ["statusCheck", "execMovement", "execSequence", "emergencyStop"], - "rules": { - "type": "set", - "itemType": "string", - "description": "Actions allowed in auto mode." - } - }, - "virtualControl": { - "default": ["statusCheck", "execMovement", "execSequence", "emergencyStop"], - "rules": { - "type": "set", - "itemType": "string", - "description": "Actions allowed in virtualControl mode." - } - }, - "fysicalControl": { - "default": ["statusCheck", "emergencyStop"], - "rules": { - "type": "set", - "itemType": "string", - "description": "Actions allowed in fysicalControl mode." - } - }, - "maintenance": { - "default": ["statusCheck"], - "rules": { - "type": "set", - "itemType": "string", - "description": "Actions allowed in maintenance mode." - } - } - }, - "description": "Information about valid command sources recognized by the machine." - } - }, - "allowedSources":{ - "default": {}, - "rules": { - "type": "object", - "schema":{ - "auto": { - "default": ["parent", "GUI", "fysical"], - "rules": { - "type": "set", - "itemType": "string", - "description": "Sources allowed in auto mode." - } - }, - "virtualControl": { - "default": ["GUI", "fysical"], - "rules": { - "type": "set", - "itemType": "string", - "description": "Sources allowed in virtualControl mode." - } - }, - "fysicalControl": { - "default": ["fysical"], - "rules": { - "type": "set", - "itemType": "string", - "description": "Sources allowed in fysicalControl mode." - } - } - }, - "description": "Information about valid command sources recognized by the machine." - } - } - }, - "sequences":{ - "default":{}, - "rules": { - "type": "object", - "schema": { - "open2gates": { - "default": ["openGate1","openGate2"], - "rules": { - "type": "set", - "itemType": "string", - "description": "Sequence of states for starting up the machine." - } - }, - "open1gate": { - "default": ["openGate1"], - "rules": { - "type": "set", - "itemType": "string", - "description": "Sequence of states for shutting down the machine." - } - }, - "stop2gates": { - "default": ["stopGate1","stopGate2"], - "rules": { - "type": "set", - "itemType": "string", - "description": "Sequence of states for stopping the machine." - } - }, - "close2gates": { - "default": ["closeGate2","closeGate1"], - "rules": { - "type": "set", - "itemType": "string", - "description": "Sequence of states for closing the gates." - } - } - } - }, - "description": "Predefined sequences of states for the machine." - - }, - "settings": { - "moveDelay": { - "default": 3, - "rules": { - "type": "number", - "description": "delay between opening first and second linear actuator in seconds" - } - }, - "autoClose": { - "default": 30, - "rules": { - "type": "number", - "description": "When auto close is enabled, the gate will close automatically after this time in seconds" - } - }, - "retryDelay": { - "default": 5, - "rules": { - "type": "number", - "description": "Delay in seconds before retrying a failed command." - } - }, - "maxRetries": { - "default": 5, - "rules": { - "type": "number", - "description": "Maximum number of retries for a failed command." - } - }, - "groundLoopOpen": { - "default": 10, - "rules": { - "type": "number", - "description": "Time before ground loop triggers opening of the gate in seconds" - } - } - - } - } - \ No newline at end of file diff --git a/ggc.html b/ggc.html deleted file mode 100644 index 3a7a864..0000000 --- a/ggc.html +++ /dev/null @@ -1,284 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/ggc.js b/ggc.js deleted file mode 100644 index c2ad8fd..0000000 --- a/ggc.js +++ /dev/null @@ -1,223 +0,0 @@ -module.exports = function (RED) { - function ggc(config) { - RED.nodes.createNode(this, config); - var node = this; - - try { - // Load Machine class and curve data - const Ggc = require("./dependencies/ggc/ggc"); - const OutputUtils = require("../generalFunctions/helper/outputUtils"); - - const ggcConfig = { - general: { - name: config.name || "Unknown", - id: node.id, - logging: { - enabled: config.eneableLog, - logLevel: config.logLevel - } - }, - asset: { - supplier: config.supplier || "Unknown", - type: config.machineType || "generic", - subType: config.subType || "generic", - model: config.model || "generic", - } - }; - - const stateConfig = { - general: { - logging: { - enabled: config.eneableLog, - logLevel: config.logLevel - } - }, - movement: { - speed: Number(config.speed) - }, - time: { - starting: Number(config.startup), - warmingup: Number(config.warmup), - stopping: Number(config.shutdown), - coolingdown: Number(config.cooldown) - } - }; - - // Create machine instance - const ggc = new Ggc(ggcConfig, stateConfig); - - // put m on node memory as source - node.source = ggc; - - //load output utils - const output = new OutputUtils(); - - function updateNodeStatus() { - try { - const mode = "auto";//ggc.currentMode; - const state = ggc.state; - const totPower = Math.round(ggc.measurements.type("power").variant("measured").position('wire').getCurrentValue()) || 0; - - const SYMBOL_MAP = { - gateGroupClosed: "G1🔴 & G2🔴", - gateGroupOpened: "G1🟢 & G2🟢", - gateGroupStopped: "G1🟥 & G2🟥", - gateGroupAccelerating: "G1🟡 & G2🟡", - gateGroupDecelerating: "G1🟠 & G2🟠", - gateOneAccelerating: "G1🟡 & G2🟢", - gateTwoAccelerating: "G1🟢 & G2🟡", - gateOneDecelerating: "G1🟠 & G2🟢", - gateTwoDecelerating: "G1🟢 & G2🟠", - oneGateOpenOneGateClosed: "G1🟢 & G2🔴", - gatePushingStop: "G1⚡ & G2⚡", - unknown: "❓ & ❓", - }; - - symbolState = SYMBOL_MAP[state] || "Unknown"; - - const position = "" ; //ggc.getGatePositions(); - const roundedPosition = Math.round(position * 100) / 100; - - let status; - switch (state) { - // —— gateGroup states first —— - case "gateGroupClosed": - status = { - fill: "red", - shape: "dot", - text: `${mode}: ${symbolState}` - }; - break; - case "gateGroupOpened": - status = { - fill: "green", - shape: "dot", - text: `${mode}: ${symbolState}` - }; - break; - case "gateGroupStopped": - status = { - fill: "red", - shape: "dot", - text: `${mode}: ${symbolState}` - }; - break; - case "oneGateOpenOneGateClosed": - status = { - fill: "green", - shape: "dot", - text: `${mode}: ${symbolState}` - }; - break; - default: - status = { fill: "grey", shape: "dot", text: `${mode}: ${symbolState}` }; - } - return status; - } catch (error) { - node.error("Error in updateNodeStatus: " + error.message); - return { fill: "red", shape: "ring", text: "Status Error" }; - } - } - - function tick() { - try { - const status = updateNodeStatus(); - node.status(status); - - //get output - const classOutput = ggc.getOutput(); - const dbOutput = output.formatMsg(classOutput, ggc.config, "influxdb"); - const pOutput = output.formatMsg(classOutput, ggc.config, "process"); - - //console.log(pOutput); - - //only send output on values that changed - let msgs = []; - msgs[0] = pOutput; - msgs[1] = dbOutput; - - node.send(msgs); - - } catch (error) { - node.error("Error in tick function: " + error); - node.status({ fill: "red", shape: "ring", text: "Tick Error" }); - } - } - - // register child on first output this timeout is needed because of node - red stuff - setTimeout( - () => { - - /*---execute code on first start----*/ - let msgs = []; - - msgs[2] = { topic : "registerChild" , payload: node.id, positionVsParent: "upstream" }; - msgs[3] = { topic : "registerChild" , payload: node.id, positionVsParent: "downstream" }; - - //send msg - this.send(msgs); - }, - 100 - ); - - //declare refresh interval internal node - - setTimeout( - () => { - //---execute code on first start---- - this.interval_id = setInterval(function(){ tick() },1000) - }, - 1000 - ); - - node.on("input", function(msg, send, done) { - try { - - /* Update to complete event based node by putting the tick function after an input event */ - switch(msg.topic) { - case 'registerChild': - const childId = msg.payload; - const childObj = RED.nodes.getNode(childId); - ggc.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent); - break; - case 'setMode': - ggc.setMode(msg.payload); - break; - case 'execSequence': - const { source, action, parameter } = msg.payload; - ggc.handleInput(source, action, parameter); - break; - case 'emergencystop': - const { source: esSource, action: esAction } = msg.payload; - ggc.handleInput(esSource, esAction); - break; - case 'safetySensor': - if(typeof msg.payload === "boolean") { - const safetySensor = msg.payload; - ggc.safetySensor = safetySensor; - } - - break; - } - - if (done) done(); - } catch (error) { - node.error("Error processing input: " + error.message); - if (done) done(error); - } - }); - - node.on('close', function(done) { - if (node.interval_id) clearTimeout(node.interval_id); - if (node.tick_interval) clearInterval(node.tick_interval); - if (done) done(); - }); - - } catch (error) { - node.error("Fatal error in node initialization: " + error.stack); - node.status({fill: "red", shape: "ring", text: "Fatal Error"}); - } - } - - RED.nodes.registerType("ggc", ggc); -}; \ No newline at end of file diff --git a/mgc.js b/mgc.js index a553f2c..b260289 100644 --- a/mgc.js +++ b/mgc.js @@ -149,7 +149,6 @@ module.exports = function (RED) { }; try{ - await mg.handleInput(source,Qd); msg.topic = mg.config.general.name; msg.payload = "done";