/** * 继电器板通道映射 + 物理按键动作配置(参数化,勿在脚本里写死通道号/时长)。 * * 每路继电器接到某设备的某个物理按键或电源。这里把「设备 → 按键名 → 通道号」以及 * 「进入配对模式要按哪些键、按多久」集中配置,脚本只引用语义名(如 '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: 33 /*, ios: <填iOS通道> */ } }, actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住主键进入添加模式' }, }, }, remote: { buttons: { convex: { android: 34 /*, ios: <填> */ }, // 凸键 concave: { android: 35 /*, ios: <填> */ }, // 凹键 }, actions: { pairing: { buttons: ['convex', 'concave'], holdMs: 2000, desc: '同时按凸键+凹键 2s 进入添加模式(切换式:按一次进、再按退,勿重按)' }, }, }, outdoor_meter: { buttons: { main: { android: 36 /*, ios: <填> */ } }, actions: { pairing: { buttons: ['main'], holdMs: 2000, desc: '按住主键 2s 进入添加模式' }, }, }, curtain3: { buttons: { power: { android: 37 /*, ios: <填> */ }, // 开关按钮 track: { android: 38 /*, ios: <填> */ }, // 控制轨道按钮(控制用,不参与配对) }, actions: { pairing: { buttons: ['power'], holdMs: 2000, desc: '按住开关按钮 2s 进入添加模式' }, }, }, curtain: { buttons: { power: { android: 39 /*, ios: <填> */ } }, // 开关按钮 actions: { pairing: { buttons: ['power'], holdMs: 2000, desc: '按住开关按钮 2s 进入添加模式' }, }, }, hub: { buttons: { main: { android: 43 /*, ios: <填> */ } }, // Hub Mini 主键 actions: { pairing: { buttons: ['main'], holdMs: 4000, desc: '按住主键 4s 进入添加模式(Hub Mini;指引页写3s,实测需4s)' }, }, }, }; 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); }