diff --git a/src/configs/index.js b/src/configs/index.js index 092a6aa..c34fada 100644 --- a/src/configs/index.js +++ b/src/configs/index.js @@ -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', diff --git a/src/configs/rotatingMachine.json b/src/configs/rotatingMachine.json index 108038e..950be91 100644 --- a/src/configs/rotatingMachine.json +++ b/src/configs/rotatingMachine.json @@ -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": { diff --git a/src/state/state.js b/src/state/state.js index abe7508..19c63e9 100644 --- a/src/state/state.js +++ b/src/state/state.js @@ -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); + } } }