@switchbot/homebridge-switchbot
Version:
The SwitchBot plugin allows you to access your SwitchBot device(s) from HomeKit.
203 lines (194 loc) • 6.54 kB
text/typescript
import type { Logger } from 'homebridge'
import type { DeviceType, SwitchBotPluginConfig } from './settings.js'
import { DEVICE_TYPE_NORMALIZATION_MAP } from './device-types.js'
import {
BlindTiltDevice,
BotDevice,
ContactSensorDevice,
Curtain3Device,
CurtainDevice,
FanDevice,
GenericDevice,
Hub2Device,
HumidifierDevice,
LightDevice,
LightStripDevice,
LockDevice,
MeterDevice,
MotionSensorDevice,
PlugDevice,
PlugMiniDevice,
RelaySwitch1PMDevice,
RelaySwitchDevice,
RollerShadeDevice,
SmartFanDevice,
StripLightDevice,
TemperatureSensorDevice,
VacuumDevice,
WalletFinderDevice,
WaterDetectorDevice,
WoSweeperDevice,
WoSweeperMiniDevice,
WoSweeperMiniProDevice,
} from './devices/genericDevice.js'
import { SwitchBotClient } from './switchbotClient.js'
export interface DeviceOptions {
id: string
type: DeviceType
name?: string
[key: string]: any
}
const DEVICE_CLASS_MAP: Record<string, any> = {
// Primary device type keys (lowercase, simplified)
'bot': BotDevice,
'curtain': CurtainDevice,
'curtain3': Curtain3Device,
'fan': FanDevice,
'light': LightDevice,
'lightstrip': LightStripDevice,
'motion': MotionSensorDevice,
'contact': ContactSensorDevice,
'vacuum': VacuumDevice,
// Canonical, normalized device type keys (lowercase, mapped to device classes)
'video doorbell': GenericDevice,
'smart radiator thermostat': GenericDevice,
'woiosensor': GenericDevice,
'garage door opener': GenericDevice,
'air purifier table pm2.5': GenericDevice,
'air purifier voc': GenericDevice,
'air purifier table voc': GenericDevice,
'meterplus': MeterDevice,
'meterpro': MeterDevice,
'meterpro(co2)': MeterDevice,
'walletfinder': WalletFinderDevice,
'plug': PlugDevice,
'plug mini (eu)': PlugMiniDevice,
'plug mini (jp)': PlugMiniDevice,
'plug mini (us)': PlugMiniDevice,
'relay switch 1pm': RelaySwitch1PMDevice,
'relay switch 2pm': RelaySwitch1PMDevice,
'k10+ pro': WoSweeperDevice,
'robot vacuum cleaner k10+ pro combo': WoSweeperDevice,
'robot vacuum cleaner k11+': WoSweeperDevice,
'robot vacuum cleaner k20 plus pro': WoSweeperDevice,
'ai hub': GenericDevice,
'hub': GenericDevice,
'hub 2': Hub2Device,
'hub 3': GenericDevice,
'hub mini': GenericDevice,
'hub plus': GenericDevice,
'indoor cam': GenericDevice,
'pan/tilt cam': GenericDevice,
'pan/tilt cam 2k': GenericDevice,
'pan/tilt cam plus 2k': GenericDevice,
'pan/tilt cam plus 3k': GenericDevice,
'humidifier': HumidifierDevice, // Includes evaporative humidifier mapping
'roller shade': RollerShadeDevice,
'strip light 3': StripLightDevice,
'circulator fan': FanDevice,
'smart lock pro': LockDevice,
'lock lite': LockDevice,
'keypad': LockDevice,
'lock vision pro': LockDevice,
'floor lamp': LightDevice,
'rgbicww floor lamp': LightStripDevice,
'rgbicww strip light': LightStripDevice,
'home climate panel': GenericDevice, // Climate panel family
'lock': LockDevice,
'humidifier2': HumidifierDevice,
'temperature': TemperatureSensorDevice,
'relay switch 1': RelaySwitchDevice,
'blind tilt': BlindTiltDevice,
'worollershade': RollerShadeDevice,
'wo rollershade': RollerShadeDevice,
'rollershade': RollerShadeDevice,
'meter': MeterDevice,
'meter plus (jp)': MeterDevice,
'water detector': WaterDetectorDevice,
'smart fan': SmartFanDevice,
'strip light': StripLightDevice,
'wosweeper': WoSweeperDevice,
'wosweepermini': WoSweeperMiniDevice,
'wosweeperminipro': WoSweeperMiniProDevice,
'k10+': WoSweeperDevice,
'k10+ pro (wosweeperminipro)': WoSweeperMiniProDevice,
'battery circulator fan': FanDevice,
'standing circulator fan': FanDevice,
'smart lock': LockDevice,
'smart lock ultra': LockDevice,
'keypad touch': LockDevice,
'keypad vision': LockDevice,
'keypad vision pro': LockDevice,
'color bulb': LightDevice,
'ceiling light': LightDevice,
'ceiling light pro': LightDevice,
'candle warmer lamp': LightDevice,
'rgbic neon rope light': LightStripDevice,
'rgbic neon wire rope light': LightStripDevice,
'robot vacuum cleaner s1': VacuumDevice,
'robot vacuum cleaner s1 plus': VacuumDevice,
'robot vacuum cleaner s10': VacuumDevice,
'robot vacuum cleaner s20': VacuumDevice,
}
function classForType(type: string) {
const rawKey = (type || '').toLowerCase()
const key = DEVICE_TYPE_NORMALIZATION_MAP[rawKey] ?? rawKey
return DEVICE_CLASS_MAP[key] ?? GenericDevice
}
export async function createDevice(opts: DeviceOptions, cfg: SwitchBotPluginConfig, useMatter: boolean, log?: Logger) {
// Always pass the logger to both device opts and config
const logger = log || (cfg as any)?.logger || (cfg as any)?.log
if (opts && opts.name && logger && typeof logger.info === 'function') {
logger.info(`[Matter/Debug] createDevice: Passing opts for ${opts.name}:`, JSON.stringify(opts, null, 2))
}
// Reuse existing client when provided via config to avoid creating
// a new SwitchBotClient per device (which can be expensive).
let client: any = (cfg as any)?._client ?? null
if (!client) {
client = new SwitchBotClient(cfg)
await client.init()
}
// Pass client via config so devices can access it
const mergedCfg = { ...(cfg as any), _client: client }
// Always pass logger to mergedCfg
if (logger) {
mergedCfg.log = logger
}
// Pass encryptionKey and keyId to device opts if present
const deviceOpts = { ...opts }
if (opts.encryptionKey) {
deviceOpts.encryptionKey = opts.encryptionKey
}
if (opts.keyId) {
deviceOpts.keyId = opts.keyId
}
// Always pass logger to deviceOpts
if (logger) {
deviceOpts.log = logger
}
const DeviceCtor = classForType(opts.type)
const device = new DeviceCtor(deviceOpts, mergedCfg)
await device.init()
// Attach a simple getState delegator to the client where appropriate
const originalGetState = device.getState.bind(device)
device.getState = async () => {
try {
// Prefer client-backed getDevice when available
const dev = await client.getDevice(opts.id)
if (dev) {
return dev
}
} catch (e) {
// ignore and fallback to device implementation
}
return originalGetState()
}
// Provide accessory factory based on platform selection
return {
instance: device,
createAccessory: useMatter
? async (api: any) => await device.createMatterAccessory(api)
: (api: any) => device.createHAPAccessory(api),
protocol: useMatter ? 'matter' : 'hap',
}
}