forked from RnD/machineGroupControl
updates groupcontrol
This commit is contained in:
526
dependencies/ggc/ggc.js
vendored
526
dependencies/ggc/ggc.js
vendored
@@ -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");
|
|
||||||
|
|
||||||
})();
|
|
||||||
//*/
|
|
||||||
297
dependencies/ggc/ggcConfig.json
vendored
297
dependencies/ggc/ggcConfig.json
vendored
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
284
ggc.html
284
ggc.html
@@ -1,284 +0,0 @@
|
|||||||
<script type="module">
|
|
||||||
|
|
||||||
import * as menuUtils from "/generalFunctions/helper/menuUtils.js";
|
|
||||||
|
|
||||||
RED.nodes.registerType("ggc", {
|
|
||||||
category: "digital twin",
|
|
||||||
color: "#4f8582",
|
|
||||||
|
|
||||||
defaults: {
|
|
||||||
// Define default properties
|
|
||||||
name: { value: "", required: true },
|
|
||||||
enableLog: { value: false },
|
|
||||||
logLevel: { value: "error" },
|
|
||||||
|
|
||||||
// Define specific properties
|
|
||||||
speed: { value: 1, required: true },
|
|
||||||
startup: { value: 0 },
|
|
||||||
warmup: { value: 0 },
|
|
||||||
shutdown: { value: 0 },
|
|
||||||
cooldown: { value: 0 },
|
|
||||||
|
|
||||||
//define general asset properties
|
|
||||||
supplier: { value: "" },
|
|
||||||
subType: { value: "" },
|
|
||||||
model: { value: "" },
|
|
||||||
unit: { value: "" },
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
inputs: 1,
|
|
||||||
outputs: 4,
|
|
||||||
inputLabels: ["Input"],
|
|
||||||
outputLabels: ["process", "dbase", "upstreamParent", "downstreamParent"],
|
|
||||||
icon: "font-awesome/fa-cogs",
|
|
||||||
|
|
||||||
label: function () {
|
|
||||||
return this.name || "GateGroupControl";
|
|
||||||
},
|
|
||||||
|
|
||||||
oneditprepare: function () {
|
|
||||||
const node = this;
|
|
||||||
|
|
||||||
console.log("ggc Node: Edit Prepare");
|
|
||||||
|
|
||||||
const elements = {
|
|
||||||
// Basic fields
|
|
||||||
name: document.getElementById("node-input-name"),
|
|
||||||
// specific fields
|
|
||||||
speed: document.getElementById("node-input-speed"),
|
|
||||||
startup: document.getElementById("node-input-startup"),
|
|
||||||
warmup: document.getElementById("node-input-warmup"),
|
|
||||||
shutdown: document.getElementById("node-input-shutdown"),
|
|
||||||
cooldown: document.getElementById("node-input-cooldown"),
|
|
||||||
// Logging fields
|
|
||||||
logCheckbox: document.getElementById("node-input-enableLog"),
|
|
||||||
logLevelSelect: document.getElementById("node-input-logLevel"),
|
|
||||||
rowLogLevel: document.getElementById("row-logLevel"),
|
|
||||||
// Asset fields
|
|
||||||
supplier: document.getElementById("node-input-supplier"),
|
|
||||||
subType: document.getElementById("node-input-subType"),
|
|
||||||
model: document.getElementById("node-input-model"),
|
|
||||||
unit: document.getElementById("node-input-unit"),
|
|
||||||
};
|
|
||||||
|
|
||||||
const projecSettingstURL = "http://localhost:1880/generalFunctions/settings/projectSettings.json";
|
|
||||||
|
|
||||||
try{
|
|
||||||
|
|
||||||
// Fetch project settings
|
|
||||||
menuUtils.fetchProjectData(projecSettingstURL)
|
|
||||||
.then((projectSettings) => {
|
|
||||||
|
|
||||||
//assign to node vars
|
|
||||||
node.configUrls = projectSettings.configUrls;
|
|
||||||
|
|
||||||
const { cloudConfigURL, localConfigURL } = menuUtils.getSpecificConfigUrl("ggc",node.configUrls.cloud.taggcodeAPI);
|
|
||||||
node.configUrls.cloud.config = cloudConfigURL; // first call
|
|
||||||
node.configUrls.local.config = localConfigURL; // backup call
|
|
||||||
|
|
||||||
node.locationId = projectSettings.locationId;
|
|
||||||
node.uuid = projectSettings.uuid;
|
|
||||||
|
|
||||||
// Gets the ID of the active workspace (Flow)
|
|
||||||
const activeFlowId = RED.workspaces.active(); //fetches active flow id
|
|
||||||
node.processId = activeFlowId;
|
|
||||||
|
|
||||||
// UI elements
|
|
||||||
menuUtils.initBasicToggles(elements);
|
|
||||||
menuUtils.fetchAndPopulateDropdowns(node.configUrls, elements, node); // function for all assets
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
}catch(e){
|
|
||||||
console.log("Error fetching project settings", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(node.d){
|
|
||||||
//this means node is disabled
|
|
||||||
console.log("Current status of node is disabled");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
oneditsave: function () {
|
|
||||||
const node = this;
|
|
||||||
|
|
||||||
console.log(`------------ Saving changes to node ------------`);
|
|
||||||
console.log(`${node.uuid}`);
|
|
||||||
|
|
||||||
//save basic properties
|
|
||||||
["name", "unit", "supplier", "subType", "model"].forEach(
|
|
||||||
(field) => {
|
|
||||||
const element = document.getElementById(`node-input-${field}`);
|
|
||||||
if (element) {
|
|
||||||
node[field] = element.value || "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Save numeric and boolean properties
|
|
||||||
["speed", "startup", "warmup", "shutdown", "cooldown"].forEach(
|
|
||||||
(field) => {
|
|
||||||
const element = document.getElementById(`node-input-${field}`);
|
|
||||||
if (element) {
|
|
||||||
node[field] = Number(element.value) || 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
//local db
|
|
||||||
node[field] = node["modelMetadata"][field];
|
|
||||||
//central db
|
|
||||||
node[field] = node["modelMetadata"]["product_model_meta"][field];
|
|
||||||
*/
|
|
||||||
|
|
||||||
const logLevelElement = document.getElementById("node-input-logLevel");
|
|
||||||
node.logLevel = logLevelElement ? logLevelElement.value || "info" : "info";
|
|
||||||
|
|
||||||
if (!node.unit) {
|
|
||||||
RED.notify("Unit selection is required.", "error");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.subType && !node.unit) {
|
|
||||||
RED.notify("Unit must be set when specifying a subtype.", "error");
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
console.log("Saving assetID and tagnumber");
|
|
||||||
console.log(node.assetTagCode);
|
|
||||||
// Fetch project settings
|
|
||||||
menuUtils.apiCall(node,node.configUrls)
|
|
||||||
.then((response) => {
|
|
||||||
|
|
||||||
console.log(" ====<<>>>> API call response", response);
|
|
||||||
|
|
||||||
//save response to node information
|
|
||||||
node.assetId = response.asset_id;
|
|
||||||
node.assetTagCode = response.asset_tag_number;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log("Error during API call", error);
|
|
||||||
});
|
|
||||||
}catch(e){
|
|
||||||
console.log("Error saving assetID and tagnumber", e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Main UI Template -->
|
|
||||||
<script type="text/html" data-template-name="ggc">
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="node-input-name"
|
|
||||||
placeholder="ggc Name"
|
|
||||||
style="width:70%;"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="node-input-speed"><i class="fa fa-clock-o"></i> Reaction Speed</label>
|
|
||||||
<input type="number" id="node-input-speed" placeholder="1" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="node-input-startup"><i class="fa fa-clock-o"></i> Startup Time</label>
|
|
||||||
<input type="number" id="node-input-startup" placeholder="0" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="node-input-warmup"><i class="fa fa-clock-o"></i> Warmup Time</label>
|
|
||||||
<input type="number" id="node-input-warmup" placeholder="0" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="node-input-shutdown"><i class="fa fa-clock-o"></i> Shutdown Time</label>
|
|
||||||
<input type="number" id="node-input-shutdown" placeholder="0" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="node-input-cooldown"><i class="fa fa-clock-o"></i> Cooldown Time</label>
|
|
||||||
<input type="number" id="node-input-cooldown" placeholder="0" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Optional Extended Fields: supplier, type, subType, model -->
|
|
||||||
<hr />
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="node-input-supplier"
|
|
||||||
><i class="fa fa-industry"></i> Supplier</label
|
|
||||||
>
|
|
||||||
<select id="node-input-supplier" style="width:60%;">
|
|
||||||
<option value="">(optional)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="node-input-subType"
|
|
||||||
><i class="fa fa-puzzle-piece"></i> SubType</label
|
|
||||||
>
|
|
||||||
<select id="node-input-subType" style="width:60%;">
|
|
||||||
<option value="">(optional)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="node-input-model"><i class="fa fa-wrench"></i> Model</label>
|
|
||||||
<select id="node-input-model" style="width:60%;">
|
|
||||||
<option value="">(optional)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="node-input-unit"><i class="fa fa-balance-scale"></i> Unit</label>
|
|
||||||
<select id="node-input-unit" style="width:60%;"></select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<!-- loglevel checkbox -->
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="node-input-enableLog"
|
|
||||||
><i class="fa fa-cog"></i> Enable Log</label
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="node-input-enableLog"
|
|
||||||
style="width:20px; vertical-align:baseline;"
|
|
||||||
/>
|
|
||||||
<span>Enable logging</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row" id="row-logLevel">
|
|
||||||
<label for="node-input-logLevel"><i class="fa fa-cog"></i> Log Level</label>
|
|
||||||
<select id="node-input-logLevel" style="width:60%;">
|
|
||||||
<option value="info">Info</option>
|
|
||||||
<option value="debug">Debug</option>
|
|
||||||
<option value="warn">Warn</option>
|
|
||||||
<option value="error">Error</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/html" data-help-name="ggc">
|
|
||||||
<p>
|
|
||||||
<b>ggc Node</b>: Configure the behavior of a ggc
|
|
||||||
used in a digital twin.
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li><b>Supplier:</b> Select a supplier to populate machine options.</li>
|
|
||||||
<li><b>SubType:</b> Select a subtype if applicable to further categorize the asset.</li>
|
|
||||||
<li><b>Model:</b> Define the specific model for more granular asset configuration.</li>
|
|
||||||
<li><b>Unit:</b> Assign a unit to standardize measurements or operations.</li>
|
|
||||||
<li><b>Speed:</b> Reaction speed of the machine in response to inputs.</li>
|
|
||||||
<li><b>Startup:</b> Define the startup time for the machine.</li>
|
|
||||||
<li><b>Warmup:</b> Define the warmup time for the machine.</li>
|
|
||||||
<li><b>Shutdown:</b> Define the shutdown time for the machine.</li>
|
|
||||||
<li><b>Cooldown:</b> Define the cooldown time for the machine.</li>
|
|
||||||
<li><b>Enable Log:</b> Enable or disable logging for the machine.</li>
|
|
||||||
<li><b>Log Level:</b> Set the log level (Info, Debug, Warn, Error).</li>
|
|
||||||
</ul>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
223
ggc.js
223
ggc.js
@@ -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);
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user