0
0
Fork 0

feat(live-values): --live-values-interval <ms> CLI flag

Replaces the hardcoded 500ms poll with a configurable interval.
Clamped to [100, 60000] -- below 100 the read_variable IPC round-
trip-per-var dominates and the pump can't keep up; above 60000 a
session shorter than the interval would never see any update.

ServerConfig.liveValuesIntervalMs?: number plumbs through bin.ts
(parseInt + clamp) into the LiveValuesPump constructor and the
'Live-values pump started (<ms>ms)' log line.

Default unchanged at 500ms, so existing --live-values invocations
behave identically.
This commit is contained in:
Karstein Phobic Nyvold Kvistad 2026-04-29 13:43:09 +02:00
parent b535eb3e3d
commit 8f40b5539a
4 changed files with 65 additions and 5 deletions

View file

@ -19,6 +19,16 @@ try {
// ignore
}
const LIVE_VALUES_INTERVAL_MIN_MS = 100;
const LIVE_VALUES_INTERVAL_MAX_MS = 60_000;
const LIVE_VALUES_INTERVAL_DEFAULT_MS = 500;
function clampInterval(raw: string | undefined): number {
const parsed = parseInt(raw ?? String(LIVE_VALUES_INTERVAL_DEFAULT_MS), 10);
if (!Number.isFinite(parsed)) return LIVE_VALUES_INTERVAL_DEFAULT_MS;
return Math.max(LIVE_VALUES_INTERVAL_MIN_MS, Math.min(parsed, LIVE_VALUES_INTERVAL_MAX_MS));
}
program
.name('codesys-mcp-sp21-plus')
.description('MCP server for CODESYS with persistent UI instance')
@ -48,7 +58,8 @@ program
.option('--keep-alive', 'Keep CODESYS running after server stops', false)
.option('--auto-mirror', 'Re-run mirror_export after every modifying tool so an external editor watching <projectDir>/mcp-mirror/ sees changes live', false)
.option('--approve-edits', 'Gate modifying MCP tools behind a phobiCS-tui y/n diff prompt', false)
.option('--live-values', 'Pump runtime values for the selected POU into tui-live-values.json so phobiCS-tui can overlay them inline (500ms poll). Requires the runtime to be online; failures are silent.', false)
.option('--live-values', 'Pump runtime values for the selected POU into tui-live-values.json so phobiCS-tui can overlay them inline. Requires the runtime to be online; failures are silent.', false)
.option('--live-values-interval <ms>', 'Poll interval for --live-values in ms. Default 500. Clamped to [100, 60000].', '500')
.option('--timeout <ms>', 'Default command timeout in ms', '60000')
.option('--verbose', 'Enable verbose logging')
.option('--debug', 'Enable debug logging (more verbose)')
@ -201,6 +212,7 @@ if (opts.sshVersion) {
autoMirror: opts.autoMirror || false,
approveEdits: opts.approveEdits || false,
liveValues: opts.liveValues || false,
liveValuesIntervalMs: clampInterval(opts.liveValuesInterval),
};
process.stderr.write(`Starting CODESYS MCP Server v${version}\n`);
@ -215,7 +227,7 @@ if (opts.sshVersion) {
process.stderr.write(` Approve edits: ENABLED (modifying tools will prompt via phobiCS-tui)\n`);
}
if (config.liveValues) {
process.stderr.write(` Live values: ENABLED (poll 500ms; writes tui-live-values.json)\n`);
process.stderr.write(` Live values: ENABLED (poll ${config.liveValuesIntervalMs ?? 500}ms; writes tui-live-values.json)\n`);
}
startMcpServer(config).catch((err) => {

View file

@ -3200,7 +3200,7 @@ export async function startMcpServer(config: ServerConfig): Promise<void> {
{
stateFilePath: defaultStateFilePath(),
liveValuesFilePath: defaultLiveValuesFilePath(),
intervalMs: 500,
intervalMs: config.liveValuesIntervalMs ?? 500,
},
{
readSelection,
@ -3225,7 +3225,7 @@ export async function startMcpServer(config: ServerConfig): Promise<void> {
}
);
liveValuesPump.start();
serverLog.info('Live-values pump started (500ms)');
serverLog.info(`Live-values pump started (${config.liveValuesIntervalMs ?? 500}ms)`);
}
// ─── Graceful Shutdown ───────────────────────────────────────────────

View file

@ -77,9 +77,14 @@ export interface ServerConfig extends LauncherConfig {
* If true, run a background pump that reads runtime values for the
* variables of the user's currently-selected POU and writes them to
* tui-live-values.json so the TUI Viewer can overlay them inline.
* Off by default. 500 ms interval (fixed in v0.3).
* Off by default.
*/
liveValues?: boolean;
/**
* Poll interval (ms) for the live-values pump. Default 500.
* Clamped to [100, 60000]; values outside the range are coerced.
*/
liveValuesIntervalMs?: number;
}
/** Script template parameters */

View file

@ -0,0 +1,43 @@
import { describe, it, expect } from 'vitest';
// The clamp helper isn't exported (it's local to bin.ts which is the entry
// point), but its behaviour is the contract this test pins down: the
// production code's clamp must match these expectations or live-values
// poll cadence is wrong.
const MIN = 100;
const MAX = 60_000;
const DEFAULT = 500;
function clampInterval(raw: string | undefined): number {
const parsed = parseInt(raw ?? String(DEFAULT), 10);
if (!Number.isFinite(parsed)) return DEFAULT;
return Math.max(MIN, Math.min(parsed, MAX));
}
describe('clampInterval', () => {
it('returns default when raw is undefined', () => {
expect(clampInterval(undefined)).toBe(DEFAULT);
});
it('returns default when raw is unparseable', () => {
expect(clampInterval('not-a-number')).toBe(DEFAULT);
});
it('clamps below the minimum', () => {
expect(clampInterval('50')).toBe(MIN);
expect(clampInterval('0')).toBe(MIN);
expect(clampInterval('-100')).toBe(MIN);
});
it('clamps above the maximum', () => {
expect(clampInterval('1000000')).toBe(MAX);
});
it('passes through values inside the range', () => {
expect(clampInterval('100')).toBe(100);
expect(clampInterval('500')).toBe(500);
expect(clampInterval('1000')).toBe(1000);
expect(clampInterval('60000')).toBe(60_000);
});
});