UNPKG

homebridge-rpi

Version:
446 lines (414 loc) 15.7 kB
// homebridge-rpi/lib/RpiAccessory.js // Copyright © 2019-2026 Erik Baauw. All rights reserved. // // Homebridge plugin for Raspberry Pi. import { toHexString } from 'homebridge-lib' import { AccessoryDelegate } from 'homebridge-lib/AccessoryDelegate' import { ServiceDelegate } from 'homebridge-lib/ServiceDelegate' import 'homebridge-lib/ServiceDelegate/History' // TODO: import on-demand import { RpiInfo } from 'hb-rpi-tools/RpiInfo' import { RpiService } from './RpiService.js' import './RpiService/RpiFan.js' // TODO: import on-demand import './RpiService/RpiPowerLed.js' // TODO: import on-demand import './RpiService/RpiSmokeSensor.js' // TODO: import on-demand import './RpiService/RpiUsbPower.js' // TODO: import on-demand let LedChainClient class RpiAccessory extends AccessoryDelegate { constructor (platform, params) { super(platform, params) this.id = params.id if (this.id === this.platform.localId) { // Local Pi this.rpiInfo = new RpiInfo() this.rpiInfo .on('error', (error) => { this.warn(error) }) .on('exec', (command) => { this.debug('exec: %s', command) }) .on('readFile', (fileName) => { this.debug('read file: %s', fileName) }) } this.pi = params.pi this.pi.removeAllListeners() this.pi .on('error', (error) => { this.warn(error) }) .on('warning', (error) => { this.warn(error) }) .on('connect', async (hostname, port) => { this.log('connected to %s:%s', hostname, port) }) .on('ready', async () => { try { await this.init() } catch (error) { this.warn(error) } }) .on('disconnect', (hostname, port) => { this.log('disconnected from %s:%s', hostname, port) this.setFault(true) for (const key in this.gpioAccessories) { this.gpioAccessories[key].setFault(true) } }) .on('command', (cmd, params) => { this.vdebug('command %s %j', this.pi.commandName(cmd), params) }) .on('response', (cmd, result) => { this.vdebug('command %s => %s', this.pi.commandName(cmd), result) }) .on('send', (data) => { this.vvdebug('send: %j', toHexString(data)) }) .on('data', (data) => { this.vvdebug('recv: %j', toHexString(data)) }) .on('message', (message) => { this.debug(message) }) .on('listen', (map) => { this.debug('listen map: [%s]', this.pi.vmap(map)) }) .on('notification', (payload) => { this.vdebug( 'gpio map: [%s]%s%s%s', this.pi.vmap(payload.map), payload.tick == null ? '' : ', tick: ' + payload.tick, payload.flags == null ? '' : ', flags: 0x' + toHexString(payload.flags, 2), payload.seqno == null ? '' : ', seqno: ' + payload.seqno ) }) this.context.hostname = this.pi.hostname this.gpioMask = params.gpioMask this.hidden = params.hidden this.rpiService = new RpiService(this, { hidden: this.hidden }) this.manageLogLevel(this.rpiService.characteristicDelegate('logLevel')) if (!this.hidden) { if (!params.noSmokeSensor) { this.smokeService = new RpiService.SmokeSensor(this) } this.historyService = new ServiceDelegate.History( this, { temperatureDelegate: this.rpiService.characteristicDelegate('temperature') } ) } if (!params.noPowerLed) { this.powerLedService = new RpiService.PowerLed(this) } if (!params.noFan) { this.fanService = new RpiService.Fan(this) } if (params.usbPower) { this.usbPowerService = new RpiService.UsbPower(this) } this.gpioAccessories = {} this.usedGpios = {} this .on('heartbeat', async (beat) => { if (beat % this.rpiService.values.heartrate === 0) { if (this.inHeartbeat) { return } this.inHeartbeat = true await this.heartbeat(beat) this.inHeartbeat = false } }) } async init () { this.setFault(false) for (const key in this.gpioAccessories) { try { await this.gpioAccessories[key].init() this.gpioAccessories[key].setFault(false) } catch (error) { this.warn(error) } } try { await this.pi.listen() } catch (error) { this.warn(error) } this.debug('used gpios: %j', Object.keys(this.usedGpios)) this.emit('initialised') this.heartbeatEnabled = true } setFault (fault) { const statusFault = fault ? this.Characteristics.hap.StatusFault.GENERAL_FAULT : this.Characteristics.hap.StatusFault.NO_FAULT this.rpiService.values.statusFault = statusFault } async shutdown () { for (const key in this.gpioAccessories) { try { await this.gpioAccessories[key].shutdown() } catch (error) { this.warn(error) } } await this.pi.disconnect() } async heartbeat (beat) { try { let state if (this.id === this.platform.localId) { // Local Pi if (!this.hidden) { state = await this.rpiInfo.getState(this.powerLedService == null, this.fanService == null) } if (this.usesGpio) { try { await this.pi.command(this.pi.commands.TICK) } catch (error) { this.warn(error) } } } else { // Remote Pi if (this.hidden && this.powerLedService == null && this.fanService == null) { await this.pi.command(this.pi.commands.TICK) } else { await this.pi.shell('getState') const text = await this.pi.readFile('/tmp/getState.json') this.vdebug('raw state: %s', text) state = RpiInfo.parseState(text) if (state.swap == null && !this.warned) { this.warn('old getState script on the remote Raspberry Pi') this.warned = true } } } if (state != null) { this.debug('state: %j', state) } if (!this.hidden) { this.rpiService.checkState(state) } if (this.powerLedService != null) { this.powerLedService.checkState(state) } if (this.fanService != null) { this.fanService.checkState(state) } } catch (error) { this.warn('heartbeat error: %s', error) } for (const gpio in this.gpioAccessories) { const gpioAccessory = this.gpioAccessories[gpio] if (gpioAccessory.service != null && gpioAccessory.service.heartbeat != null) { try { await gpioAccessory.service.heartbeat(beat) } catch (error) { this.warn(error) } } } } checkGpio (gpio) { if ((this.gpioMask & (1 << gpio)) === 0) { throw new Error(`${gpio}: invalid gpio`) } if (this.usedGpios[gpio] != null) { throw new Error(`${gpio}: duplicate gpio`) } this.usedGpios[gpio] = true this.usesGpio = true } async createGpioAccessory (device) { this.checkGpio(device.gpio) if (RpiAccessory.GpioAccessory == null) { await import('./RpiAccessory/GpioAccessory.js') } const gpioAccessory = new RpiAccessory.GpioAccessory(this, device) this.gpioAccessories[device.gpio] = gpioAccessory return gpioAccessory } async addLedChain (device) { this.checkGpio(device.gpioClock) this.checkGpio(device.gpioData) if (RpiAccessory.LedChainAccessory == null) { await import('./RpiAccessory/LedChainAccessory.js') LedChainClient = await import('hb-rpi-tools/LedChainClient') } if (device.device === 'p9813') { if (LedChainClient.P9813 == null) { await import('hb-rpi-tools/LedChainClient/P9813') } } else { if (LedChainClient.Blinkt == null) { await import('hb-rpi-tools/LedChainClient/Blinkt') } } const ledChainAccessory = new RpiAccessory.LedChainAccessory(this, device) this.gpioAccessories[device.gpioClock] = ledChainAccessory } async addButton (device) { this.checkGpio(device.gpio) if (this.buttonAccessory == null) { if (RpiAccessory.ButtonAccessory == null) { await import('./RpiAccessory/ButtonAccessory.js') } this.buttonAccessory = new RpiAccessory.ButtonAccessory(this, device) this.gpioAccessories[device.gpio] = this.buttonAccessory } await this.buttonAccessory.addButton(device) } async addCarbonMonoxide (device) { const gpioAccessory = await this.createGpioAccessory(device) if (RpiService.GpioCarbonMonoxide == null) { await import('./RpiService/GpioInput/GpioCarbonMonoxide.js') } gpioAccessory.service = new RpiService.GpioCarbonMonoxide(gpioAccessory, device) setImmediate(() => { gpioAccessory.emit('initialised') }) } async addContact (device) { const gpioAccessory = await this.createGpioAccessory(device) if (RpiService.GpioContact == null) { await import('./RpiService/GpioInput/GpioContact.js') } gpioAccessory.service = new RpiService.GpioContact(gpioAccessory, device) gpioAccessory.historyService = new ServiceDelegate.History( gpioAccessory, { contactDelegate: gpioAccessory.service.characteristicDelegate('contact'), timesOpenedDelegate: gpioAccessory.service.characteristicDelegate('timesOpened'), lastContactDelegate: gpioAccessory.service.characteristicDelegate('lastActivation') } ) setImmediate(() => { gpioAccessory.emit('initialised') }) } async addDht (device) { const gpioAccessory = await this.createGpioAccessory(device) if (RpiService.GpioDht == null) { await import('./RpiService/GpioDht.js') } gpioAccessory.service = new RpiService.GpioDht(gpioAccessory, device) gpioAccessory.humidityService = new RpiService.GpioDht.Humidity(gpioAccessory) if (gpioAccessory.service.values.temperature == null) { gpioAccessory.service.characteristicDelegate('temperature').once('didSet', () => { this.historyService = new ServiceDelegate.History( gpioAccessory, { temperatureDelegate: gpioAccessory.service.characteristicDelegate('temperature'), humidityDelegate: gpioAccessory.humidityService.characteristicDelegate('humidity') } ) }) } else { this.historyService = new ServiceDelegate.History( gpioAccessory, { temperatureDelegate: gpioAccessory.service.characteristicDelegate('temperature'), humidityDelegate: gpioAccessory.humidityService.characteristicDelegate('humidity') } ) } setImmediate(() => { gpioAccessory.emit('initialised') }) } async addDoorBell (device) { const gpioAccessory = await this.createGpioAccessory(device) if (RpiService.GpioDoorBell == null) { await import('./RpiService/GpioInput/GpioDoorBell.js') } gpioAccessory.service = new RpiService.GpioDoorBell(gpioAccessory, device) setImmediate(() => { gpioAccessory.emit('initialised') }) } async addFan (device) { const gpioAccessory = await this.createGpioAccessory(device) if (RpiService.GpioFan == null) { await import('./RpiService/GpioFan.js') } gpioAccessory.service = new RpiService.GpioFan(gpioAccessory, device) setImmediate(() => { gpioAccessory.emit('initialised') }) } async addGarage (device) { const gpioAccessory = await this.createGpioAccessory(device) if (RpiService.GpioGarage == null) { await import('./RpiService/GpioOutput/GpioGarage.js') } gpioAccessory.service = new RpiService.GpioGarage(gpioAccessory, device) setImmediate(() => { gpioAccessory.emit('initialised') }) } async addLeak (device) { const gpioAccessory = await this.createGpioAccessory(device) if (RpiService.GpioLeak == null) { await import('./RpiService/GpioInput/GpioLeak.js') } gpioAccessory.service = new RpiService.GpioLeak(gpioAccessory, device) setImmediate(() => { gpioAccessory.emit('initialised') }) } async addLight (device) { const gpioAccessory = await this.createGpioAccessory(device) if (RpiService.GpioLight == null) { await import('./RpiService/GpioLight.js') } gpioAccessory.service = new RpiService.GpioLight(gpioAccessory, device) gpioAccessory.historyService = new ServiceDelegate.History( gpioAccessory, { lightOnDelegate: gpioAccessory.service.characteristicDelegate('on'), lastLightOnDelegate: gpioAccessory.service.characteristicDelegate('lastActivation') } ) setImmediate(() => { gpioAccessory.emit('initialised') }) } async addLock (device) { const gpioAccessory = await this.createGpioAccessory(device) if (RpiService.GpioLock == null) { await import('./RpiService/GpioOutput/GpioLock.js') } gpioAccessory.service = new RpiService.GpioLock(gpioAccessory, device) setImmediate(() => { gpioAccessory.emit('initialised') }) } async addMotion (device) { const gpioAccessory = await this.createGpioAccessory(device) if (RpiService.GpioMotion == null) { await import('./RpiService/GpioInput/GpioMotion.js') } gpioAccessory.service = new RpiService.GpioMotion(gpioAccessory, device) gpioAccessory.historyService = new ServiceDelegate.History( gpioAccessory, { motionDelegate: gpioAccessory.service.characteristicDelegate('motion'), lastMotionDelegate: gpioAccessory.service.characteristicDelegate('lastActivation') } ) setImmediate(() => { gpioAccessory.emit('initialised') }) } async addRocker (device) { this.checkGpio(device.gpio) if (this.buttonAccessory == null) { if (RpiAccessory.ButtonAccessory == null) { await import('./RpiAccessory/ButtonAccessory.js') } this.buttonAccessory = new RpiAccessory.ButtonAccessory(this, device) this.gpioAccessories[device.gpio] = this.buttonAccessory } await this.buttonAccessory.addRocker(device) } async addServo (device) { const gpioAccessory = await this.createGpioAccessory(device) if (RpiService.GpioServo == null) { await import('./RpiService/GpioServo.js') } gpioAccessory.service = new RpiService.GpioServo(gpioAccessory, device) setImmediate(() => { gpioAccessory.emit('initialised') }) } async addSmoke (device) { const gpioAccessory = await this.createGpioAccessory(device) if (RpiService.GpioSmoke == null) { await import('./RpiService/GpioInput/GpioSmoke.js') } gpioAccessory.service = new RpiService.GpioSmoke(gpioAccessory, device) setImmediate(() => { gpioAccessory.emit('initialised') }) } async addSwitch (device) { const gpioAccessory = await this.createGpioAccessory(device) if (RpiService.GpioSwitch == null) { await import('./RpiService/GpioOutput/GpioSwitch.js') } gpioAccessory.service = new RpiService.GpioSwitch(gpioAccessory, device) gpioAccessory.historyService = new ServiceDelegate.History( gpioAccessory, { onDelegate: gpioAccessory.service.characteristicDelegate('on'), lastOnDelegate: gpioAccessory.service.characteristicDelegate('lastActivation') } ) setImmediate(() => { gpioAccessory.emit('initialised') }) } async addValve (device) { const gpioAccessory = await this.createGpioAccessory(device) if (RpiService.GpioValve == null) { await import('./RpiService/GpioOutput/GpioValve.js') } gpioAccessory.service = new RpiService.GpioValve(gpioAccessory, device) setImmediate(() => { gpioAccessory.emit('initialised') }) } } export { RpiAccessory }