diff --git a/config/relay.config.ts b/config/relay.config.ts index 61d441d..537e084 100644 --- a/config/relay.config.ts +++ b/config/relay.config.ts @@ -25,39 +25,41 @@ export interface RelayAction { export interface RelayDeviceConfig { buttons: Record; // 按键名 → 通道(可按平台) actions?: Record; + /** 该设备接在哪块继电器板(MODBUS 设备 ID/板号)。缺省=1。现有接线全在 1 号板,新增板上的设备标 board: 2。 */ + board?: number; } export const RELAY_MAP: Record = { meter: { - buttons: { main: { android: 32 /*, ios: <填iOS通道> */ } }, + buttons: { main: { android: 32 , ios: 33 } }, actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住主键进入添加模式' }, }, }, remote: { buttons: { - convex: { android: 26 /*, ios: <填> */ }, // 凸键 - concave: { android: 25 /*, ios: <填> */ }, // 凹键 + convex: { android: 26 , ios: 42 }, // 凸键 + concave: { android: 25 , ios: 43 }, // 凹键 }, actions: { pairing: { buttons: ['convex', 'concave'], holdMs: 2000, desc: '同时按凸键+凹键 2s 进入添加模式(切换式:按一次进、再按退,勿重按)' }, }, }, outdoor_meter: { - buttons: { main: { android: 28 /*, ios: <填> */ } }, + buttons: { main: { android: 28 , ios: 38 } }, actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住主键 2s 进入添加模式' }, }, }, motion: { - buttons: { main: { android: 29 /*, ios: <填> */ } }, // Motion Sensor 配对键(电池仓内按键) + buttons: { main: { android: 29 , ios: 34 } }, // Motion Sensor 配对键(电池仓内按键) actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住配对键 2s 进入添加模式' }, }, }, curtain3: { buttons: { - power: { android: 27 /*, ios: <填> */ }, // 开关按钮 + power: { android: 27 , ios: 46}, // 开关按钮 track: { android: 38 /*, ios: <填> */ }, // 控制轨道按钮(控制用,不参与配对) }, actions: { @@ -65,13 +67,13 @@ export const RELAY_MAP: Record = { }, }, curtain: { - buttons: { power: { android: 24 /*, ios: <填> */ } }, // 开关按钮 + buttons: { power: { android: 24 , ios: 63 } }, // 开关按钮 actions: { pairing: { buttons: ['power'], holdMs: 2000, desc: '按住开关按钮 2s 进入添加模式' }, }, }, hub: { - buttons: { main: { android: 23 /*, ios: <填> */ } }, // Hub Mini 主键 + buttons: { main: { android: 23 , ios: 37} }, // Hub Mini 主键 actions: { pairing: { buttons: ['main'], holdMs: 4000, desc: '按住主键 4s 进入添加模式(Hub Mini;指引页写3s,实测需4s)' }, }, @@ -87,8 +89,8 @@ export const RELAY_MAP: Record = { }, hub_plus: { buttons: { - a: { android: 14 /*, ios: <填> */ }, // Hub Plus 配对键1(ch14,与 K20+/S10 共用,物理同时只接一个) - b: { android: 15 /*, ios: <填> */ }, // Hub Plus 配对键2(ch15) + a: { android: 14 , ios: 47 }, // Hub Plus 配对键1(ch14,与 K20+/S10 共用,物理同时只接一个) + b: { android: 15 , ios: 48 }, // Hub Plus 配对键2(ch15) }, actions: { pairing: { buttons: ['a', 'b'], holdMs: 4000, desc: '同时长按 ch14+15 4s 进入添加模式(Hub Plus,WiFi;时长待实测)' }, @@ -102,7 +104,7 @@ export const RELAY_MAP: Record = { }, }, water: { - buttons: { main: { android: 22 /*, ios: <填> */ } }, // Water Detector(水浸)配对键 + buttons: { main: { android: 22 , ios: 40 } }, // Water Detector(水浸)配对键 actions: { // ⚠️ 配对时长待实测;先按 2s 试 pairing: { buttons: ['main'], holdMs: 2000, desc: '按住配对键进入添加模式(水浸,待实测确认时长)' }, @@ -125,20 +127,20 @@ export const RELAY_MAP: Record = { }, }, contact: { - buttons: { main: { android: 47 /*, ios: <填> */ } }, // Contact Sensor 配对键(ch47,2026-06 由 44 改;与 plug_mini_eu 同口,同时只接一个) + buttons: { main: { android: 47 , ios: 45 } }, // Contact Sensor 配对键(ch47,2026-06 由 44 改;与 plug_mini_eu 同口,同时只接一个) actions: { // 实测:长按 6s 进配对(2s/4s 不够,2026-06 确认) pairing: { buttons: ['main'], holdMs: 6000, desc: '长按配对键 6s 进入添加模式(Contact Sensor)' }, }, }, meter_plus: { - buttons: { main: { android: 31 /*, ios: <填> */ } }, // Meter Plus 主键 + buttons: { main: { android: 31 , ios: 44 } }, // Meter Plus 主键 actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住主键 2s 进入添加模式(Meter Plus)' }, }, }, find_card: { - buttons: { main: { android: 30 /*, ios: <填> */ } }, // Wallet Finder Card 配对键 + buttons: { main: { android: 30 , ios: 64 } }, // Wallet Finder Card 配对键 actions: { // ⚠️ 配对时长待实测 pairing: { buttons: ['main'], holdMs: 2000, desc: '按住配对键进入添加模式(Wallet Finder Card,待实测确认时长)' }, @@ -157,25 +159,25 @@ export const RELAY_MAP: Record = { }, }, safety_alarm: { - buttons: { main: { android: 3 /*, ios: <填> */ } }, // Safety Alarm 配对键(ch3,BLE 无WiFi,流程近 Find Card) + buttons: { main: { android: 3 , ios:49 } }, // Safety Alarm 配对键(ch3,BLE 无WiFi,流程近 Find Card) actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住配对键 2s 进入添加模式(Safety Alarm,BLE无WiFi;选品类后 Disclaimers→Next 再 Connect Device)' }, }, }, meter_pro: { - buttons: { main: { android: 4 /*, ios: <填> */ } }, // Meter Pro (CO2) 配对键(ch4,纯BLE,与 meter 同流程、入口品类名不同) + buttons: { main: { android: 4 , ios: 36 } }, // Meter Pro (CO2) 配对键(ch4,纯BLE,与 meter 同流程、入口品类名不同) actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住主键 2s 进入添加模式(Meter Pro CO2)' }, }, }, presence_sensor: { - buttons: { main: { android: 5 /*, ios: <填> */ } }, // Presence Sensor 配对键(ch5,与 Motion Sensor 同品类组/同流程,入口名不同) + buttons: { main: { android: 5 , ios: 35 } }, // Presence Sensor 配对键(ch5,与 Motion Sensor 同品类组/同流程,入口名不同) actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住配对键 2s 进入添加模式(Presence Sensor)' }, }, }, curtain3_2025: { - buttons: { power: { android: 6 /*, ios: <填> */ } }, // Curtain 3 2025 开关键(ch6,入口品类=Curtain;校准流程待实测 curtain/curtain3) + buttons: { power: { android: 6 , ios: 39 } }, // Curtain 3 2025 开关键(ch6,入口品类=Curtain;校准流程待实测 curtain/curtain3) actions: { pairing: { buttons: ['power'], holdMs: 2000, desc: '按住开关键 2s 进入添加模式(Curtain 3 2025)' }, }, @@ -201,7 +203,7 @@ export const RELAY_MAP: Record = { }, }, strip_light: { - buttons: { main: { android: 33 /*, ios: <填> */ } }, // Strip Light(灯带)配对键(ch33,灯类,WiFi 产品) + buttons: { main: { android: 33 , ios: 41 } }, // Strip Light(灯带)配对键(ch33,灯类,WiFi 产品) actions: { // 长按 2s 进配对(灯带,不同于 color bulb 的两次点按)。 pairing: { buttons: ['main'], holdMs: 2000, desc: '长按配对键 2s 进入添加模式(Strip Light 灯带,WiFi)' }, @@ -217,7 +219,7 @@ export const RELAY_MAP: Record = { }, }, outdoor_ptc: { - buttons: { main: { android: 36 /*, ios: <填> */ } }, // Outdoor PTC 配对键(ch36;与 doorbell.talk 同口,物理同时只接一个) + buttons: { main: { android: 36 , ios: 50 } }, // Outdoor PTC 配对键(ch36;与 doorbell.talk 同口,物理同时只接一个) actions: { // ⚠️ 配对时长待实测;先按 2s 试(流程参考水浸 Water Detector:按键进配对 + WiFi 配网) pairing: { buttons: ['main'], holdMs: 2000, desc: '按住配对键 2s 进入添加模式(Outdoor PTC,入口品类 Outdoor Pan/Tilt Cam 3K;待实测确认时长)' }, @@ -327,13 +329,13 @@ export const RELAY_MAP: Record = { }, }, humidifier2: { - buttons: { main: { android: 63 /*, ios: <填> */ } }, // Evaporative Humidifier(口头"加湿器2")配对键(ch63,带 WiFi 模块) + buttons: { main: { android: 63 , ios: 62 } }, // Evaporative Humidifier(口头"加湿器2")配对键(ch63,带 WiFi 模块) actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '长按配对键 2s 进入添加模式(Evaporative Humidifier,WiFi,实测2s可用)' }, }, }, air_purifier: { - buttons: { main: { android: 62 /*, ios: <填> */ } }, // 空气净化器(Air Purifier)配对键(ch62,带 WiFi 模块;流程同加湿器2) + buttons: { main: { android: 62 , ios: 32 } }, // 空气净化器(Air Purifier)配对键(ch62,带 WiFi 模块;流程同加湿器2) actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '长按配对键 2s 进入添加模式(Air Purifier,WiFi;流程同 Evaporative Humidifier)' }, }, @@ -355,11 +357,16 @@ function channelOf(device: string, button: string): number { return ch; } -/** 取设备某动作要触发的通道列表 + 时长(按当前平台解析通道;env 可覆盖时长)。 */ +/** 取设备所在板号(MODBUS 设备 ID);缺省 1(现有接线全在 1 号板)。 */ +export function resolveRelayBoard(device: string): number { + return RELAY_MAP[device]?.board ?? 1; +} + +/** 取设备某动作要触发的通道列表 + 时长 + 板号(按当前平台解析通道;env 可覆盖时长)。 */ export function resolveRelayAction( device: string, action: string -): { channels: number[]; holdMs: number; desc?: string } { +): { channels: number[]; holdMs: number; desc?: string; board: number } { const dev = RELAY_MAP[device]; if (!dev) throw new Error(`relay 配置无设备 '${device}'(config/relay.config.ts)`); const act = dev.actions?.[action]; @@ -367,7 +374,7 @@ export function resolveRelayAction( const channels = act.buttons.map((b) => channelOf(device, b)); const envKey = `RELAY_HOLD_MS_${device.toUpperCase()}_${action.toUpperCase()}`; const holdMs = process.env[envKey] ? parseInt(process.env[envKey] as string, 10) : act.holdMs; - return { channels, holdMs, desc: act.desc }; + return { channels, holdMs, desc: act.desc, board: resolveRelayBoard(device) }; } /** 取设备某按键在当前平台的通道号。 */ diff --git a/scripts/press-raw.ts b/scripts/press-raw.ts index fcd2a8a..5f17cb4 100644 --- a/scripts/press-raw.ts +++ b/scripts/press-raw.ts @@ -2,8 +2,9 @@ import { SerialController } from '../utils/common/serial_controller'; (async () => { const chs = (process.env.CHS || '50,51').split(',').map(Number); const ms = Number(process.env.MS || 5000); - const c = new SerialController(); + const board = process.env.BOARD ? Number(process.env.BOARD) : undefined; + const c = new SerialController(board ? { deviceId: board } : {}); await c.open(); - try { await c.pulseChannels(chs, ms); console.log(`已脉冲通道[${chs.join(',')}] ${ms}ms`); } + try { await c.pulseChannels(chs, ms, board); console.log(`已脉冲 板${board ?? c.defaultDeviceId} 通道[${chs.join(',')}] ${ms}ms`); } finally { await c.close(); } })(); diff --git a/scripts/relay.ts b/scripts/relay.ts index ff3e2f9..31b02f1 100644 --- a/scripts/relay.ts +++ b/scripts/relay.ts @@ -1,15 +1,17 @@ /** * 继电器命令行调试工具 —— 参数化触发按键/配对/原始命令,方便接线调试。 * + * 多板:--board N 指定 MODBUS 板号(默认 1)。pair/press 不传时按 config 的 board 字段自动路由。 + * * 用法(端口/波特率走 .env 的 SERIAL_PORT/SERIAL_BAUD): - * npx ts-node scripts/relay.ts probe # 查版本(只读握手) - * npx ts-node scripts/relay.ts status # 读 64 路状态 - * npx ts-node scripts/relay.ts pair remote # 进配对(按 config 默认) + * 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 # 同时脉冲指定通道 - * npx ts-node scripts/relay.ts on 34 / off 34 # 单路通/断 - * npx ts-node scripts/relay.ts raw 55C8210155 # 发原始 16 进制帧 + * 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'; @@ -40,63 +42,64 @@ function positionals(): string[] { 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(); + const c = new SerialController(board ? { deviceId: board } : {}); await c.open(); - console.log(await c.queryVersion()); + console.log(await c.ping(board)); await c.close(); break; } case 'status': { - const c = new SerialController(); + const c = new SerialController(board ? { deviceId: board } : {}); await c.open(); - console.log('status:', (await c.readStatus()).toString('hex').toUpperCase()); + console.log('status:', (await c.readStatus(board)).toString('hex').toUpperCase()); await c.close(); break; } case 'pair': { - if (!a) throw new Error('用法: pair [--hold ms]'); - await triggerRelayAction(a, 'pairing', hold ? { holdMs: hold } : {}); + if (!a) throw new Error('用法: pair [--hold ms] [--board N]'); + await triggerRelayAction(a, 'pairing', { ...(hold ? { holdMs: hold } : {}), ...(board ? { deviceId: board } : {}) }); break; } case 'press': { - if (!a || !b) throw new Error('用法: press