//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;