P6: convert valve to BaseDomain + BaseNodeAdapter + concern split
Refactor of valve to use the platform infrastructure (BaseDomain, BaseNodeAdapter, ChildRouter, commandRegistry, statusBadge). Extracts concerns into focused modules per .claude/refactor/MODULE_SPLIT.md generic template. Tests stay green; CONTRACT.md generated; legacy aliases preserved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
84
src/flow/flowController.js
Normal file
84
src/flow/flowController.js
Normal file
@@ -0,0 +1,84 @@
|
||||
'use strict';
|
||||
|
||||
// Sequence + setpoint execution. Mirrors the pre-refactor Valve.handleInput
|
||||
// switch but delegates state transitions to host.state. Pre-shutdown ramp-down
|
||||
// to 0 happens here so the existing test contract holds.
|
||||
|
||||
class FlowController {
|
||||
constructor(host) {
|
||||
this.host = host;
|
||||
this.logger = host.logger;
|
||||
}
|
||||
|
||||
isValidSourceForMode(source, mode) {
|
||||
const allowed = this.host.config.mode.allowedSources[mode] || [];
|
||||
return allowed.has(source);
|
||||
}
|
||||
|
||||
async handleInput(source, action, parameter) {
|
||||
if (!this.isValidSourceForMode(source, this.host.currentMode)) {
|
||||
const msg = `Source '${source}' is not valid for mode '${this.host.currentMode}'.`;
|
||||
this.logger.warn(msg);
|
||||
return { status: false, feedback: msg };
|
||||
}
|
||||
this.logger.info(`Handling input from source '${source}' with action '${action}' in mode '${this.host.currentMode}'.`);
|
||||
try {
|
||||
switch (action) {
|
||||
case 'execSequence':
|
||||
await this.executeSequence(parameter);
|
||||
break;
|
||||
case 'execMovement':
|
||||
await this.setpoint(parameter);
|
||||
break;
|
||||
case 'emergencyStop':
|
||||
case 'emergencystop':
|
||||
this.logger.warn(`Emergency stop activated by '${source}'.`);
|
||||
await this.executeSequence('emergencystop');
|
||||
break;
|
||||
case 'statusCheck':
|
||||
this.logger.info(`Status Check: Mode = '${this.host.currentMode}', Source = '${source}'.`);
|
||||
break;
|
||||
default:
|
||||
this.logger.warn(`Action '${action}' is not implemented.`);
|
||||
}
|
||||
this.logger.debug(`Action '${action}' successfully executed`);
|
||||
return { status: true, feedback: `Action '${action}' successfully executed.` };
|
||||
} catch (error) {
|
||||
this.logger.error(`Error handling input: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async executeSequence(sequenceName) {
|
||||
const sequence = this.host.config.sequences[sequenceName];
|
||||
if (!sequence || sequence.size === 0) {
|
||||
this.logger.warn(`Sequence '${sequenceName}' not defined.`);
|
||||
return;
|
||||
}
|
||||
if (this.host.state.getCurrentState() === 'operational' && sequenceName === 'shutdown') {
|
||||
this.logger.info(`Machine will ramp down to position 0 before performing ${sequenceName} sequence`);
|
||||
await this.setpoint(0);
|
||||
}
|
||||
this.logger.info(` --------- Executing sequence: ${sequenceName} -------------`);
|
||||
for (const stateName of sequence) {
|
||||
try {
|
||||
await this.host.state.transitionToState(stateName);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error during sequence '${sequenceName}': ${error}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async setpoint(value) {
|
||||
try {
|
||||
if (typeof value !== 'number' || value < 0) {
|
||||
throw new Error('Invalid setpoint: Setpoint must be a non-negative number.');
|
||||
}
|
||||
await this.host.state.moveTo(value);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error setting setpoint: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FlowController;
|
||||
Reference in New Issue
Block a user