Files
generalFunctions/src/state/state.js
znetsixe 44033da15d Added logging data on menu and distance
Added helper functionality to abort movements in state class and safeguards to NOT be able to abort in protected states.
some caps removal
2025-10-02 17:29:31 +02:00

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;