/** * 继电器板通道映射 + 物理按键动作配置(参数化,勿在脚本里写死通道号/时长)。 * * 每路继电器接到某设备的某个物理按键或电源。这里把「设备 → 按键名 → 通道号」以及 * 「进入配对模式要按哪些键、按多久」集中配置,脚本只引用语义名(如 'remote' 的 'pairing')。 * * **按平台区分通道**:Android 与 iOS 是两套真机,同一设备的按键接在继电器板的不同通道上。 * 通道号写成 { android, ios };动作逻辑(按哪些键/按多久)两端共用。平台取 process.env.PLATFORM * (与 config/app.config、设备名注册表一致,默认 ios)。 * * 实测接线(Android,2026-06):ch33=meter 主键、ch34=remote 凸键、ch35=remote 凹键。 * iOS 通道待接线后填入(留空则在 iOS 下调用会报清晰错误)。 * 时长可被 env 覆盖:RELAY_HOLD_MS__(全大写)。 */ /** 通道号:两端相同写数字;两端不同写 { android, ios }。 */ export type ChannelSpec = number | { android?: number; ios?: number }; export interface RelayAction { buttons: string[]; // 同时按下的按键名(引用本设备 buttons 的 key);单键即数组里一个 holdMs: number; // 按住毫秒数 desc?: string; } export interface RelayDeviceConfig { buttons: Record; // 按键名 → 通道(可按平台) actions?: Record; } export const RELAY_MAP: Record = { meter: { buttons: { main: { android: 32 /*, ios: <填iOS通道> */ } }, actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住主键进入添加模式' }, }, }, remote: { buttons: { convex: { android: 26 /*, ios: <填> */ }, // 凸键 concave: { android: 25 /*, ios: <填> */ }, // 凹键 }, actions: { pairing: { buttons: ['convex', 'concave'], holdMs: 2000, desc: '同时按凸键+凹键 2s 进入添加模式(切换式:按一次进、再按退,勿重按)' }, }, }, outdoor_meter: { buttons: { main: { android: 28 /*, ios: <填> */ } }, actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住主键 2s 进入添加模式' }, }, }, motion: { buttons: { main: { android: 29 /*, ios: <填> */ } }, // Motion Sensor 配对键(电池仓内按键) actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住配对键 2s 进入添加模式' }, }, }, curtain3: { buttons: { power: { android: 27 /*, ios: <填> */ }, // 开关按钮 track: { android: 38 /*, ios: <填> */ }, // 控制轨道按钮(控制用,不参与配对) }, actions: { pairing: { buttons: ['power'], holdMs: 2000, desc: '按住开关按钮 2s 进入添加模式' }, }, }, curtain: { buttons: { power: { android: 24 /*, ios: <填> */ } }, // 开关按钮 actions: { pairing: { buttons: ['power'], holdMs: 2000, desc: '按住开关按钮 2s 进入添加模式' }, }, }, hub: { buttons: { main: { android: 23 /*, ios: <填> */ } }, // Hub Mini 主键 actions: { pairing: { buttons: ['main'], holdMs: 4000, desc: '按住主键 4s 进入添加模式(Hub Mini;指引页写3s,实测需4s)' }, }, }, hub3: { buttons: { off: { android: 9 /*, ios: <填> */ }, // 关机键(ch9) on: { android: 10 /*, ios: <填> */ }, // 开机键(ch10) }, actions: { pairing: { buttons: ['off', 'on'], holdMs: 3000, desc: '同时长按 off+on 键(ch9+10)3s 进入添加模式(Hub 3,WiFi)' }, }, }, hub_plus: { buttons: { a: { android: 14 /*, ios: <填> */ }, // Hub Plus 配对键1(ch14,与 K20+/S10 共用,物理同时只接一个) b: { android: 15 /*, ios: <填> */ }, // Hub Plus 配对键2(ch15) }, actions: { pairing: { buttons: ['a', 'b'], holdMs: 4000, desc: '同时长按 ch14+15 4s 进入添加模式(Hub Plus,WiFi;时长待实测)' }, }, }, relay_switch: { buttons: { main: { android: 48 /*, ios: <填> */ } }, // Relay Switch 1 配对键(ch48,WiFi 产品) actions: { // 连接页明示"press the button for 2s" pairing: { buttons: ['main'], holdMs: 4000, desc: '长按配对键 2s 进入添加模式(Relay Switch 1,WiFi)' }, }, }, water: { buttons: { main: { android: 22 /*, ios: <填> */ } }, // Water Detector(水浸)配对键 actions: { // ⚠️ 配对时长待实测;先按 2s 试 pairing: { buttons: ['main'], holdMs: 2000, desc: '按住配对键进入添加模式(水浸,待实测确认时长)' }, }, }, urc: { buttons: { off: { android: 20 /*, ios: <填> */ }, // 关机键(ch20) on: { android: 21 /*, ios: <填> */ }, // 开机键(ch21) }, actions: { pairing: { buttons: ['off', 'on'], holdMs: 4000, desc: '同时长按 off+on 键(ch20+21)4s 进入添加模式(Universal Remote 万能遥控器)' }, }, }, hubMatter: { buttons: { main: { android: 23 /*, ios: <填> */ } }, // Hub Mini Matter 主键(添加入口仍是 "Hub Mini") actions: { // ⚠️ 配对时长待实测;先沿用 Hub Mini 的 4s pairing: { buttons: ['main'], holdMs: 4000, desc: '按住主键进入添加模式(Hub Mini Matter,添加入口=Hub Mini;待实测确认时长)' }, }, }, contact: { buttons: { main: { android: 47 /*, ios: <填> */ } }, // 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 主键 actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住主键 2s 进入添加模式(Meter Plus)' }, }, }, find_card: { buttons: { main: { android: 30 /*, ios: <填> */ } }, // Wallet Finder Card 配对键 actions: { // ⚠️ 配对时长待实测 pairing: { buttons: ['main'], holdMs: 2000, desc: '按住配对键进入添加模式(Wallet Finder Card,待实测确认时长)' }, }, }, plug_mini: { buttons: { main: { android: 1 /*, ios: <填> */ } }, // Plug Mini (JP) 配对键 actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住配对键2s 进入添加模式(Plug Mini JP,品类"Plug Mini (JP)")' }, }, }, plug_mini_us: { buttons: { main: { android: 2 /*, ios: <填> */ } }, // Plug Mini (US) 配对键 actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住配对键2s 进入添加模式(Plug Mini US,品类"Plug Mini (US)")' }, }, }, safety_alarm: { buttons: { main: { android: 3 /*, ios: <填> */ } }, // 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 同流程、入口品类名不同) actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住主键 2s 进入添加模式(Meter Pro CO2)' }, }, }, presence_sensor: { buttons: { main: { android: 5 /*, ios: <填> */ } }, // 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) actions: { pairing: { buttons: ['power'], holdMs: 2000, desc: '按住开关键 2s 进入添加模式(Curtain 3 2025)' }, }, }, plug_mini_eu: { buttons: { main: { android: 7 /*, ios: <填> */ } }, // Plug Mini (EU) 配对键(ch7,WiFi 产品,品类“Plug Mini (EU)”) actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住配对键2s 进入添加模式(Plug Mini EU,品类“Plug Mini (EU)”)' }, }, }, color_bulb: { buttons: { main: { android: 2 /*, ios: <填> */ } }, // Color Bulb 配对键(ch2,与 plug_mini_us 共用通道;物理同时只接一个) actions: { // 特殊:点一次→等2s→再点一次 进配对(非单次长按)。脚本用 pressButton 两次实现。 pairing: { buttons: ['main'], holdMs: 400, desc: '点一次→等2s→再点一次 进添加模式(Color Bulb,WiFi 产品)' }, }, }, ceiling_light_pro: { buttons: { main: { android: 1 /*, ios: <填> */ } }, // 吸顶灯 Pro 配对键(ch1,与 plug_mini JP 共用通道;同时只接一个) actions: { // 同 Color Bulb:点一次→等2s→再点一次 进配对(脚本用 pressButton 两次)。 pairing: { buttons: ['main'], holdMs: 400, desc: '点一次→等2s→再点一次 进添加模式(吸顶灯 Pro,WiFi 产品)' }, }, }, strip_light: { buttons: { main: { android: 33 /*, ios: <填> */ } }, // Strip Light(灯带)配对键(ch33,灯类,WiFi 产品) actions: { // 长按 2s 进配对(灯带,不同于 color bulb 的两次点按)。 pairing: { buttons: ['main'], holdMs: 2000, desc: '长按配对键 2s 进入添加模式(Strip Light 灯带,WiFi)' }, }, }, doorbell: { buttons: { talk: { android: 36 /*, ios: <填> */ }, // 通话键 hangup: { android: 37 /*, ios: <填> */ }, // 挂断键 }, actions: { pairing: { buttons: ['talk', 'hangup'], holdMs: 5000, desc: '同时长按 通话+挂断 键 5s 进入添加模式(门铃 Video Doorbell,WiFi)' }, }, }, outdoor_ptc: { buttons: { main: { android: 36 /*, ios: <填> */ } }, // Outdoor PTC 配对键(ch36;与 doorbell.talk 同口,物理同时只接一个) actions: { // ⚠️ 配对时长待实测;先按 2s 试(流程参考水浸 Water Detector:按键进配对 + WiFi 配网) pairing: { buttons: ['main'], holdMs: 2000, desc: '按住配对键 2s 进入添加模式(Outdoor PTC,入口品类 Outdoor Pan/Tilt Cam 3K;待实测确认时长)' }, }, }, keypad: { buttons: { unlock: { android: 35 /*, ios: <填> */ }, // ch35=上锁键(配对用,历史名 unlock) confirm: { android: 34 /*, ios: <填> */ }, // ch34=确定键 k1: { android: 51 /*, ios: <填> */ }, // 数字键 1-6 = ch51-56(密码输入用) k2: { android: 52 /*, ios: <填> */ }, k3: { android: 53 /*, ios: <填> */ }, k4: { android: 54 /*, ios: <填> */ }, k5: { android: 55 /*, ios: <填> */ }, k6: { android: 56 /*, ios: <填> */ }, }, actions: { pairing: { buttons: ['unlock', 'confirm'], holdMs: 5000, desc: '同时长按 ch35+ch34 5s 进入添加模式(Keypad Vision Pro,BLE)' }, // 输入密码 123456 + 确定:脚本依次短按 k1..k6 再短按 confirm(ch34)。 }, }, keypad_touch: { buttons: { key1: { android: 39 /*, ios: <填> */ }, key2: { android: 40 /*, ios: <填> */ }, }, actions: { pairing: { buttons: ['key1', 'key2'], holdMs: 3000, desc: '同时长按 39+40 键 3s 进入添加模式(Keypad Touch,BLE)' }, }, }, neon_light: { buttons: { main: { android: 41 /*, ios: <填> */ } }, // 炫彩霓虹灯(RGBIC Neon Rope)配对键(ch41,灯类WiFi,同灯带流程) actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '长按配对键 2s 进入添加模式(炫彩霓虹灯,WiFi)' }, }, }, floor_lamp: { buttons: { main: { android: 42 /*, ios: <填> */ } }, // 落地灯(Floor Lamp)配对键(ch42,灯类WiFi) actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '长按配对键 2s 进入添加模式(落地灯 Floor Lamp,WiFi)' }, }, }, strip_light_3: { buttons: { main: { android: 45 /*, ios: <填> */ } }, // 灯带3(RGBWW Strip Light 3)配对键(ch45,灯类WiFi;流程同落地灯) actions: { pairing: { buttons: ['main'], holdMs: 4000, desc: '长按配对键 4s 进入添加模式(灯带3 RGBWW strip light 3,WiFi;流程同落地灯)' }, }, }, color_floor_lamp: { buttons: { main: { android: 44 /*, ios: <填> */ } }, // 炫彩落地灯(RGBIC Floor Lamp)配对键(ch44,灯类WiFi) actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '长按配对键 2s 进入添加模式(炫彩落地灯 RGBIC Floor Lamp,WiFi)' }, }, }, eave_light: { buttons: { main: { android: 38 /*, ios: <填> */ } }, // 屋檐灯(Permanent Outdoor Lights)配对键(ch38,灯类WiFi;与 curtain3.track 共用通道,物理同时只接一个) actions: { pairing: { buttons: ['main'], holdMs: 4000, desc: '长按配对键 4s 进入添加模式(屋檐灯 Permanent Outdoor Lights,WiFi;流程同落地灯 Floor Lamp)' }, }, }, aihub: { buttons: { main: { android: 43 /*, ios: <填> */ } }, // AI Hub 配对键(ch43,与 Hub Mini 同口;按一下即可) actions: { pairing: { buttons: ['main'], holdMs: 400, desc: '按一下配对键(AI Hub,单次短按)' }, }, }, robot_s1p: { buttons: { a: { android: 11 /*, ios: <填> */ }, // 扫地机 S1 Plus 配对键1(ch11) b: { android: 12 /*, ios: <填> */ }, // 扫地机 S1 Plus 配对键2(ch12) }, actions: { pairing: { buttons: ['a', 'b'], holdMs: 5000, desc: '同时长按 ch11+12 5s 进入添加模式(扫地机 S1 Plus;在添加向导第二步按)' }, }, }, robot_k20p: { buttons: { a: { android: 13 /*, ios: <填> */ }, // 扫地机 K20+ 配对键1(ch13) b: { android: 14 /*, ios: <填> */ }, // 扫地机 K20+ 配对键2(ch14) }, actions: { pairing: { buttons: ['a', 'b'], holdMs: 5000, desc: '同时长按 ch13+14 5s 进入添加模式(扫地机 K20+;流程同 S1 Plus,向导第二步按)' }, }, }, robot_s10: { buttons: { a: { android: 17 /*, ios: <填> */ }, // 扫地机 S10 配对键1(ch17) b: { android: 18 /*, ios: <填> */ }, // 扫地机 S10 配对键2(ch18) }, actions: { pairing: { buttons: ['a', 'b'], holdMs: 5000, desc: '同时长按 ch17+18 5s 进入添加模式(扫地机 S10;入口 Floor Cleaning Robot S10,流程同水浸串口配对+配网)' }, }, }, ai_art_frame: { buttons: { main: { android: 46 /*, ios: <填> */ } }, // AI Art Frame 配对键(ch46,流程同 AI Hub;长按 3s 进添加模式) actions: { pairing: { buttons: ['main'], holdMs: 3000, desc: '长按配对键 3s 进入添加模式(AI Art Frame,流程同 AI Hub)' }, }, }, kata: { buttons: { key1: { android: 17 /*, ios: <填> */ }, key2: { android: 18 /*, ios: <填> */ }, }, actions: { pairing: { buttons: ['key1', 'key2'], holdMs: 3000, desc: '同时长按 17+18 键 3s 进入添加模式(KATA Friends,流程同 AI Hub)' }, }, }, humidifier2: { buttons: { main: { android: 63 /*, ios: <填> */ } }, // 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) actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '长按配对键 2s 进入添加模式(Air Purifier,WiFi;流程同 Evaporative Humidifier)' }, }, }, }; function currentPlatform(): 'android' | 'ios' { return (process.env.PLATFORM || 'ios').toLowerCase() === 'android' ? 'android' : 'ios'; } /** 解析某按键在当前平台的通道号。iOS 未单独配通道时,回退到 android(两端复用同一套设备/通道时用)。 */ function channelOf(device: string, button: string): number { const spec = RELAY_MAP[device]?.buttons?.[button]; if (spec == null) throw new Error(`relay 配置无 '${device}.${button}'(config/relay.config.ts)`); if (typeof spec === 'number') return spec; const plat = currentPlatform(); const ch = spec[plat] ?? spec.android; // iOS 缺省回退 android(先复用 Android 配置) if (ch == null) throw new Error(`'${device}.${button}' 未配置 ${plat}(且无 android 回退)的通道(config/relay.config.ts)`); return ch; } /** 取设备某动作要触发的通道列表 + 时长(按当前平台解析通道;env 可覆盖时长)。 */ export function resolveRelayAction( device: string, action: string ): { channels: number[]; holdMs: number; desc?: string } { const dev = RELAY_MAP[device]; if (!dev) throw new Error(`relay 配置无设备 '${device}'(config/relay.config.ts)`); const act = dev.actions?.[action]; if (!act) throw new Error(`设备 '${device}' 无动作 '${action}'`); 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 }; } /** 取设备某按键在当前平台的通道号。 */ export function resolveRelayButton(device: string, button: string): number { return channelOf(device, button); }