377 lines
17 KiB
TypeScript
377 lines
17 KiB
TypeScript
/**
|
|
* 继电器板通道映射 + 物理按键动作配置(参数化,勿在脚本里写死通道号/时长)。
|
|
*
|
|
* 每路继电器接到某设备的某个物理按键或电源。这里把「设备 → 按键名 → 通道号」以及
|
|
* 「进入配对模式要按哪些键、按多久」集中配置,脚本只引用语义名(如 '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_<DEVICE>_<ACTION>(全大写)。
|
|
*/
|
|
|
|
/** 通道号:两端相同写数字;两端不同写 { 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<string, ChannelSpec>; // 按键名 → 通道(可按平台)
|
|
actions?: Record<string, RelayAction>;
|
|
}
|
|
|
|
export const RELAY_MAP: Record<string, RelayDeviceConfig> = {
|
|
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);
|
|
}
|