/** * @file stateManager.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 Class for managing state transitions and state descriptions. * @description Class for managing state transitions and state descriptions. * @module stateManager * @exports stateManager * @version 0.1.0 * @since 0.1.0 * * Author: * - Rene De Ren * Email: * - rene@thegoldenbasket.nl */ class stateManager { constructor(config, logger) { this.currentState = config.state.current; this.availableStates = config.state.available; this.descriptions = config.state.descriptions; this.logger = logger; this.transitionTimeleft = 0; this.transitionTimes = config.time; // Define valid transitions (can be extended dynamically if needed) this.validTransitions = config.state.allowedTransitions; // NEW: Initialize runtime tracking this.runTimeHours = 0; // cumulative runtime in hours this.runTimeStart = null; // timestamp when active state began // Define active states (runtime counts only in these states) this.activeStates = config.state.activeStates; } getCurrentState() { return this.currentState; } transitionTo(newState,signal) { return new Promise((resolve, reject) => { if (signal && signal.aborted) { this.logger.debug("Transition aborted."); return reject("Transition aborted."); } if (!this.isValidTransition(newState)) { return reject( `Invalid transition from ${this.currentState} to ${newState}. Transition not executed.` ); //go back early and reject promise } // NEW: Handle runtime tracking based on active states this.handleRuntimeTracking(newState); const transitionDuration = this.transitionTimes[this.currentState] || 0; // Default to 0 if no transition time this.logger.debug( `Transition from ${this.currentState} to ${newState} will take ${transitionDuration}s.` ); if (transitionDuration > 0) { const timeoutId = setTimeout(() => { this.currentState = newState; resolve(`Transition from ${this.currentState} to ${newState} completed in ${transitionDuration}s.`); }, transitionDuration * 1000); if (signal) { signal.addEventListener('abort', () => { clearTimeout(timeoutId); reject(new Error('Transition aborted')); }); } } else { this.currentState = newState; resolve(`Immediate transition to ${this.currentState} completed.`); } }); } handleRuntimeTracking(newState) { // NEW: Handle runtime tracking based on active states const wasActive = this.activeStates.has(this.currentState); const willBeActive = this.activeStates.has(newState); if (wasActive && !willBeActive && this.runTimeStart) { // stop runtime timer and accumulate elapsed time const elapsed = (Date.now() - this.runTimeStart) / 3600000; // hours this.runTimeHours += elapsed; this.runTimeStart = null; this.logger.debug( `Runtime timer stopped; elapsed=${elapsed.toFixed( 3 )}h, total=${this.runTimeHours.toFixed(3)}h.` ); } else if (!wasActive && willBeActive && !this.runTimeStart) { // starting new runtime this.runTimeStart = Date.now(); this.logger.debug("Runtime timer started."); } } isValidTransition(newState) { this.logger.debug( `Check 1 Transition valid ? From ${ this.currentState } To ${newState} => ${this.validTransitions[this.currentState]?.has( newState )} ` ); this.logger.debug( `Check 2 Transition valid ? ${ this.currentState } is not equal to ${newState} => ${this.currentState !== newState}` ); // check if transition is valid and not the same as before const valid = this.validTransitions[this.currentState]?.has(newState) && this.currentState !== newState; //if not valid if (!valid) { return false; } else { return true; } } getStateDescription(state = this.currentState) { return this.descriptions[state] || "No description available."; } // NEW: Getter to retrieve current cumulative runtime (active time) in hours. getRunTimeHours() { // If currently active add the ongoing duration. let currentElapsed = 0; if (this.runTimeStart) { currentElapsed = (Date.now() - this.runTimeStart) / 3600000; } return this.runTimeHours + currentElapsed; } } module.exports = stateManager;