fix: conditional abort recovery — don't auto-transition on routine aborts
The unconditional transition to 'operational' after every movement abort caused a bounce loop when MGC called abortActiveMovements on each demand tick: abort→operational→new-flowmovement→abort→operational→... endlessly. Pumps never reached their setpoint. Fix: abortCurrentMovement now takes an options.returnToOperational flag (default false). Routine MGC aborts leave the pump in accelerating/ decelerating — the pump continues its residual movement and reaches operational naturally. Shutdown/emergency-stop paths pass returnToOperational:true so the FSM unblocks for the stopping transition. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -85,17 +85,24 @@ class state{
|
||||
this.emitter.emit("movementComplete", { position: targetPosition });
|
||||
await this.transitionToState("operational");
|
||||
} catch (error) {
|
||||
// Abort path: return to 'operational' so a subsequent shutdown/emergency
|
||||
// sequence can proceed. Without this, the FSM remains stuck in
|
||||
// accelerating/decelerating and blocks stopping/idle transitions.
|
||||
// Abort path: only return to 'operational' when explicitly requested
|
||||
// (shutdown/emergency-stop needs it to unblock the FSM). Routine MGC
|
||||
// demand-update aborts must NOT auto-transition — doing so causes a
|
||||
// bounce loop where every tick aborts → operational → new move →
|
||||
// abort → operational → ... and the pump never reaches its setpoint.
|
||||
const msg = typeof error === 'string' ? error : error?.message;
|
||||
if (msg === 'Transition aborted' || msg === 'Movement aborted') {
|
||||
this.logger.debug(`Movement aborted; returning to 'operational' to unblock further transitions.`);
|
||||
try {
|
||||
await this.transitionToState("operational");
|
||||
} catch (e) {
|
||||
this.logger.debug(`Post-abort transition to operational failed: ${e?.message || e}`);
|
||||
if (this._returnToOperationalOnAbort) {
|
||||
this.logger.debug(`Movement aborted; returning to 'operational' (requested by caller).`);
|
||||
try {
|
||||
await this.transitionToState("operational");
|
||||
} catch (e) {
|
||||
this.logger.debug(`Post-abort transition to operational failed: ${e?.message || e}`);
|
||||
}
|
||||
} else {
|
||||
this.logger.debug(`Movement aborted; staying in current state (routine abort).`);
|
||||
}
|
||||
this._returnToOperationalOnAbort = false;
|
||||
this.emitter.emit("movementAborted", { position: targetPosition });
|
||||
} else {
|
||||
this.logger.error(error);
|
||||
@@ -105,9 +112,19 @@ class state{
|
||||
|
||||
// -------- State Transition Methods -------- //
|
||||
|
||||
abortCurrentMovement(reason = "group override") {
|
||||
/**
|
||||
* @param {string} reason - human-readable abort reason
|
||||
* @param {object} [options]
|
||||
* @param {boolean} [options.returnToOperational=false] - when true the FSM
|
||||
* transitions back to 'operational' after the abort so a subsequent
|
||||
* shutdown/emergency-stop sequence can proceed. Set to false (default)
|
||||
* for routine demand updates where the caller will send a new movement
|
||||
* immediately — auto-transitioning would cause a bounce loop.
|
||||
*/
|
||||
abortCurrentMovement(reason = "group override", options = {}) {
|
||||
if (this.abortController && !this.abortController.signal.aborted) {
|
||||
this.logger.warn(`Aborting movement: ${reason}`);
|
||||
this._returnToOperationalOnAbort = Boolean(options.returnToOperational);
|
||||
this.abortController.abort();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user