forked from RnD/machineGroupControl
update
This commit is contained in:
526
dependencies/ggc/ggc.js
vendored
Normal file
526
dependencies/ggc/ggc.js
vendored
Normal file
@@ -0,0 +1,526 @@
|
||||
/**
|
||||
* @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");
|
||||
|
||||
})();
|
||||
//*/
|
||||
Reference in New Issue
Block a user