mirror of
				https://github.com/tsightler/ring-mqtt.git
				synced 2025-10-27 02:30:55 +08:00 
			
		
		
		
	Initial device state framework
This commit is contained in:
		| @@ -22,6 +22,9 @@ class RingPolledDevice extends RingDevice { | ||||
|         }) | ||||
|  | ||||
|         this.monitorHeartbeat() | ||||
|  | ||||
|         // Request saved state for device | ||||
|         utils.event.emit('get_device_state', this.deviceId) | ||||
|     } | ||||
|  | ||||
|     // Publish device discovery, set online, and send all state data | ||||
|   | ||||
| @@ -12,6 +12,7 @@ class RingDevice { | ||||
|         this.isOnline = () => {  | ||||
|             return this.availabilityState === 'online' ? true : false  | ||||
|         } | ||||
|  | ||||
|         this.debug = (message, debugType) => { | ||||
|             utils.debug(debugType === 'disc' ? message : colors.green(`[${this.device.name}] `)+message, debugType ? debugType : 'mqtt') | ||||
|         } | ||||
| @@ -19,10 +20,22 @@ class RingDevice { | ||||
|         this.deviceTopic = `${utils.config.ring_topic}/${this.locationId}/${category}/${this.deviceId}` | ||||
|         this.availabilityTopic = `${this.deviceTopic}/status` | ||||
|  | ||||
|         if (deviceInfo.hasOwnProperty('childDevices')) { | ||||
|             this.childDevices = deviceInfo.childDevices | ||||
|         } | ||||
|  | ||||
|         if (deviceInfo.hasOwnProperty('parentDevices')) { | ||||
|             this.parentDevices = deviceInfo.parentDevices | ||||
|         } | ||||
|  | ||||
|         // Initialize device with saved state data | ||||
|         utils.event.on(`device_state_${this.deviceId}`, (stateData) => { | ||||
|             this.init(stateData) | ||||
|             if (primaryAttribute !== 'disable') { | ||||
|                 this.initAttributeEntities(primaryAttribute) | ||||
|                 this.schedulePublishAttributes() | ||||
|             }     | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     // This function loops through each entity of the device, creates a unique | ||||
| @@ -151,7 +164,7 @@ class RingDevice { | ||||
|                 Object.keys(discoveryMessage).filter(property => property.match('topic')).forEach(topic => { | ||||
|                     this.entity[entityKey][topic] = discoveryMessage[topic] | ||||
|                     if (topic.match('command_topic')) { | ||||
|                         utils.event.emit('mqttSubscribe', discoveryMessage[topic]) | ||||
|                         utils.event.emit('mqtt_subscribe', discoveryMessage[topic]) | ||||
|                         utils.event.on(discoveryMessage[topic], (command, message) => { | ||||
|                             this.processCommand(command, message) | ||||
|                         }) | ||||
| @@ -193,7 +206,7 @@ class RingDevice { | ||||
|         if (debugType !== false) { | ||||
|             this.debug(colors.blue(`${topic} `)+colors.cyan(`${message}`), debugType) | ||||
|         } | ||||
|         utils.event.emit('mqttPublish', topic, message) | ||||
|         utils.event.emit('mqtt_publish', topic, message) | ||||
|     } | ||||
|  | ||||
|     // Set state topic online | ||||
|   | ||||
| @@ -17,6 +17,9 @@ class RingSocketDevice extends RingDevice { | ||||
|         this.device.onData.subscribe((data) => { | ||||
|             if (this.isOnline()) { this.publishState(data) } | ||||
|         }) | ||||
|  | ||||
|         // Request saved state for device | ||||
|         utils.event.emit('get_device_state', this.deviceId) | ||||
|     } | ||||
|  | ||||
|     // Publish device discovery, set online, and send all state data | ||||
|   | ||||
| @@ -4,14 +4,12 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class BaseStation extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm', 'acStatus') | ||||
|     } | ||||
|  | ||||
|     async init() { | ||||
|         this.deviceData.mdl = 'Alarm Base Station' | ||||
|         this.deviceData.name = this.device.location.name + ' Base Station' | ||||
|  | ||||
|         this.initVolumeEntity() | ||||
|     } | ||||
|      | ||||
|     // Check if account has access to control base state volume and initialize topics if so | ||||
|     async initVolumeEntity() { | ||||
|         const origVolume = (this.device.data.volume && !isNaN(this.device.data.volume) ? this.device.data.volume : 0) | ||||
|         const testVolume = (origVolume === 1) ? .99 : origVolume+.01 | ||||
|         this.device.setVolume(testVolume) | ||||
|   | ||||
| @@ -4,28 +4,29 @@ const { RingDeviceType } = require('ring-client-api') | ||||
| class BeamOutdoorPlug extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'lighting') | ||||
|         this.deviceData.mdl = 'Outdoor Smart Plug' | ||||
|  | ||||
|         this.childDevices = { | ||||
|             outlet1: deviceInfo.childDevices.find(d => d.deviceType === RingDeviceType.BeamsSwitch && d.data.relToParentZid === "1"), | ||||
|             outlet2: deviceInfo.childDevices.find(d => d.deviceType === RingDeviceType.BeamsSwitch && d.data.relToParentZid === "2") | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'Outdoor Smart Plug' | ||||
|  | ||||
|         this.outlet1 = this.childDevices.find(d => d.deviceType === RingDeviceType.BeamsSwitch && d.data.relToParentZid === "1"), | ||||
|         this.outlet2 = this.childDevices.find(d => d.deviceType === RingDeviceType.BeamsSwitch && d.data.relToParentZid === "2") | ||||
|          | ||||
|         this.entity.outlet1 = { | ||||
|             component: (this.childDevices.outlet1.data.categoryId === 2) ? 'light' : 'switch', | ||||
|             name: `${this.childDevices.outlet1.name}` | ||||
|             component: (this.outlet1.data.categoryId === 2) ? 'light' : 'switch', | ||||
|             name: `${this.outlet1.name}` | ||||
|         } | ||||
|  | ||||
|         this.entity.outlet2 = { | ||||
|             component: (this.childDevices.outlet2.data.categoryId === 2) ? 'light' : 'switch', | ||||
|             name: `${this.childDevices.outlet2.name}` | ||||
|             component: (this.outlet2.data.categoryId === 2) ? 'light' : 'switch', | ||||
|             name: `${this.outlet2.name}` | ||||
|         } | ||||
|  | ||||
|         this.childDevices.outlet1.onData.subscribe((data) => { | ||||
|         this.outlet1.onData.subscribe((data) => { | ||||
|             if (this.isOnline()) { this.publishOutlet1State() } | ||||
|         }) | ||||
|  | ||||
|         this.childDevices.outlet2.onData.subscribe((data) => { | ||||
|         this.outlet2.onData.subscribe((data) => { | ||||
|             if (this.isOnline()) { this.publishOutlet2State() } | ||||
|         }) | ||||
|     } | ||||
| @@ -37,11 +38,11 @@ class BeamOutdoorPlug extends RingSocketDevice { | ||||
|     } | ||||
|  | ||||
|     publishOutlet1State() { | ||||
|         this.mqttPublish(this.entity.outlet1.state_topic, this.childDevices.outlet1.data.on ? "ON" : "OFF") | ||||
|         this.mqttPublish(this.entity.outlet1.state_topic, this.outlet1.data.on ? "ON" : "OFF") | ||||
|     } | ||||
|  | ||||
|     publishOutlet2State() { | ||||
|         this.mqttPublish(this.entity.outlet2.state_topic, this.childDevices.outlet2.data.on ? "ON" : "OFF") | ||||
|         this.mqttPublish(this.entity.outlet2.state_topic, this.outlet2.data.on ? "ON" : "OFF") | ||||
|     } | ||||
|  | ||||
|     // Process messages from MQTT command topic | ||||
| @@ -73,7 +74,7 @@ class BeamOutdoorPlug extends RingSocketDevice { | ||||
|                 const duration = 32767 | ||||
|                 const on = command === 'on' ? true : false | ||||
|                 const data = on ? { lightMode: 'on', duration } : { lightMode: 'default' } | ||||
|                 this.childDevices[outletId].sendCommand('light-mode.set', data) | ||||
|                 this[outletId].sendCommand('light-mode.set', data) | ||||
|                 break; | ||||
|             } | ||||
|             default: | ||||
|   | ||||
| @@ -3,7 +3,9 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class Beam extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'lighting') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         // Setup device topics based on capabilities. | ||||
|         switch (this.device.data.deviceType) { | ||||
|             case 'group.light-group.beams': | ||||
|   | ||||
| @@ -4,7 +4,9 @@ const { RingDeviceType } = require('ring-client-api') | ||||
| class BinarySensor extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         let device_class = 'None' | ||||
|  | ||||
|         // Override icons and and topics | ||||
|   | ||||
| @@ -3,6 +3,9 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class Bridge extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm', 'commStatus') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'Bridge' | ||||
|         this.deviceData.name = this.device.location.name + ' Bridge' | ||||
|     } | ||||
|   | ||||
| @@ -11,7 +11,9 @@ const rss = require('../lib/rtsp-simple-server') | ||||
| class Camera extends RingPolledDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'camera') | ||||
|     } | ||||
|  | ||||
|     init(stateData) { | ||||
|         this.data = { | ||||
|             motion: { | ||||
|                 active_ding: false, | ||||
| @@ -109,6 +111,27 @@ class Camera extends RingPolledDevice { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (stateData) { | ||||
|             if (utils.config.snapshot_mode.match(/^(interval|all)$/)) { | ||||
|                 if (stateData.hasOwnProperty('snapshot')) { | ||||
|                     this.data.snapshot.autoInterval = stateData.snapshot.hasOwnProperty('autoInterval') | ||||
|                         ? stateData.snapshot.autoInterval | ||||
|                         : this.data.snapshot.autoInterval | ||||
|                     if (!this.data.snapshot.autoInterval) { | ||||
|                         this.data.snapshot.interval = stateData.snapshot.hasOwnProperty('interval')  | ||||
|                             ? stateData.snapshot.interval | ||||
|                             : this.data.snapshot.interval | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (stateData.hasOwnProperty('event_select')) { | ||||
|                 this.data.event_select.state = stateData.event_select.hasOwnProperty('state') | ||||
|                     ? stateData.event_select.state | ||||
|                     : this.data.event_select.state | ||||
|             } | ||||
|         } | ||||
|        | ||||
|         this.entity = { | ||||
|             ...this.entity, | ||||
|             motion: { | ||||
| @@ -178,7 +201,7 @@ class Camera extends RingPolledDevice { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         utils.event.on(`${this.deviceId}_livestream`, (state) => { | ||||
|         utils.event.on(`livestream_${this.deviceId}`, (state) => { | ||||
|             switch (state) { | ||||
|                 case 'active': | ||||
|                     if (this.data.stream.live.status !== 'active') { | ||||
| @@ -207,34 +230,6 @@ class Camera extends RingPolledDevice { | ||||
|             if (this.isOnline()) { this.processDing(ding) } | ||||
|         }) | ||||
|  | ||||
|         utils.event.on(`deviceState_${this.deviceId}`, (stateData) => { | ||||
|             this.initDeviceState(stateData) | ||||
|         }) | ||||
|         utils.event.emit('getDeviceState', this.deviceId) | ||||
|     } | ||||
|  | ||||
|     async initDeviceState(stateData) { | ||||
|         if (stateData) { | ||||
|             if (utils.config.snapshot_mode.match(/^(interval|all)$/)) { | ||||
|                 if (stateData.hasOwnProperty('snapshot')) { | ||||
|                     this.data.snapshot.autoInterval = stateData.snapshot.hasOwnProperty('autoInterval') | ||||
|                         ? stateData.snapshot.autoInterval | ||||
|                         : this.data.snapshot.autoInterval | ||||
|                     if (!this.data.snapshot.autoInterval) { | ||||
|                         this.data.snapshot.interval = stateData.snapshot.hasOwnProperty('interval')  | ||||
|                             ? stateData.snapshot.interval | ||||
|                             : this.data.snapshot.interval | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (stateData.hasOwnProperty('event_select')) { | ||||
|                 this.data.event_select.state = stateData.event_select.hasOwnProperty('state') | ||||
|                     ? stateData.event_select.state | ||||
|                     : this.data.event_select.state | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (this.data.snapshot.interval > 0) { | ||||
|             this.scheduleSnapshotRefresh() | ||||
|         } | ||||
| @@ -249,10 +244,10 @@ class Camera extends RingPolledDevice { | ||||
|                 interval: this.data.snapshot.interval | ||||
|             }, | ||||
|             event_select: { | ||||
|                 state: this.data.snapshot.state | ||||
|                 state: this.data.event_select.state | ||||
|             } | ||||
|         } | ||||
|         utils.event.emit(`saveDeviceState`, this.deviceId, stateData) | ||||
|         utils.event.emit(`save_device_state`, this.deviceId, stateData) | ||||
|     } | ||||
|  | ||||
|     // Build standard and optional entities for device | ||||
|   | ||||
| @@ -4,7 +4,9 @@ const RingPolledDevice = require('./base-polled-device') | ||||
| class Chime extends RingPolledDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'chime') | ||||
|     } | ||||
|  | ||||
|     init(stateData) { | ||||
|         this.data = { | ||||
|             volume: null, | ||||
|             snooze: null, | ||||
| @@ -14,6 +16,12 @@ class Chime extends RingPolledDevice { | ||||
|             play_motion_sound: 'OFF' | ||||
|         } | ||||
|  | ||||
|         if (stateData) { | ||||
|             this.data.snooze_minutes = (stateData.hasOwnProperty('snooze_minutes')) | ||||
|                 ? stateData.snooze_minutes | ||||
|                 : this.data.snooze_minutes | ||||
|         } | ||||
|  | ||||
|         // Define entities for this device | ||||
|         this.entity = { | ||||
|             ...this.entity, | ||||
| @@ -48,6 +56,15 @@ class Chime extends RingPolledDevice { | ||||
|                 value_template: '{{ value_json["lastUpdate"] | default("") }}' | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this.saveDeviceState() | ||||
|     } | ||||
|  | ||||
|     saveDeviceState() { | ||||
|         const stateData = { | ||||
|             snooze_minutes: this.data.snooze_minutes | ||||
|         } | ||||
|         utils.event.emit(`save_device_state`, this.deviceId, stateData) | ||||
|     } | ||||
|  | ||||
|     initAttributeEntities() { | ||||
|   | ||||
| @@ -3,9 +3,12 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class CoAlarm extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'CO Alarm' | ||||
|         this.deviceData.mf = (deviceInfo.hasOwnProperty('parentDevice') && deviceInfo.parentDevice.data && deviceInfo.parentDevice.data.hasOwnProperty('manufacturerName')) | ||||
|             ? deviceInfo.parentDevice.data.manufacturerName  | ||||
|         this.deviceData.mf = (this.hasOwnProperty('parentDevice') && this.parentDevice.hasOwnProperty('data') && this.parentDevice.data.hasOwnProperty('manufacturerName')) | ||||
|             ? this.parentDevice.data.manufacturerName | ||||
|             : 'Ring' | ||||
|  | ||||
|         this.entity.co = { | ||||
|   | ||||
| @@ -4,6 +4,9 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class Fan extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'Fan Control' | ||||
|  | ||||
|         this.entity.fan = { | ||||
|   | ||||
| @@ -3,6 +3,9 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class FloodFreezeSensor extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'Flood & Freeze Sensor' | ||||
|  | ||||
|         this.entity.flood = { | ||||
|   | ||||
| @@ -3,6 +3,9 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class Keypad extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'Security Keypad' | ||||
|  | ||||
|         this.entity.volume = { | ||||
|   | ||||
| @@ -3,6 +3,9 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class Lock extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'Lock' | ||||
|  | ||||
|         this.entity.lock = { | ||||
|   | ||||
| @@ -4,6 +4,9 @@ const RingPolledDevice = require('./base-polled-device') | ||||
| class ModesPanel extends RingPolledDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm', 'disable') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'Mode Control Panel' | ||||
|         this.deviceData.name = `${this.device.location.name} Mode` | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,9 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class MultiLevelSwitch extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'Dimming Light' | ||||
|          | ||||
|         this.entity.light = { | ||||
|   | ||||
| @@ -3,6 +3,9 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class RangeExtender extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm', 'acStatus') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'Z-Wave Range Extender' | ||||
|         this.deviceData.name = this.device.location.name + ' Range Extender' | ||||
|     } | ||||
|   | ||||
| @@ -5,6 +5,9 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class SecurityPanel extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm', 'alarmState') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'Alarm Control Panel' | ||||
|         this.deviceData.name = `${this.device.location.name} Alarm` | ||||
|          | ||||
| @@ -41,7 +44,7 @@ class SecurityPanel extends RingSocketDevice { | ||||
|     } | ||||
|  | ||||
|     publishState() { | ||||
|         var alarmMode | ||||
|         let alarmMode | ||||
|         const alarmInfo = this.device.data.alarmInfo ? this.device.data.alarmInfo : [] | ||||
|  | ||||
|         // If alarm is active report triggered or, if entry-delay, pending | ||||
| @@ -150,7 +153,7 @@ class SecurityPanel extends RingSocketDevice { | ||||
|         while (retries-- > 0 && !(setAlarmSuccess)) { | ||||
|             let bypassDeviceIds = [] | ||||
|  | ||||
|             // If arming bypass arming mode is enabled, get device ids requiring bypass | ||||
|             // If arming bypass mode is enabled, get device ids requiring bypass | ||||
|             if (message.toLowerCase() !== 'disarm' && this.entity.bypass.state) { | ||||
|                 const bypassDevices = (await this.device.location.getDevices()).filter((device) => { | ||||
|                     return ( | ||||
|   | ||||
| @@ -3,7 +3,9 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class Siren extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = (this.device.data.deviceType === 'siren.outdoor-strobe') ? 'Outdoor Siren' : 'Siren'         | ||||
|         this.entity = { | ||||
|             ...this.entity, | ||||
|   | ||||
| @@ -3,6 +3,9 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class SmokeAlarm extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'Smoke Alarm' | ||||
|          | ||||
|         this.entity.smoke = { | ||||
|   | ||||
| @@ -3,6 +3,9 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class SmokeCoListener extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'Smoke & CO Listener' | ||||
|          | ||||
|         this.entity.smoke = { | ||||
|   | ||||
| @@ -3,6 +3,9 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class Switch extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = (this.device.data.categoryId === 2) ? 'Light' : 'Switch' | ||||
|         this.component = (this.device.data.categoryId === 2) ? 'light' : 'switch' | ||||
|          | ||||
|   | ||||
| @@ -3,6 +3,9 @@ const RingSocketDevice = require('./base-socket-device') | ||||
| class TemperatureSensor extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'Temperature Sensor' | ||||
|  | ||||
|         this.entity.temperature = { | ||||
|   | ||||
| @@ -5,12 +5,13 @@ const utils = require( '../lib/utils' ) | ||||
| class Thermostat extends RingSocketDevice { | ||||
|     constructor(deviceInfo) { | ||||
|         super(deviceInfo, 'alarm') | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.deviceData.mdl = 'Thermostat' | ||||
|  | ||||
|         this.childDevices = { | ||||
|             operatingStatus: deviceInfo.childDevices.find(d => d.deviceType === 'thermostat-operating-status'), | ||||
|             temperatureSensor: deviceInfo.childDevices.find(d => d.deviceType === RingDeviceType.TemperatureSensor) | ||||
|         } | ||||
|         this.operatingStatus = this.childDevices.find(d => d.deviceType === 'thermostat-operating-status'), | ||||
|         this.temperatureSensor = this.childDevices.find(d => d.deviceType === RingDeviceType.TemperatureSensor) | ||||
|  | ||||
|         this.entity.thermostat = { | ||||
|             component: 'climate', | ||||
| @@ -28,16 +29,16 @@ class Thermostat extends RingSocketDevice { | ||||
|             setPoint: (() => { | ||||
|                 return this.device.data.setPoint | ||||
|                     ? this.device.data.setPoint | ||||
|                     : this.childDevices.temperatureSensor.data.celsius  | ||||
|                     : this.temperatureSensor.data.celsius  | ||||
|                 }), | ||||
|             operatingMode: (() => {  | ||||
|                 return this.childDevices.operatingStatus.data.operatingMode !== 'off' | ||||
|                     ? `${this.childDevices.operatingStatus.data.operatingMode}ing` | ||||
|                 return this.operatingStatus.data.operatingMode !== 'off' | ||||
|                     ? `${this.operatingStatus.data.operatingMode}ing` | ||||
|                     : this.device.data.mode === 'off' | ||||
|                         ? 'off' | ||||
|                         : this.device.data.fanMode === 'on' ? 'fan' : 'idle'  | ||||
|                 }), | ||||
|             temperature: (() => { return this.childDevices.temperatureSensor.data.celsius }), | ||||
|             temperature: (() => { return this.temperatureSensor.data.celsius }), | ||||
|             ... this.entity.thermostat.modes.includes('auto') | ||||
|                 ? {  | ||||
|                     autoSetPointInProgress: false, | ||||
| @@ -49,14 +50,14 @@ class Thermostat extends RingSocketDevice { | ||||
|                 } : {} | ||||
|         } | ||||
|  | ||||
|         this.childDevices.operatingStatus.onData.subscribe(() => {  | ||||
|         this.operatingStatus.onData.subscribe(() => {  | ||||
|             if (this.isOnline()) {  | ||||
|                 this.publishOperatingMode() | ||||
|                 this.publishAttributes() | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|         this.childDevices.temperatureSensor.onData.subscribe(() => { | ||||
|         this.temperatureSensor.onData.subscribe(() => { | ||||
|             if (this.isOnline()) {  | ||||
|                 this.publishTemperature() | ||||
|                 this.publishAttributes() | ||||
|   | ||||
| @@ -13,7 +13,7 @@ class Main { | ||||
|     constructor() { | ||||
|  | ||||
|         // Start event listeners | ||||
|         utils.event.on('generatedToken', (generatedToken) => { | ||||
|         utils.event.on('generated_token', (generatedToken) => { | ||||
|             this.init(generatedToken) | ||||
|         }) | ||||
|  | ||||
|   | ||||
							
								
								
									
										14
									
								
								lib/mqtt.js
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								lib/mqtt.js
									
									
									
									
									
								
							| @@ -9,7 +9,7 @@ class Mqtt { | ||||
|         this.connected = false | ||||
|  | ||||
|         // Configure event listeners | ||||
|         utils.event.on('ringState', async (state) => { | ||||
|         utils.event.on('ring_state', async (state) => { | ||||
|             if (!this.client && state === 'connected') { | ||||
|                 // Ring API connected, short wait before starting MQTT client | ||||
|                 await utils.sleep(2) | ||||
| @@ -17,13 +17,13 @@ class Mqtt { | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|         utils.event.on('mqttPublish', (topic, message) => { | ||||
|         utils.event.on('mqtt_publish', (topic, message) => { | ||||
|             this.client.publish(topic, (typeof message === 'number') ? message.toString() : message, { qos: 1 }, () => { | ||||
|                 // Just a dummy callback to prevent MQTT memory leak in 4.3.5/4.3.6 | ||||
|             }) | ||||
|         }) | ||||
|  | ||||
|         utils.event.on('mqttSubscribe', (topic) => { | ||||
|         utils.event.on('mqtt_subscribe', (topic) => { | ||||
|             this.client.subscribe(topic) | ||||
|         }) | ||||
|     } | ||||
| @@ -61,7 +61,7 @@ class Mqtt { | ||||
|         this.client.on('connect', () => { | ||||
|             if (!this.connected) { | ||||
|                 this.connected = true | ||||
|                 utils.event.emit('mqttState', 'connected') | ||||
|                 utils.event.emit('mqtt_state', 'connected') | ||||
|             } | ||||
|         }) | ||||
|  | ||||
| @@ -72,20 +72,20 @@ class Mqtt { | ||||
|                 debug('Attempting to reconnect to MQTT broker...') | ||||
|             } | ||||
|             this.connected = false | ||||
|             utils.event.emit('mqttState', 'disconnected') | ||||
|             utils.event.emit('mqtt_state', 'disconnected') | ||||
|         }) | ||||
|  | ||||
|         this.client.on('error', (error) => { | ||||
|             debug('Unable to connect to MQTT broker', error.message) | ||||
|             this.connected = false | ||||
|             utils.event.emit('mqttState', 'disconnected') | ||||
|             utils.event.emit('mqtt_state', 'disconnected') | ||||
|         }) | ||||
|  | ||||
|         // Process MQTT messages from subscribed command topics | ||||
|         this.client.on('message', (topic, message) => { | ||||
|             message = message.toString() | ||||
|             if (topic === utils.config.hass_topic || topic === 'hass/status' || topic === 'hassio/status') { | ||||
|                 utils.event.emit('haStatus', topic, message) | ||||
|                 utils.event.emit('ha_status', topic, message) | ||||
|             } else { | ||||
|                 utils.event.emit(topic, topic.split("/").slice(-2).join("/"), message) | ||||
|             } | ||||
|   | ||||
							
								
								
									
										14
									
								
								lib/ring.js
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								lib/ring.js
									
									
									
									
									
								
							| @@ -35,7 +35,7 @@ class RingMqtt { | ||||
|         this.republishCount = 6 // Republish config/state this many times after startup or HA start/restart | ||||
|  | ||||
|         // Configure event listeners | ||||
|         utils.event.on('mqttState', (state) => { | ||||
|         utils.event.on('mqtt_state', (state) => { | ||||
|             if (state === 'connected') { | ||||
|                 this.mqttConnected = true | ||||
|                 debug('MQTT connection established, processing Ring locations...') | ||||
| @@ -45,7 +45,7 @@ class RingMqtt { | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|         utils.event.on('haStatus', async (topic, message) => { | ||||
|         utils.event.on('ha_status', async (topic, message) => { | ||||
|             debug('Home Assistant state topic '+topic+' received message: '+message) | ||||
|             if (message === 'online') { | ||||
|                 // Republish devices and state if restart of HA is detected | ||||
| @@ -77,7 +77,7 @@ class RingMqtt { | ||||
|             debug(`Attempting connection to Ring API using ${generatedToken ? 'generated' : 'saved'} refresh token...`) | ||||
|             this.client = new RingApi(ringAuth) | ||||
|             await this.client.getProfile() | ||||
|             utils.event.emit('ringState', 'connected') | ||||
|             utils.event.emit('ring_state', 'connected') | ||||
|             debug(`Successfully established connection to Ring API using ${generatedToken ? 'generated' : 'saved'} token`) | ||||
|  | ||||
|             // Subscribe to token update events and save new tokens to state file | ||||
| @@ -145,12 +145,12 @@ class RingMqtt { | ||||
|         // Get all Ring locations | ||||
|         const locations = await this.client.getLocations() | ||||
|  | ||||
|         debug(colors.green('-'.repeat(82))) | ||||
|         debug(colors.green('-'.repeat(90))) | ||||
|         debug(colors.white('This account has access to the following locations:')) | ||||
|         locations.map(function(location) { | ||||
|             debug('           '+colors.green(location.name)+colors.cyan(` (${location.id})`)) | ||||
|         }) | ||||
|         debug(colors.green(' '.repeat(82))) | ||||
|         debug(colors.green(' '.repeat(90))) | ||||
|         debug(colors.brightYellow('IMPORTANT: ')+colors.white('All alarm and smart lighting hubs at these locations *MUST* be online  ')) | ||||
|         debug(colors.white('           or device discovery for that location will hang indefinitely!          ')) | ||||
|         debug(colors.white('           If desired, use the "location_ids" config option to restrict discovery ')) | ||||
| @@ -162,7 +162,7 @@ class RingMqtt { | ||||
|             let chimes = new Array() | ||||
|             const unsupportedDevices = new Array() | ||||
|  | ||||
|             debug(colors.green('-'.repeat(82))) | ||||
|             debug(colors.green('-'.repeat(90))) | ||||
|             // If new location, set custom properties and add to location list | ||||
|             if (this.locations.find(l => l.locationId == location.locationId)) { | ||||
|                 debug(colors.white('Existing location: ')+colors.green(location.name)+colors.cyan(` (${location.id})`)) | ||||
| @@ -238,7 +238,7 @@ class RingMqtt { | ||||
|                 debug(colors.yellow(`  Unsupported device: ${deviceType}`)) | ||||
|             }) | ||||
|         } | ||||
|         debug(colors.green('-'.repeat(82))) | ||||
|         debug(colors.green('-'.repeat(90))) | ||||
|         debug('Ring location/device data updated, sleeping for 5 seconds.') | ||||
|         await utils.sleep(2) | ||||
|         const cameras = await this.devices.filter(d => d.device instanceof RingCamera) | ||||
|   | ||||
| @@ -16,7 +16,7 @@ class RtspSimpleServer { | ||||
|             this.cameras = cameras | ||||
|         } | ||||
|         this.started = true | ||||
|         debug(colors.green('-'.repeat(73))) | ||||
|         debug(colors.green('-'.repeat(90))) | ||||
|         debug('Starting rtsp-simple-server process...') | ||||
|  | ||||
|         this.rssProcess = spawn('rtsp-simple-server', [`${__dirname}/../config/rtsp-simple-server.yml`], { | ||||
| @@ -108,7 +108,7 @@ class RtspSimpleServer { | ||||
|             } | ||||
|         } | ||||
|         await utils.msleep(100) | ||||
|         debug(colors.green('-'.repeat(73))) | ||||
|         debug(colors.green('-'.repeat(90))) | ||||
|     } | ||||
|  | ||||
|     async getPathDetails(path) { | ||||
|   | ||||
							
								
								
									
										21
									
								
								lib/state.js
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								lib/state.js
									
									
									
									
									
								
							| @@ -8,7 +8,7 @@ const writeFileAtomic = require('write-file-atomic') | ||||
| class State { | ||||
|     constructor() { | ||||
|         this.valid = false | ||||
|         this.writeDelay = false | ||||
|         this.writeScheduled = false | ||||
|         this.data = {  | ||||
|             ring_token: '', | ||||
|             systemId: '', | ||||
| @@ -23,16 +23,16 @@ class State { | ||||
|             : this.file = '/data/ring-state.json' | ||||
|         await this.loadStateData() | ||||
|  | ||||
|         utils.event.on(`saveDeviceState`, (deviceId, stateData) => { | ||||
|         utils.event.on(`save_device_state`, (deviceId, stateData) => { | ||||
|             this.data.devices[deviceId] = stateData | ||||
|             this.updateStateFile() | ||||
|         }) | ||||
|  | ||||
|         utils.event.on('getDeviceState', (deviceId) => { | ||||
|         utils.event.on('get_device_state', (deviceId) => { | ||||
|             if (this.data.devices.hasOwnProperty(deviceId)) { | ||||
|                 utils.event.emit(`deviceState_${deviceId}`, this.data.devices[deviceId]) | ||||
|                 utils.event.emit(`device_state_${deviceId}`, this.data.devices[deviceId]) | ||||
|             } else { | ||||
|                 utils.event.emit(`deviceState_${deviceId}`, false) | ||||
|                 utils.event.emit(`device_state_${deviceId}`, false) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| @@ -79,9 +79,13 @@ class State { | ||||
|     } | ||||
|  | ||||
|     async updateStateFile() { | ||||
|         if (!this.writeDelay) { | ||||
|             this.writeDelay = true | ||||
|             await utils.sleep(10) | ||||
|         // The writeDelay flag is a hack to keep from writing too often when there are burst | ||||
|         // of state updates such as during startup. If a state file update is already scheduled | ||||
|         // then calls to this function are skipped. | ||||
|         if (!this.writeScheduled) { | ||||
|             this.writeScheduled = true | ||||
|             await utils.sleep(5) | ||||
|             this.writeScheduled = false | ||||
|             try { | ||||
|                 await writeFileAtomic(this.file, JSON.stringify(this.data)) | ||||
|                 debug('Successfully saved updated refresh token in state file: '+this.file) | ||||
| @@ -89,7 +93,6 @@ class State { | ||||
|                 debug(colors.red('Failed to save updated refresh token in state file: '+this.file)) | ||||
|                 debug(err.message) | ||||
|             } | ||||
|             this.writeDelay = false | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -49,15 +49,15 @@ class StreamWorkers { | ||||
|                 if (workerId >= 0) { | ||||
|                     switch (data.state) { | ||||
|                         case 'active': | ||||
|                             utils.event.emit(`${deviceId}_livestream`, 'active') | ||||
|                             utils.event.emit(`livestream_${deviceId}`, 'active') | ||||
|                             this.streamWorkers[workerId].sessions[deviceId].streamData.sessionId = data.liveCallData.sessionId | ||||
|                             break; | ||||
|                         case 'inactive': | ||||
|                             utils.event.emit(`${deviceId}_livestream`, 'inactive') | ||||
|                             utils.event.emit(`livestream_${deviceId}`, 'inactive') | ||||
|                             delete this.streamWorkers[workerId].sessions[deviceId] | ||||
|                             break; | ||||
|                         case 'failed': | ||||
|                             utils.event.emit(`${deviceId}_livestream`, 'failed') | ||||
|                             utils.event.emit(`livestream_${deviceId}`, 'failed') | ||||
|                             delete this.streamWorkers[workerId].sessions[deviceId] | ||||
|                             break; | ||||
|                     } | ||||
|   | ||||
| @@ -14,7 +14,7 @@ class TokenApp { | ||||
|             this.start() | ||||
|         } | ||||
|  | ||||
|         utils.event.on('ringState', async (state) => { | ||||
|         utils.event.on('ring_state', async (state) => { | ||||
|             if (state === 'connected') { | ||||
|                 this.ringConnected = true | ||||
|  | ||||
| @@ -87,7 +87,7 @@ class TokenApp { | ||||
|             } | ||||
|             if (generatedToken) { | ||||
|                 res.sendFile('restart.html', {root: webdir}) | ||||
|                 utils.event.emit('generatedToken', generatedToken.refresh_token) | ||||
|                 utils.event.emit('generated_token', generatedToken.refresh_token) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 tsightler
					tsightler