fix: make movement abort unblock subsequent FSM transitions + add rotatingMachine schema keys
state.js: When moveTo catches a 'Movement aborted' or 'Transition aborted'
error, transition the FSM back to 'operational'. This ensures a subsequent
shutdown or emergency-stop sequence is accepted — previously the FSM stayed
stuck in 'accelerating'/'decelerating' and rejected stopping/idle
transitions, silently dropping shutdown commands issued mid-ramp. Also
emits a 'movementAborted' event for observability.
rotatingMachine.json: Add schema entries for functionality.distance,
functionality.distanceUnit, functionality.distanceDescription, and top-level
output.{process,dbase}. These keys are produced by buildConfig / the HTML
editor but were previously stripped by the validator with an
'Unknown key' warning on every deploy.
configs/index.js: Trim buildConfig so it no longer unconditionally injects
distanceUnit/distanceDescription — those keys are rotatingMachine-specific
and would otherwise produce Unknown-key warnings on every other node.
Verified via Docker-hosted Node-RED E2E: shutdown from accelerating now
reaches idle; emergency stop from accelerating reaches off; 0 Unknown-key
warnings in container logs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -109,7 +109,7 @@ class ConfigManager {
|
||||
functionality: {
|
||||
softwareType: nodeName.toLowerCase(),
|
||||
positionVsParent: uiConfig.positionVsParent || 'atEquipment',
|
||||
distance: uiConfig.hasDistance ? uiConfig.distance : undefined
|
||||
distance: uiConfig.hasDistance ? uiConfig.distance : null
|
||||
},
|
||||
output: {
|
||||
process: uiConfig.processOutputFormat || 'process',
|
||||
|
||||
@@ -91,7 +91,55 @@
|
||||
],
|
||||
"description": "Defines the position of the measurement relative to its parent equipment or system."
|
||||
}
|
||||
},
|
||||
"distance": {
|
||||
"default": null,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "Optional spatial offset from the parent equipment reference. Populated from the editor when hasDistance is enabled; null otherwise."
|
||||
}
|
||||
},
|
||||
"distanceUnit": {
|
||||
"default": "m",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "Unit for the functionality.distance offset (e.g. 'm', 'cm')."
|
||||
}
|
||||
},
|
||||
"distanceDescription": {
|
||||
"default": "",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "Free-text description of what the distance offset represents (e.g. 'cable length from control panel to motor')."
|
||||
}
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"process": {
|
||||
"default": "process",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{ "value": "process", "description": "Delta-compressed process message (default)." },
|
||||
{ "value": "json", "description": "Raw JSON payload." },
|
||||
{ "value": "csv", "description": "CSV-formatted payload." }
|
||||
],
|
||||
"description": "Format of the process payload emitted on output port 0."
|
||||
}
|
||||
},
|
||||
"dbase": {
|
||||
"default": "influxdb",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{ "value": "influxdb", "description": "InfluxDB line-protocol payload (default)." },
|
||||
{ "value": "json", "description": "Raw JSON payload." },
|
||||
{ "value": "csv", "description": "CSV-formatted payload." }
|
||||
],
|
||||
"description": "Format of the telemetry payload emitted on output port 1."
|
||||
}
|
||||
}
|
||||
},
|
||||
"asset": {
|
||||
"uuid": {
|
||||
|
||||
@@ -85,7 +85,21 @@ class state{
|
||||
this.emitter.emit("movementComplete", { position: targetPosition });
|
||||
await this.transitionToState("operational");
|
||||
} catch (error) {
|
||||
this.logger.error(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.
|
||||
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}`);
|
||||
}
|
||||
this.emitter.emit("movementAborted", { position: targetPosition });
|
||||
} else {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user