Initial device state framework

This commit is contained in:
tsightler
2022-04-03 12:56:38 -04:00
parent 5765b60537
commit c9478d4a94
32 changed files with 181 additions and 99 deletions

View File

@@ -22,6 +22,9 @@ class RingPolledDevice extends RingDevice {
}) })
this.monitorHeartbeat() 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 // Publish device discovery, set online, and send all state data

View File

@@ -12,6 +12,7 @@ class RingDevice {
this.isOnline = () => { this.isOnline = () => {
return this.availabilityState === 'online' ? true : false return this.availabilityState === 'online' ? true : false
} }
this.debug = (message, debugType) => { this.debug = (message, debugType) => {
utils.debug(debugType === 'disc' ? message : colors.green(`[${this.device.name}] `)+message, debugType ? debugType : 'mqtt') 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.deviceTopic = `${utils.config.ring_topic}/${this.locationId}/${category}/${this.deviceId}`
this.availabilityTopic = `${this.deviceTopic}/status` this.availabilityTopic = `${this.deviceTopic}/status`
if (primaryAttribute !== 'disable') { if (deviceInfo.hasOwnProperty('childDevices')) {
this.initAttributeEntities(primaryAttribute) this.childDevices = deviceInfo.childDevices
this.schedulePublishAttributes()
} }
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 // 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 => { Object.keys(discoveryMessage).filter(property => property.match('topic')).forEach(topic => {
this.entity[entityKey][topic] = discoveryMessage[topic] this.entity[entityKey][topic] = discoveryMessage[topic]
if (topic.match('command_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) => { utils.event.on(discoveryMessage[topic], (command, message) => {
this.processCommand(command, message) this.processCommand(command, message)
}) })
@@ -193,7 +206,7 @@ class RingDevice {
if (debugType !== false) { if (debugType !== false) {
this.debug(colors.blue(`${topic} `)+colors.cyan(`${message}`), debugType) 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 // Set state topic online

View File

@@ -17,6 +17,9 @@ class RingSocketDevice extends RingDevice {
this.device.onData.subscribe((data) => { this.device.onData.subscribe((data) => {
if (this.isOnline()) { this.publishState(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 // Publish device discovery, set online, and send all state data

View File

@@ -4,14 +4,12 @@ const RingSocketDevice = require('./base-socket-device')
class BaseStation extends RingSocketDevice { class BaseStation extends RingSocketDevice {
constructor(deviceInfo) { constructor(deviceInfo) {
super(deviceInfo, 'alarm', 'acStatus') super(deviceInfo, 'alarm', 'acStatus')
}
async init() {
this.deviceData.mdl = 'Alarm Base Station' this.deviceData.mdl = 'Alarm Base Station'
this.deviceData.name = this.device.location.name + ' 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 origVolume = (this.device.data.volume && !isNaN(this.device.data.volume) ? this.device.data.volume : 0)
const testVolume = (origVolume === 1) ? .99 : origVolume+.01 const testVolume = (origVolume === 1) ? .99 : origVolume+.01
this.device.setVolume(testVolume) this.device.setVolume(testVolume)

View File

@@ -4,28 +4,29 @@ const { RingDeviceType } = require('ring-client-api')
class BeamOutdoorPlug extends RingSocketDevice { class BeamOutdoorPlug extends RingSocketDevice {
constructor(deviceInfo) { constructor(deviceInfo) {
super(deviceInfo, 'lighting') super(deviceInfo, 'lighting')
}
init() {
this.deviceData.mdl = 'Outdoor Smart Plug' this.deviceData.mdl = 'Outdoor Smart Plug'
this.childDevices = { this.outlet1 = this.childDevices.find(d => d.deviceType === RingDeviceType.BeamsSwitch && d.data.relToParentZid === "1"),
outlet1: deviceInfo.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")
outlet2: deviceInfo.childDevices.find(d => d.deviceType === RingDeviceType.BeamsSwitch && d.data.relToParentZid === "2")
}
this.entity.outlet1 = { this.entity.outlet1 = {
component: (this.childDevices.outlet1.data.categoryId === 2) ? 'light' : 'switch', component: (this.outlet1.data.categoryId === 2) ? 'light' : 'switch',
name: `${this.childDevices.outlet1.name}` name: `${this.outlet1.name}`
} }
this.entity.outlet2 = { this.entity.outlet2 = {
component: (this.childDevices.outlet2.data.categoryId === 2) ? 'light' : 'switch', component: (this.outlet2.data.categoryId === 2) ? 'light' : 'switch',
name: `${this.childDevices.outlet2.name}` name: `${this.outlet2.name}`
} }
this.childDevices.outlet1.onData.subscribe((data) => { this.outlet1.onData.subscribe((data) => {
if (this.isOnline()) { this.publishOutlet1State() } if (this.isOnline()) { this.publishOutlet1State() }
}) })
this.childDevices.outlet2.onData.subscribe((data) => { this.outlet2.onData.subscribe((data) => {
if (this.isOnline()) { this.publishOutlet2State() } if (this.isOnline()) { this.publishOutlet2State() }
}) })
} }
@@ -37,11 +38,11 @@ class BeamOutdoorPlug extends RingSocketDevice {
} }
publishOutlet1State() { 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() { 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 // Process messages from MQTT command topic
@@ -73,7 +74,7 @@ class BeamOutdoorPlug extends RingSocketDevice {
const duration = 32767 const duration = 32767
const on = command === 'on' ? true : false const on = command === 'on' ? true : false
const data = on ? { lightMode: 'on', duration } : { lightMode: 'default' } const data = on ? { lightMode: 'on', duration } : { lightMode: 'default' }
this.childDevices[outletId].sendCommand('light-mode.set', data) this[outletId].sendCommand('light-mode.set', data)
break; break;
} }
default: default:

View File

@@ -3,7 +3,9 @@ const RingSocketDevice = require('./base-socket-device')
class Beam extends RingSocketDevice { class Beam extends RingSocketDevice {
constructor(deviceInfo) { constructor(deviceInfo) {
super(deviceInfo, 'lighting') super(deviceInfo, 'lighting')
}
init() {
// Setup device topics based on capabilities. // Setup device topics based on capabilities.
switch (this.device.data.deviceType) { switch (this.device.data.deviceType) {
case 'group.light-group.beams': case 'group.light-group.beams':

View File

@@ -4,7 +4,9 @@ const { RingDeviceType } = require('ring-client-api')
class BinarySensor extends RingSocketDevice { class BinarySensor extends RingSocketDevice {
constructor(deviceInfo) { constructor(deviceInfo) {
super(deviceInfo, 'alarm') super(deviceInfo, 'alarm')
}
init() {
let device_class = 'None' let device_class = 'None'
// Override icons and and topics // Override icons and and topics

View File

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

View File

@@ -11,7 +11,9 @@ const rss = require('../lib/rtsp-simple-server')
class Camera extends RingPolledDevice { class Camera extends RingPolledDevice {
constructor(deviceInfo) { constructor(deviceInfo) {
super(deviceInfo, 'camera') super(deviceInfo, 'camera')
}
init(stateData) {
this.data = { this.data = {
motion: { motion: {
active_ding: false, active_ding: false,
@@ -108,6 +110,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 = {
...this.entity, ...this.entity,
@@ -178,7 +201,7 @@ class Camera extends RingPolledDevice {
} }
} }
utils.event.on(`${this.deviceId}_livestream`, (state) => { utils.event.on(`livestream_${this.deviceId}`, (state) => {
switch (state) { switch (state) {
case 'active': case 'active':
if (this.data.stream.live.status !== 'active') { if (this.data.stream.live.status !== 'active') {
@@ -207,34 +230,6 @@ class Camera extends RingPolledDevice {
if (this.isOnline()) { this.processDing(ding) } 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) { if (this.data.snapshot.interval > 0) {
this.scheduleSnapshotRefresh() this.scheduleSnapshotRefresh()
} }
@@ -249,10 +244,10 @@ class Camera extends RingPolledDevice {
interval: this.data.snapshot.interval interval: this.data.snapshot.interval
}, },
event_select: { 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 // Build standard and optional entities for device

View File

@@ -4,7 +4,9 @@ const RingPolledDevice = require('./base-polled-device')
class Chime extends RingPolledDevice { class Chime extends RingPolledDevice {
constructor(deviceInfo) { constructor(deviceInfo) {
super(deviceInfo, 'chime') super(deviceInfo, 'chime')
}
init(stateData) {
this.data = { this.data = {
volume: null, volume: null,
snooze: null, snooze: null,
@@ -14,6 +16,12 @@ class Chime extends RingPolledDevice {
play_motion_sound: 'OFF' 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 // Define entities for this device
this.entity = { this.entity = {
...this.entity, ...this.entity,
@@ -48,6 +56,15 @@ class Chime extends RingPolledDevice {
value_template: '{{ value_json["lastUpdate"] | default("") }}' 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() { initAttributeEntities() {

View File

@@ -3,9 +3,12 @@ const RingSocketDevice = require('./base-socket-device')
class CoAlarm extends RingSocketDevice { class CoAlarm extends RingSocketDevice {
constructor(deviceInfo) { constructor(deviceInfo) {
super(deviceInfo, 'alarm') super(deviceInfo, 'alarm')
}
init() {
this.deviceData.mdl = 'CO Alarm' this.deviceData.mdl = 'CO Alarm'
this.deviceData.mf = (deviceInfo.hasOwnProperty('parentDevice') && deviceInfo.parentDevice.data && deviceInfo.parentDevice.data.hasOwnProperty('manufacturerName')) this.deviceData.mf = (this.hasOwnProperty('parentDevice') && this.parentDevice.hasOwnProperty('data') && this.parentDevice.data.hasOwnProperty('manufacturerName'))
? deviceInfo.parentDevice.data.manufacturerName ? this.parentDevice.data.manufacturerName
: 'Ring' : 'Ring'
this.entity.co = { this.entity.co = {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,9 @@ const RingSocketDevice = require('./base-socket-device')
class SecurityPanel extends RingSocketDevice { class SecurityPanel extends RingSocketDevice {
constructor(deviceInfo) { constructor(deviceInfo) {
super(deviceInfo, 'alarm', 'alarmState') super(deviceInfo, 'alarm', 'alarmState')
}
init() {
this.deviceData.mdl = 'Alarm Control Panel' this.deviceData.mdl = 'Alarm Control Panel'
this.deviceData.name = `${this.device.location.name} Alarm` this.deviceData.name = `${this.device.location.name} Alarm`
@@ -41,7 +44,7 @@ class SecurityPanel extends RingSocketDevice {
} }
publishState() { publishState() {
var alarmMode let alarmMode
const alarmInfo = this.device.data.alarmInfo ? this.device.data.alarmInfo : [] const alarmInfo = this.device.data.alarmInfo ? this.device.data.alarmInfo : []
// If alarm is active report triggered or, if entry-delay, pending // If alarm is active report triggered or, if entry-delay, pending
@@ -150,7 +153,7 @@ class SecurityPanel extends RingSocketDevice {
while (retries-- > 0 && !(setAlarmSuccess)) { while (retries-- > 0 && !(setAlarmSuccess)) {
let bypassDeviceIds = [] 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) { if (message.toLowerCase() !== 'disarm' && this.entity.bypass.state) {
const bypassDevices = (await this.device.location.getDevices()).filter((device) => { const bypassDevices = (await this.device.location.getDevices()).filter((device) => {
return ( return (

View File

@@ -3,7 +3,9 @@ const RingSocketDevice = require('./base-socket-device')
class Siren extends RingSocketDevice { class Siren extends RingSocketDevice {
constructor(deviceInfo) { constructor(deviceInfo) {
super(deviceInfo, 'alarm') super(deviceInfo, 'alarm')
}
init() {
this.deviceData.mdl = (this.device.data.deviceType === 'siren.outdoor-strobe') ? 'Outdoor Siren' : 'Siren' this.deviceData.mdl = (this.device.data.deviceType === 'siren.outdoor-strobe') ? 'Outdoor Siren' : 'Siren'
this.entity = { this.entity = {
...this.entity, ...this.entity,

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,12 +5,13 @@ const utils = require( '../lib/utils' )
class Thermostat extends RingSocketDevice { class Thermostat extends RingSocketDevice {
constructor(deviceInfo) { constructor(deviceInfo) {
super(deviceInfo, 'alarm') super(deviceInfo, 'alarm')
}
init() {
this.deviceData.mdl = 'Thermostat' this.deviceData.mdl = 'Thermostat'
this.childDevices = { this.operatingStatus = this.childDevices.find(d => d.deviceType === 'thermostat-operating-status'),
operatingStatus: deviceInfo.childDevices.find(d => d.deviceType === 'thermostat-operating-status'), this.temperatureSensor = this.childDevices.find(d => d.deviceType === RingDeviceType.TemperatureSensor)
temperatureSensor: deviceInfo.childDevices.find(d => d.deviceType === RingDeviceType.TemperatureSensor)
}
this.entity.thermostat = { this.entity.thermostat = {
component: 'climate', component: 'climate',
@@ -28,16 +29,16 @@ class Thermostat extends RingSocketDevice {
setPoint: (() => { setPoint: (() => {
return this.device.data.setPoint return this.device.data.setPoint
? this.device.data.setPoint ? this.device.data.setPoint
: this.childDevices.temperatureSensor.data.celsius : this.temperatureSensor.data.celsius
}), }),
operatingMode: (() => { operatingMode: (() => {
return this.childDevices.operatingStatus.data.operatingMode !== 'off' return this.operatingStatus.data.operatingMode !== 'off'
? `${this.childDevices.operatingStatus.data.operatingMode}ing` ? `${this.operatingStatus.data.operatingMode}ing`
: this.device.data.mode === 'off' : this.device.data.mode === 'off'
? 'off' ? 'off'
: this.device.data.fanMode === 'on' ? 'fan' : 'idle' : 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') ... this.entity.thermostat.modes.includes('auto')
? { ? {
autoSetPointInProgress: false, autoSetPointInProgress: false,
@@ -49,14 +50,14 @@ class Thermostat extends RingSocketDevice {
} : {} } : {}
} }
this.childDevices.operatingStatus.onData.subscribe(() => { this.operatingStatus.onData.subscribe(() => {
if (this.isOnline()) { if (this.isOnline()) {
this.publishOperatingMode() this.publishOperatingMode()
this.publishAttributes() this.publishAttributes()
} }
}) })
this.childDevices.temperatureSensor.onData.subscribe(() => { this.temperatureSensor.onData.subscribe(() => {
if (this.isOnline()) { if (this.isOnline()) {
this.publishTemperature() this.publishTemperature()
this.publishAttributes() this.publishAttributes()

View File

@@ -13,7 +13,7 @@ class Main {
constructor() { constructor() {
// Start event listeners // Start event listeners
utils.event.on('generatedToken', (generatedToken) => { utils.event.on('generated_token', (generatedToken) => {
this.init(generatedToken) this.init(generatedToken)
}) })

View File

@@ -9,7 +9,7 @@ class Mqtt {
this.connected = false this.connected = false
// Configure event listeners // Configure event listeners
utils.event.on('ringState', async (state) => { utils.event.on('ring_state', async (state) => {
if (!this.client && state === 'connected') { if (!this.client && state === 'connected') {
// Ring API connected, short wait before starting MQTT client // Ring API connected, short wait before starting MQTT client
await utils.sleep(2) 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 }, () => { 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 // 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) this.client.subscribe(topic)
}) })
} }
@@ -61,7 +61,7 @@ class Mqtt {
this.client.on('connect', () => { this.client.on('connect', () => {
if (!this.connected) { if (!this.connected) {
this.connected = true 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...') debug('Attempting to reconnect to MQTT broker...')
} }
this.connected = false this.connected = false
utils.event.emit('mqttState', 'disconnected') utils.event.emit('mqtt_state', 'disconnected')
}) })
this.client.on('error', (error) => { this.client.on('error', (error) => {
debug('Unable to connect to MQTT broker', error.message) debug('Unable to connect to MQTT broker', error.message)
this.connected = false this.connected = false
utils.event.emit('mqttState', 'disconnected') utils.event.emit('mqtt_state', 'disconnected')
}) })
// Process MQTT messages from subscribed command topics // Process MQTT messages from subscribed command topics
this.client.on('message', (topic, message) => { this.client.on('message', (topic, message) => {
message = message.toString() message = message.toString()
if (topic === utils.config.hass_topic || topic === 'hass/status' || topic === 'hassio/status') { 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 { } else {
utils.event.emit(topic, topic.split("/").slice(-2).join("/"), message) utils.event.emit(topic, topic.split("/").slice(-2).join("/"), message)
} }

View File

@@ -35,7 +35,7 @@ class RingMqtt {
this.republishCount = 6 // Republish config/state this many times after startup or HA start/restart this.republishCount = 6 // Republish config/state this many times after startup or HA start/restart
// Configure event listeners // Configure event listeners
utils.event.on('mqttState', (state) => { utils.event.on('mqtt_state', (state) => {
if (state === 'connected') { if (state === 'connected') {
this.mqttConnected = true this.mqttConnected = true
debug('MQTT connection established, processing Ring locations...') 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) debug('Home Assistant state topic '+topic+' received message: '+message)
if (message === 'online') { if (message === 'online') {
// Republish devices and state if restart of HA is detected // 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...`) debug(`Attempting connection to Ring API using ${generatedToken ? 'generated' : 'saved'} refresh token...`)
this.client = new RingApi(ringAuth) this.client = new RingApi(ringAuth)
await this.client.getProfile() 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`) debug(`Successfully established connection to Ring API using ${generatedToken ? 'generated' : 'saved'} token`)
// Subscribe to token update events and save new tokens to state file // Subscribe to token update events and save new tokens to state file
@@ -145,12 +145,12 @@ class RingMqtt {
// Get all Ring locations // Get all Ring locations
const locations = await this.client.getLocations() 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:')) debug(colors.white('This account has access to the following locations:'))
locations.map(function(location) { locations.map(function(location) {
debug(' '+colors.green(location.name)+colors.cyan(` (${location.id})`)) 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.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(' or device discovery for that location will hang indefinitely! '))
debug(colors.white(' If desired, use the "location_ids" config option to restrict discovery ')) debug(colors.white(' If desired, use the "location_ids" config option to restrict discovery '))
@@ -162,7 +162,7 @@ class RingMqtt {
let chimes = new Array() let chimes = new Array()
const unsupportedDevices = 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 new location, set custom properties and add to location list
if (this.locations.find(l => l.locationId == location.locationId)) { if (this.locations.find(l => l.locationId == location.locationId)) {
debug(colors.white('Existing location: ')+colors.green(location.name)+colors.cyan(` (${location.id})`)) 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.yellow(` Unsupported device: ${deviceType}`))
}) })
} }
debug(colors.green('-'.repeat(82))) debug(colors.green('-'.repeat(90)))
debug('Ring location/device data updated, sleeping for 5 seconds.') debug('Ring location/device data updated, sleeping for 5 seconds.')
await utils.sleep(2) await utils.sleep(2)
const cameras = await this.devices.filter(d => d.device instanceof RingCamera) const cameras = await this.devices.filter(d => d.device instanceof RingCamera)

View File

@@ -16,7 +16,7 @@ class RtspSimpleServer {
this.cameras = cameras this.cameras = cameras
} }
this.started = true this.started = true
debug(colors.green('-'.repeat(73))) debug(colors.green('-'.repeat(90)))
debug('Starting rtsp-simple-server process...') debug('Starting rtsp-simple-server process...')
this.rssProcess = spawn('rtsp-simple-server', [`${__dirname}/../config/rtsp-simple-server.yml`], { this.rssProcess = spawn('rtsp-simple-server', [`${__dirname}/../config/rtsp-simple-server.yml`], {
@@ -108,7 +108,7 @@ class RtspSimpleServer {
} }
} }
await utils.msleep(100) await utils.msleep(100)
debug(colors.green('-'.repeat(73))) debug(colors.green('-'.repeat(90)))
} }
async getPathDetails(path) { async getPathDetails(path) {

View File

@@ -8,7 +8,7 @@ const writeFileAtomic = require('write-file-atomic')
class State { class State {
constructor() { constructor() {
this.valid = false this.valid = false
this.writeDelay = false this.writeScheduled = false
this.data = { this.data = {
ring_token: '', ring_token: '',
systemId: '', systemId: '',
@@ -23,16 +23,16 @@ class State {
: this.file = '/data/ring-state.json' : this.file = '/data/ring-state.json'
await this.loadStateData() await this.loadStateData()
utils.event.on(`saveDeviceState`, (deviceId, stateData) => { utils.event.on(`save_device_state`, (deviceId, stateData) => {
this.data.devices[deviceId] = stateData this.data.devices[deviceId] = stateData
this.updateStateFile() this.updateStateFile()
}) })
utils.event.on('getDeviceState', (deviceId) => { utils.event.on('get_device_state', (deviceId) => {
if (this.data.devices.hasOwnProperty(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 { } else {
utils.event.emit(`deviceState_${deviceId}`, false) utils.event.emit(`device_state_${deviceId}`, false)
} }
}) })
} }
@@ -79,9 +79,13 @@ class State {
} }
async updateStateFile() { async updateStateFile() {
if (!this.writeDelay) { // The writeDelay flag is a hack to keep from writing too often when there are burst
this.writeDelay = true // of state updates such as during startup. If a state file update is already scheduled
await utils.sleep(10) // then calls to this function are skipped.
if (!this.writeScheduled) {
this.writeScheduled = true
await utils.sleep(5)
this.writeScheduled = false
try { try {
await writeFileAtomic(this.file, JSON.stringify(this.data)) await writeFileAtomic(this.file, JSON.stringify(this.data))
debug('Successfully saved updated refresh token in state file: '+this.file) 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(colors.red('Failed to save updated refresh token in state file: '+this.file))
debug(err.message) debug(err.message)
} }
this.writeDelay = false
} }
} }
} }

View File

@@ -49,15 +49,15 @@ class StreamWorkers {
if (workerId >= 0) { if (workerId >= 0) {
switch (data.state) { switch (data.state) {
case 'active': case 'active':
utils.event.emit(`${deviceId}_livestream`, 'active') utils.event.emit(`livestream_${deviceId}`, 'active')
this.streamWorkers[workerId].sessions[deviceId].streamData.sessionId = data.liveCallData.sessionId this.streamWorkers[workerId].sessions[deviceId].streamData.sessionId = data.liveCallData.sessionId
break; break;
case 'inactive': case 'inactive':
utils.event.emit(`${deviceId}_livestream`, 'inactive') utils.event.emit(`livestream_${deviceId}`, 'inactive')
delete this.streamWorkers[workerId].sessions[deviceId] delete this.streamWorkers[workerId].sessions[deviceId]
break; break;
case 'failed': case 'failed':
utils.event.emit(`${deviceId}_livestream`, 'failed') utils.event.emit(`livestream_${deviceId}`, 'failed')
delete this.streamWorkers[workerId].sessions[deviceId] delete this.streamWorkers[workerId].sessions[deviceId]
break; break;
} }

View File

@@ -14,7 +14,7 @@ class TokenApp {
this.start() this.start()
} }
utils.event.on('ringState', async (state) => { utils.event.on('ring_state', async (state) => {
if (state === 'connected') { if (state === 'connected') {
this.ringConnected = true this.ringConnected = true
@@ -87,7 +87,7 @@ class TokenApp {
} }
if (generatedToken) { if (generatedToken) {
res.sendFile('restart.html', {root: webdir}) res.sendFile('restart.html', {root: webdir})
utils.event.emit('generatedToken', generatedToken.refresh_token) utils.event.emit('generated_token', generatedToken.refresh_token)
} }
}) })
} }