155 lines
4.9 KiB
JavaScript
155 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();
|
|
}
|
|
|
|
getMaintenanceTimeHours(){
|
|
return this.stateManager.getMaintenanceTimeHours();
|
|
}
|
|
|
|
|
|
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;
|
|
|