AI_UIAutomation/scripts/relay.ts

110 lines
4.4 KiB
TypeScript

/**
* 继电器命令行调试工具 —— 参数化触发按键/配对/原始命令,方便接线调试。
*
* 多板:--board N 指定 MODBUS 板号(默认 1)。pair/press 不传时按 config 的 board 字段自动路由。
*
* 用法(端口/波特率走 .env 的 SERIAL_PORT/SERIAL_BAUD):
* npx ts-node scripts/relay.ts probe --board 1 # 在线探测指定板(读线圈)
* npx ts-node scripts/relay.ts status --board 2 # 读 64 路线圈状态
* npx ts-node scripts/relay.ts pair remote # 进配对(板号按 config)
* npx ts-node scripts/relay.ts pair meter --hold 2500 # 进配对,覆盖时长
* npx ts-node scripts/relay.ts press remote convex --hold 800 # 点按命名键
* npx ts-node scripts/relay.ts pulse 34,35 --hold 3000 --board 1 # 同时脉冲指定通道
* npx ts-node scripts/relay.ts on 34 --board 2 / off 34 # 单路通/断
* npx ts-node scripts/relay.ts raw 010500050000 # 发原始 16 进制帧(自带 CRC 时勿重复)
*/
import * as dotenv from 'dotenv';
import * as path from 'path';
dotenv.config({ path: path.resolve(__dirname, '../.env') });
import { SerialController } from '../utils/common/serial_controller';
import { triggerRelayAction, pressButton } from '../utils/common/relay.helper';
function flag(name: string): string | undefined {
const i = process.argv.indexOf(`--${name}`);
return i >= 0 ? process.argv[i + 1] : undefined;
}
/** 取位置参数(剔除 --flag 及其紧随的值)。 */
function positionals(): string[] {
const argv = process.argv.slice(2);
const out: string[] = [];
for (let i = 0; i < argv.length; i++) {
if (argv[i].startsWith('--')) {
i++; // 跳过该 flag 的值
continue;
}
out.push(argv[i]);
}
return out;
}
async function main() {
const [cmd, a, b] = positionals();
const hold = flag('hold') ? parseInt(flag('hold') as string, 10) : undefined;
const board = flag('board') ? parseInt(flag('board') as string, 10) : undefined;
switch (cmd) {
case 'probe': {
const c = new SerialController(board ? { deviceId: board } : {});
await c.open();
console.log(await c.ping(board));
await c.close();
break;
}
case 'status': {
const c = new SerialController(board ? { deviceId: board } : {});
await c.open();
console.log('status:', (await c.readStatus(board)).toString('hex').toUpperCase());
await c.close();
break;
}
case 'pair': {
if (!a) throw new Error('用法: pair <device> [--hold ms] [--board N]');
await triggerRelayAction(a, 'pairing', { ...(hold ? { holdMs: hold } : {}), ...(board ? { deviceId: board } : {}) });
break;
}
case 'press': {
if (!a || !b) throw new Error('用法: press <device> <button> [--hold ms] [--board N]');
await pressButton(a, b, hold ?? 500, board ? { deviceId: board } : {});
break;
}
case 'pulse': {
if (!a) throw new Error('用法: pulse <ch1,ch2,...> [--hold ms] [--board N]');
const chs = a.split(',').map((s) => parseInt(s, 10));
const c = new SerialController(board ? { deviceId: board } : {});
await c.open();
await c.pulseChannels(chs, hold ?? 500, board);
console.log(`脉冲 板${board ?? c.defaultDeviceId} 通道[${chs.join(',')}] ${hold ?? 500}ms 完成`);
await c.close();
break;
}
case 'on':
case 'off': {
if (!a) throw new Error(`用法: ${cmd} <ch> [--board N]`);
const c = new SerialController(board ? { deviceId: board } : {});
await c.open();
const resp = cmd === 'on' ? await c.relayOn(parseInt(a, 10), board) : await c.relayOff(parseInt(a, 10), board);
console.log(`${cmd} ${a} (板${board ?? c.defaultDeviceId}):`, resp.toString('hex').toUpperCase());
await c.close();
break;
}
case 'raw': {
if (!a) throw new Error('用法: raw <hex,如 010500050000>');
const bytes = (a.match(/.{1,2}/g) || []).map((h) => parseInt(h, 16));
const c = new SerialController(board ? { deviceId: board } : {});
await c.open();
console.log('resp:', (await c.send(bytes)).toString('hex').toUpperCase());
await c.close();
break;
}
default:
console.log('命令: probe | status | pair <dev> | press <dev> <btn> | pulse <chs> | on <ch> | off <ch> | raw <hex> [--hold ms] [--board N]');
}
}
main().catch((e) => {
console.error('错误:', e.message);
process.exit(1);
});