Added helper functionality to abort movements in state class and safeguards to NOT be able to abort in protected states. some caps removal
151 lines
4.9 KiB
JavaScript
151 lines
4.9 KiB
JavaScript
//load local dependencies
|
|
const EventEmitter = require('events');
|
|
const StateManager = require('./stateManager');
|
|
const MovementManager = require('./movementManager');
|
|
|
|
//load all config modules
|
|
const defaultConfig = require('./stateConfig.json');
|
|
const ConfigUtils = require('../helper/configUtils');
|
|
|
|
class state{
|
|
constructor(config = {}, logger) {
|
|
|
|
this.emitter = new EventEmitter(); // Own EventEmitter
|
|
this.configUtils = new ConfigUtils(defaultConfig);
|
|
this.config = this.configUtils.initConfig(config);
|
|
this.abortController = null; // new abort controller for aborting async tasks
|
|
// Init after config is set
|
|
this.logger = logger;
|
|
|
|
// Initialize StateManager for state handling
|
|
this.stateManager = new StateManager(this.config,this.logger);
|
|
this.movementManager = new MovementManager(this.config, this.logger, this.emitter);
|
|
|
|
this.delayedMove = null;
|
|
this.mode = this.config.mode.current;
|
|
|
|
// Log initialization
|
|
this.logger.info("State class initialized.");
|
|
|
|
}
|
|
|
|
// -------- Delegate State Management -------- //
|
|
|
|
getMoveTimeLeft() {
|
|
return this.movementManager.timeleft;
|
|
}
|
|
|
|
getCurrentState() {
|
|
return this.stateManager.currentState;
|
|
}
|
|
|
|
getStateDescription() {
|
|
return this.stateManager.getStateDescription();
|
|
}
|
|
|
|
// -------- Movement Methods -------- //
|
|
getCurrentPosition() {
|
|
return this.movementManager.getCurrentPosition();
|
|
}
|
|
|
|
getRunTimeHours() {
|
|
return this.stateManager.getRunTimeHours();
|
|
}
|
|
|
|
|
|
async moveTo(targetPosition) {
|
|
|
|
// Check for invalid conditions and throw errors
|
|
if (targetPosition === this.getCurrentPosition()) {
|
|
this.logger.warn(`Target position=${targetPosition} is the same as the current position ${this.getCurrentPosition()}. Not executing move.`);
|
|
return;
|
|
}
|
|
|
|
if (this.stateManager.getCurrentState() !== "operational") {
|
|
if (this.config.mode.current === "auto") {
|
|
this.delayedMove = targetPosition;
|
|
this.logger.warn(`Saving setpoint=${targetPosition} to execute once back in 'operational' state.`);
|
|
}
|
|
else{
|
|
this.logger.warn(`Not able to accept setpoint=${targetPosition} while not in ${this.stateManager.getCurrentState()} state`);
|
|
}
|
|
//return early
|
|
return;
|
|
}
|
|
this.abortController = new AbortController();
|
|
const { signal } = this.abortController;
|
|
try {
|
|
const newState = targetPosition < this.getCurrentPosition() ? "decelerating" : "accelerating";
|
|
await this.transitionToState(newState,signal); // awaits transition
|
|
await this.movementManager.moveTo(targetPosition,signal); // awaits moving
|
|
this.emitter.emit("movementComplete", { position: targetPosition });
|
|
await this.transitionToState("operational");
|
|
} catch (error) {
|
|
this.logger.error(error);
|
|
}
|
|
}
|
|
|
|
// -------- State Transition Methods -------- //
|
|
|
|
abortCurrentMovement(reason = "group override") {
|
|
if (this.abortController && !this.abortController.signal.aborted) {
|
|
this.logger.warn(`Aborting movement: ${reason}`);
|
|
this.abortController.abort();
|
|
}
|
|
}
|
|
|
|
async transitionToState(targetState, signal) {
|
|
|
|
const fromState = this.getCurrentState();
|
|
const position = this.getCurrentPosition();
|
|
|
|
// Define states that cannot be aborted for safety reasons
|
|
const protectedStates = ['warmingup', 'coolingdown'];
|
|
const isProtectedTransition = protectedStates.includes(fromState);
|
|
|
|
try {
|
|
|
|
this.logger.debug(`Starting transition from ${fromState} to ${targetState}.`);
|
|
if( isProtectedTransition){
|
|
//overrule signal to prevent abortion
|
|
signal = null; // Disable abortion for protected states
|
|
//spit warning
|
|
this.logger.warn(`Transition from ${fromState} to ${targetState} is protected and cannot be aborted.`);
|
|
}
|
|
|
|
// Await the state transition and pass signal for abortion
|
|
const feedback = await this.stateManager.transitionTo(targetState,signal);
|
|
this.logger.info(`Statemanager: ${feedback}`);
|
|
|
|
/* -- Auto pick setpoints in auto mode when operational--*/
|
|
if (
|
|
targetState === "operational" &&
|
|
this.config.mode.current === "auto" &&
|
|
this.delayedMove !== position &&
|
|
this.delayedMove
|
|
) {
|
|
this.logger.info(`Automatically picking up on last requested setpoint ${this.delayedMove}`);
|
|
//trigger move
|
|
await this.moveTo(this.delayedMove,signal);
|
|
this.delayedMove = null;
|
|
this.logger.info(`moveTo : ${feedback} `);
|
|
}
|
|
|
|
this.logger.info(`State change to ${targetState} completed.`);
|
|
this.emitter.emit('stateChange', targetState); // <-- Implement Here
|
|
} catch (error) {
|
|
if (
|
|
error.message === "Transition aborted" ||
|
|
error.message === "Movement aborted"
|
|
) {
|
|
throw error;
|
|
}
|
|
this.logger.error(error);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = state;
|
|
|