Standardize multi-device discovery logic

This commit is contained in:
tsightler
2022-02-03 20:21:04 -05:00
parent 2d2dce2ad9
commit 23af855094
27 changed files with 76 additions and 87 deletions

View File

@@ -3,8 +3,8 @@ const RingDevice = require('./base-ring-device')
// Base class for devices/features that communicate via HTTP polling interface (cameras/chime/modes)
class RingPolledDevice extends RingDevice {
constructor(deviceInfo, primaryAttribute) {
super(deviceInfo, deviceInfo.device.data.device_id, deviceInfo.device.data.location_id, primaryAttribute)
constructor(deviceInfo, category, primaryAttribute) {
super(deviceInfo, category, primaryAttribute, deviceInfo.device.data.device_id, deviceInfo.device.data.location_id)
this.heartbeat = 3
// Sevice data for Home Assistant device registry

View File

@@ -8,7 +8,7 @@ const colors = require('colors/safe')
// Base class with functions common to all devices
class RingDevice {
constructor(deviceInfo, deviceId, locationId, primaryAttribute) {
constructor(deviceInfo, category, primaryAttribute, deviceId, locationId) {
this.device = deviceInfo.device
this.mqttClient = deviceInfo.mqttClient
this.config = deviceInfo.CONFIG
@@ -24,7 +24,7 @@ class RingDevice {
debug[debugType](colors.green(`[${this.deviceData.name}] `)+message)
}
// Build device base and availability topic
this.deviceTopic = `${this.config.ring_topic}/${this.locationId}/${deviceInfo.category}/${this.deviceId}`
this.deviceTopic = `${this.config.ring_topic}/${this.locationId}/${category}/${this.deviceId}`
this.availabilityTopic = `${this.deviceTopic}/status`
if (primaryAttribute !== 'disable') {

View File

@@ -3,8 +3,8 @@ const RingDevice = require('./base-ring-device')
// Base class for devices that communicate with hubs via websocket (alarm/smart lighting)
class RingSocketDevice extends RingDevice {
constructor(deviceInfo, primaryAttribute) {
super(deviceInfo, deviceInfo.device.id, deviceInfo.device.location.locationId, primaryAttribute)
constructor(deviceInfo, category, primaryAttribute) {
super(deviceInfo, category, primaryAttribute, deviceInfo.device.id, deviceInfo.device.location.locationId)
// Set default device data for Home Assistant device registry
// Values may be overridden by individual devices

View File

@@ -3,7 +3,7 @@ const RingSocketDevice = require('./base-socket-device')
class BaseStation extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo, 'acStatus')
super(deviceInfo, 'alarm','acStatus')
this.deviceData.mdl = 'Alarm Base Station'
this.deviceData.name = this.device.location.name + ' Base Station'

View File

@@ -1,28 +1,31 @@
const RingSocketDevice = require('./base-socket-device')
const { RingDeviceType } = require('ring-client-api')
class BeamOutdoorPlug extends RingSocketDevice {
constructor(deviceInfo, childDevices) {
super(deviceInfo)
constructor(deviceInfo, allDevices) {
super(deviceInfo, 'lighting')
this.deviceData.mdl = 'Outdoor Smart Plug'
this.outlet1 = childDevices.find(d => d.data.relToParentZid === "1")
this.outlet2 = childDevices.find(d => d.data.relToParentZid === "2")
this.childDevices = {
outlet1: allDevices.find(d => d.data.parentZid === this.device.id && d.deviceType === RingDeviceType.BeamsSwitch && d.data.relToParentZid === "1"),
outlet2: allDevices.find(d => d.data.parentZid === this.device.id && d.deviceType === RingDeviceType.BeamsSwitch && d.data.relToParentZid === "2")
}
this.entity.outlet1 = {
component: (this.outlet1.data.categoryId === 2) ? 'light' : 'switch',
name: `${this.outlet1.name}`,
component: (this.childDevices.outlet1.data.categoryId === 2) ? 'light' : 'switch',
name: `${this.childDevices.outlet1.name}`
}
this.entity.outlet2 = {
component: (this.outlet1.data.categoryId === 2) ? 'light' : 'switch',
name: `${this.outlet2.name}`,
component: (this.childDevices.outlet2.data.categoryId === 2) ? 'light' : 'switch',
name: `${this.childDevices.outlet2.name}`
}
this.outlet1.onData.subscribe((data) => {
this.childDevices.outlet1.onData.subscribe((data) => {
if (this.isOnline()) { this.publishOutlet1State() }
})
this.outlet2.onData.subscribe((data) => {
this.childDevices.outlet2.onData.subscribe((data) => {
if (this.isOnline()) { this.publishOutlet2State() }
})
}
@@ -34,11 +37,11 @@ class BeamOutdoorPlug extends RingSocketDevice {
}
publishOutlet1State() {
this.publishMqtt(this.entity.outlet1.state_topic, this.outlet1.data.on ? "ON" : "OFF")
this.publishMqtt(this.entity.outlet1.state_topic, this.childDevices.outlet1.data.on ? "ON" : "OFF")
}
publishOutlet2State() {
this.publishMqtt(this.entity.outlet2.state_topic, this.outlet2.data.on ? "ON" : "OFF")
this.publishMqtt(this.entity.outlet2.state_topic, this.childDevices.outlet2.data.on ? "ON" : "OFF")
}
// Process messages from MQTT command topic
@@ -70,7 +73,7 @@ class BeamOutdoorPlug extends RingSocketDevice {
const duration = 32767
const on = command === 'on' ? true : false
const data = on ? { lightMode: 'on', duration } : { lightMode: 'default' }
this[outletId].sendCommand('light-mode.set', data)
this.childDevices[outletId].sendCommand('light-mode.set', data)
break;
}
default:

View File

@@ -2,7 +2,7 @@ const RingSocketDevice = require('./base-socket-device')
class Beam extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'lighting')
// Setup device topics based on capabilities.
switch (this.device.data.deviceType) {

View File

@@ -2,7 +2,7 @@ const RingSocketDevice = require('./base-socket-device')
class Bridge extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo, 'commStatus')
super(deviceInfo, 'alarm', 'commStatus')
this.deviceData.mdl = 'Bridge'
this.deviceData.name = this.device.location.name + ' Bridge'
}

View File

@@ -11,7 +11,7 @@ const rss = require('../lib/rtsp-simple-server')
class Camera extends RingPolledDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'camera')
this.data = {
motion: {

View File

@@ -3,7 +3,7 @@ const RingPolledDevice = require('./base-polled-device')
class Chime extends RingPolledDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'chime')
this.data = {
volume: null,

View File

@@ -1,9 +1,11 @@
const RingSocketDevice = require('./base-socket-device')
const { RingDeviceType } = require('ring-client-api')
class CoAlarm extends RingSocketDevice {
constructor(deviceInfo, parentDevice) {
super(deviceInfo)
constructor(deviceInfo, allDevices) {
super(deviceInfo, 'alarm')
this.deviceData.mdl = 'CO Alarm'
const parentDevice = allDevices.find(d => d.id === this.device.data.parentZid && d.deviceType === RingDeviceType.SmokeAlarm)
this.deviceData.mf = (parentDevice && parentDevice.data && parentDevice.data.manufacturerName)
? parentDevice.data.manufacturerName
: 'Ring'

View File

@@ -2,7 +2,7 @@ const RingSocketDevice = require('./base-socket-device')
class ContactSensor extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'alarm')
let device_class = 'None'

View File

@@ -3,7 +3,7 @@ const RingSocketDevice = require('./base-socket-device')
class Fan extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'alarm')
this.deviceData.mdl = 'Fan Control'
this.entity.fan = {

View File

@@ -2,7 +2,7 @@ const RingSocketDevice = require('./base-socket-device')
class FloodFreezeSensor extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'alarm')
this.deviceData.mdl = 'Flood & Freeze Sensor'
this.entity.flood = {

View File

@@ -2,7 +2,7 @@ const RingSocketDevice = require('./base-socket-device')
class Keypad extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'alarm')
this.deviceData.mdl = 'Security Keypad'
this.entity.volume = {

View File

@@ -2,7 +2,7 @@ const RingSocketDevice = require('./base-socket-device')
class Lock extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'alarm')
this.deviceData.mdl = 'Lock'
this.entity.lock = {

View File

@@ -3,7 +3,7 @@ const RingPolledDevice = require('./base-polled-device')
class ModesPanel extends RingPolledDevice {
constructor(deviceInfo) {
super(deviceInfo, 'disable')
super(deviceInfo, 'alarm', 'disable')
this.deviceData.mdl = 'Mode Control Panel'
this.deviceData.name = `${this.device.location.name} Mode`

View File

@@ -2,7 +2,7 @@ const RingSocketDevice = require('./base-socket-device')
class MotionSensor extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'alarm')
this.deviceData.mdl = 'Motion Sensor'
this.entity.motion = {

View File

@@ -2,7 +2,7 @@ const RingSocketDevice = require('./base-socket-device')
class MultiLevelSwitch extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'alarm')
this.deviceData.mdl = 'Dimming Light'
this.entity.light = {

View File

@@ -2,7 +2,7 @@ const RingSocketDevice = require('./base-socket-device')
class RangeExtender extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo, 'acStatus')
super(deviceInfo, 'alarm', 'acStatus')
this.deviceData.mdl = 'Z-Wave Range Extender'
this.deviceData.name = this.device.location.name + ' Range Extender'
}

View File

@@ -4,7 +4,7 @@ const RingSocketDevice = require('./base-socket-device')
class SecurityPanel extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo, 'alarmState')
super(deviceInfo, 'alarm', 'alarmState')
this.deviceData.mdl = 'Alarm Control Panel'
this.deviceData.name = `${this.device.location.name} Alarm`

View File

@@ -2,7 +2,7 @@ const RingSocketDevice = require('./base-socket-device')
class Siren extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'alarm')
if (this.device.data.deviceType === 'siren.outdoor-strobe') {
this.deviceData.mdl = 'Outdoor Siren'

View File

@@ -2,7 +2,7 @@ const RingSocketDevice = require('./base-socket-device')
class SmokeAlarm extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'alarm')
this.deviceData.mdl = 'Smoke Alarm'
this.entity.smoke = {

View File

@@ -2,7 +2,7 @@ const RingSocketDevice = require('./base-socket-device')
class SmokeCoListener extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'alarm')
this.deviceData.mdl = 'Smoke & CO Listener'
this.entity.smoke = {

View File

@@ -2,7 +2,7 @@ const RingSocketDevice = require('./base-socket-device')
class Switch extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'alarm')
this.deviceData.mdl = (this.device.data.categoryId === 2) ? 'Light' : 'Switch'
this.component = (this.device.data.categoryId === 2) ? 'light' : 'switch'

View File

@@ -2,7 +2,7 @@ const RingSocketDevice = require('./base-socket-device')
class TemperatureSensor extends RingSocketDevice {
constructor(deviceInfo) {
super(deviceInfo)
super(deviceInfo, 'alarm')
this.deviceData.mdl = 'Temperature Sensor'
this.entity.temperature = {

View File

@@ -1,11 +1,15 @@
const RingSocketDevice = require('./base-socket-device')
const { RingDeviceType } = require('ring-client-api')
class Thermostat extends RingSocketDevice {
constructor(deviceInfo, operatingStatus, temperatureSensor) {
super(deviceInfo)
constructor(deviceInfo, allDevices) {
super(deviceInfo, 'alarm')
this.deviceData.mdl = 'Thermostat'
this.operatingStatus = operatingStatus
this.temperatureSensor = temperatureSensor
this.childDevices = {
operatingStatus: allDevices.find(d => d.data.parentZid === this.device.id && d.deviceType === 'thermostat-operating-status'),
temperatureSensor: allDevices.find(d => d.data.parentZid === this.device.id && d.deviceType === RingDeviceType.TemperatureSensor)
}
this.entity.thermostat = {
component: 'climate',
@@ -21,26 +25,26 @@ class Thermostat extends RingSocketDevice {
setPoint: (() => {
return this.device.data.setPoint
? this.device.data.setPoint.toString()
: this.temperatureSensor.data.celsius.toString()
: this.childDevices.temperatureSensor.data.celsius.toString()
}),
operatingMode: (() => {
return this.operatingStatus.data.operatingMode !== 'off'
? `${this.operatingStatus.data.operatingMode}ing`
return this.childDevices.operatingStatus.data.operatingMode !== 'off'
? `${this.childDevices.operatingStatus.data.operatingMode}ing`
: this.device.data.mode === 'off'
? 'off'
: this.device.data.fanMode === 'on' ? 'fan' : 'idle'
}),
temperature: (() => { return this.temperatureSensor.data.celsius.toString() })
temperature: (() => { return this.childDevices.temperatureSensor.data.celsius.toString() })
}
this.operatingStatus.onData.subscribe(() => {
this.childDevices.operatingStatus.onData.subscribe(() => {
if (this.isOnline()) {
this.publishOperatingMode()
this.publishAttributes()
}
})
this.temperatureSensor.onData.subscribe(() => {
this.childDevices.temperatureSensor.onData.subscribe(() => {
if (this.isOnline()) {
this.publishTemperature()
this.publishAttributes()

View File

@@ -36,6 +36,7 @@ const Switch = require('./devices/switch')
const TemperatureSensor = require('./devices/temperature-sensor')
const Thermostat = require('./devices/thermostat')
const { debugPort } = require('process')
const e = require('express')
var CONFIG
var ringLocations = new Array()
@@ -92,15 +93,12 @@ async function processExit(exitCode) {
async function getDevice(device, mqttClient, allDevices) {
const deviceInfo = {
device: device,
category: 'alarm',
mqttClient: mqttClient,
CONFIG
}
if (device instanceof RingCamera) {
deviceInfo.category = 'camera'
return new Camera(deviceInfo)
} else if (device instanceof RingChime) {
deviceInfo.category = 'chime'
return new Chime(deviceInfo)
} else if (/^lock($|\.)/.test(device.deviceType)) {
return new Lock(deviceInfo)
@@ -119,25 +117,18 @@ async function getDevice(device, mqttClient, allDevices) {
case RingDeviceType.SmokeAlarm:
return new SmokeAlarm(deviceInfo)
case RingDeviceType.CoAlarm:
// If this is a child device pass in parent device as well
const parentDevice = allDevices.find(d => d.id === device.data.parentZid && d.deviceType === RingDeviceType.SmokeAlarm)
return new CoAlarm(deviceInfo, parentDevice)
return new CoAlarm(deviceInfo, allDevices)
case RingDeviceType.SmokeCoListener:
return new SmokeCoListener(deviceInfo)
case RingDeviceType.BeamsMotionSensor:
case RingDeviceType.BeamsMultiLevelSwitch:
case RingDeviceType.BeamsTransformerSwitch:
case RingDeviceType.BeamsLightGroupSwitch:
deviceInfo.category = 'lighting'
return new Beam(deviceInfo)
case RingDeviceType.BeamsDevice:
deviceInfo.category = 'lighting'
const childDevices = allDevices.filter(d => d.data.parentZid === device.id && d.deviceType === RingDeviceType.BeamsSwitch)
return new BeamOutdoorPlug(deviceInfo, childDevices)
return new BeamOutdoorPlug(deviceInfo, allDevices)
case RingDeviceType.MultiLevelSwitch:
return newDevice = (device.categoryId === 17)
? new Fan(deviceInfo)
: new MultiLevelSwitch(deviceInfo)
return newDevice = (device.categoryId === 17) ? new Fan(deviceInfo) : new MultiLevelSwitch(deviceInfo)
case RingDeviceType.Switch:
return new Switch(deviceInfo)
case RingDeviceType.Keypad:
@@ -158,11 +149,7 @@ async function getDevice(device, mqttClient, allDevices) {
case 'siren.outdoor-strobe':
return new Siren(deviceInfo)
case RingDeviceType.Thermostat:
const operatingStatus = allDevices.find(d => d.data.parentZid === device.id && d.deviceType === 'thermostat-operating-status')
const temperatureSensor = allDevices.find(d => d.data.parentZid === device.id && d.deviceType === RingDeviceType.TemperatureSensor)
if (operatingStatus && temperatureSensor) {
return new Thermostat(deviceInfo, operatingStatus, temperatureSensor)
}
return new Thermostat(deviceInfo, allDevices)
case RingDeviceType.TemperatureSensor:
// If this is a thermostat component, ignore this device
if (allDevices.find(d => d.id === device.data.parentZid && d.deviceType === RingDeviceType.Thermostat)) {
@@ -251,25 +238,18 @@ async function updateRingData(mqttClient, ringClient) {
}
if (ringDevice) {
debug(colors.white(foundMessage)+colors.green(`${ringDevice.deviceData.name}`)+colors.cyan(' ('+ringDevice.deviceId+')'))
const indent = ' '.repeat(foundMessage.length-4)
switch (ringDevice.device.deviceType) {
case RingDeviceType.Thermostat:
debug(colors.white(`${indent}`)+colors.gray(ringDevice.device.deviceType))
debug(colors.white(`${indent}├─: `)+colors.green('Operating Status')+colors.cyan(` (${ringDevice.operatingStatus.id})`))
debug(colors.white(`${indent}`)+colors.gray(ringDevice.operatingStatus.deviceType))
debug(colors.white(`${indent}─: `)+colors.green('Temperature Sensor')+colors.cyan(` (${ringDevice.temperatureSensor.id})`))
debug(colors.gray(`${indent} `+ringDevice.temperatureSensor.deviceType))
break
case RingDeviceType.BeamsDevice:
debug(colors.white(`${indent}`)+colors.gray(ringDevice.device.deviceType))
debug(colors.white(`${indent}├─: `)+colors.green('Outlet A')+colors.cyan(` (${ringDevice.outlet1.id})`))
debug(colors.white(`${indent}`)+colors.gray(ringDevice.outlet1.deviceType))
debug(colors.white(`${indent}└─: `)+colors.green('Outlet B')+colors.cyan(` (${ringDevice.outlet2.id})`))
debug(colors.gray(`${indent} `+ringDevice.outlet2.deviceType))
break
default:
debug(colors.gray(`${indent}${ringDevice.device.deviceType}`))
debug(colors.white(foundMessage)+colors.green(`${ringDevice.deviceData.name}`)+colors.cyan(' ('+ringDevice.deviceId+')'))
if (ringDevice.hasOwnProperty('childDevices')) {
debug(colors.white(`${indent}`)+colors.gray(ringDevice.device.deviceType))
let keys = Object.keys(ringDevice.childDevices).length
Object.keys(ringDevice.childDevices).forEach(childDevice => {
debug(colors.white(`${indent}─: `)+colors.green(`${childDevice.name}`)+colors.cyan(` (${childDevice.id})`))
debug(colors.white(`${indent}${(keys > 1) ? '│ ' : ' '}`)+colors.gray(childDevice.deviceType))
keys--
})
} else {
debug(colors.gray(`${indent}${ringDevice.device.deviceType}`))
}
}
}