homebridge-zigbee-v2
Version:
ZigBee Platform plugin for HomeBridge
257 lines (227 loc) • 8.94 kB
JavaScript
const castArray = require('../utils/castArray')
const timestamp = require('../utils/timestamp')
function parseHistoryServiceName(name) {
return `${name} History`
}
function getHistoryServices(items) {
const bySubtype = {}
for (const item of items) {
if (item.history) {
const service = bySubtype[item.history.name] || {
type: item.history.type,
name: item.history.name || 'Default',
config: {},
}
for (const use of castArray(item.history.use)) {
service.config[use] = {
name: item.name,
type: item.type,
subtype: item.subtype,
}
}
bySubtype[item.history.name] = service
}
}
return Object.values(bySubtype)
}
function copyCharacteristicValueIfExist(device, oldService, newService, characteristic) {
device.addCharacteristicIfDoesNotExist(newService, characteristic)
if (oldService && oldService.testCharacteristic(characteristic)) {
newService.getCharacteristic(characteristic).setValue(
oldService.getCharacteristic(characteristic).value
)
}
}
function createService(device, name, type) {
const oldService = device.getService(parseHistoryServiceName(name))
const newService = new device.Service.EveHistory(type, {
displayName: device.parseServiceName(name),
}, {
disableTimer: true,
storage: 'fs',
path: `${device.platform.api.user.storagePath()}/accessories`,
filename: `history_${device.ieeeAddr}_${name.toLowerCase()}.json`,
})
// Addition characteristics
switch (type) {
case 'door':
case 'energy':
copyCharacteristicValueIfExist(
device, oldService, newService,
device.Characteristic.EveResetTotal
)
break
default:
// Do nothing
}
return newService
}
function mount(device) {
for (const item of getHistoryServices(device.getAvailbleServices())) {
// Get target and history services
// const targetService = device.getService(item.name, item.subtype)
const historyService = device.getService(parseHistoryServiceName(item.name))
// Mount history handlers by types
switch (item.type) {
case 'motion':
mountHistoryForMotion(device, historyService, item.config)
break
case 'door':
mountHistoryForDoor(device, historyService, item.config)
break
case 'weather':
mountHistoryForWeather(device, historyService, item.config)
break
case 'energy':
mountHistoryForEnergy(device, historyService, item.config)
break
default:
// Do nothing
}
}
}
function mountHistoryForMotion(device, historyService, config) {
const statusService = device.getService(config.status.name, config.status.subtype)
device.addCharacteristicIfDoesNotExist(statusService, device.Characteristic.EveLastActivation)
const lastActivation = statusService.getCharacteristic(device.Characteristic.EveLastActivation)
const motionDetected = statusService.getCharacteristic(device.Characteristic.MotionDetected)
const entry = { status: motionDetected.value }
const firstStartTimeout = 1000 * 60 * 3
const minimumGapTimeout = 1000 * 60 * 10
let updateTimeout = null
function updateEntry(timeout = 0) {
device.clearTimeout(updateTimeout)
updateTimeout = device.setTimeout(() => {
historyService.addEntry({ time: timestamp(), ...entry })
// Set new timeout to avoid breaks in charts
updateEntry(minimumGapTimeout)
}, timeout)
}
motionDetected.on('change', ({ newValue }) => {
entry.status = newValue
lastActivation.setValue(timestamp() - historyService.getInitialTime())
updateEntry()
})
updateEntry(firstStartTimeout)
}
function mountHistoryForDoor(device, historyService, config) {
const statusService = device.getService(config.status.name, config.status.subtype)
device.addCharacteristicIfDoesNotExist(statusService, device.Characteristic.EveTimesOpened)
device.addCharacteristicIfDoesNotExist(statusService, device.Characteristic.EveLastActivation)
device.addCharacteristicIfDoesNotExist(statusService, device.Characteristic.EveOpenDuration)
device.addCharacteristicIfDoesNotExist(statusService, device.Characteristic.EveClosedDuration)
const timesOpened = statusService.getCharacteristic(device.Characteristic.EveTimesOpened)
const lastActivation = statusService.getCharacteristic(device.Characteristic.EveLastActivation)
const contactSensor = statusService.getCharacteristic(device.Characteristic.ContactSensorState)
const resetTotal = historyService.getCharacteristic(device.Characteristic.EveResetTotal)
const entry = { status: contactSensor.value }
const firstStartTimeout = 1000 * 60 * 3
const minimumGapTimeout = 1000 * 60 * 10
let updateTimeout = null
function updateEntry(timeout = 0) {
device.clearTimeout(updateTimeout)
updateTimeout = device.setTimeout(() => {
historyService.addEntry({ time: timestamp(), ...entry })
// Set new timeout to avoid breaks in charts
updateEntry(minimumGapTimeout)
}, timeout)
}
resetTotal.on('set', (value, callback) => {
timesOpened.setValue(0)
callback(null)
})
contactSensor.on('change', ({ newValue }) => {
entry.status = newValue
timesOpened.setValue(timesOpened.value + newValue)
lastActivation.setValue(timestamp() - historyService.getInitialTime())
updateEntry()
})
updateEntry(firstStartTimeout)
}
function mountHistoryForWeather(device, historyService, config) {
const entry = { temp: 0, humidity: 0, pressure: 0 }
const waitForValuesTimeout = 1000 * 3
const firstStartTimeout = 1000 * 60 * 3
const minimumGapTimeout = 1000 * 60 * 10
let updateTimeout = null
function updateEntry(timeout = 0) {
device.clearTimeout(updateTimeout)
updateTimeout = device.setTimeout(() => {
// Make sure all weather entry attributes have been updated
historyService.addEntry({ time: timestamp(), ...entry })
// Set new timeout to avoid breaks in charts
updateEntry(minimumGapTimeout)
}, timeout)
}
if (config.temp) {
const tempService = device.getService(config.temp.name, config.temp.subtype)
const temperature = tempService.getCharacteristic(
device.Characteristic.CurrentTemperature
)
entry.temp = temperature.value
temperature.on('change', ({ newValue }) => {
entry.temp = newValue
updateEntry(waitForValuesTimeout)
})
}
if (config.humidity) {
const humidityService = device.getService(config.humidity.name, config.humidity.subtype)
const humidity = humidityService.getCharacteristic(
device.Characteristic.CurrentRelativeHumidity
)
entry.humidity = humidity.value
humidity.on('change', ({ newValue }) => {
entry.humidity = newValue
updateEntry(waitForValuesTimeout)
})
}
if (config.pressure) {
const pressureService = device.getService(config.pressure.name, config.pressure.subtype)
device.addCharacteristicIfDoesNotExist(pressureService, device.Characteristic.EveAirPressure)
device.addCharacteristicIfDoesNotExist(pressureService, device.Characteristic.EveElevation)
pressureService.updateCharacteristic(device.Characteristic.EveElevation, 0)
const pressure = pressureService.getCharacteristic(
device.Characteristic.EveAirPressure
)
entry.pressure = pressure.value
pressure.on('change', ({ newValue }) => {
entry.pressure = newValue
updateEntry(waitForValuesTimeout)
})
}
updateEntry(firstStartTimeout)
}
function mountHistoryForEnergy(device, historyService, config) {
const powerService = device.getService(config.power.name, config.power.subtype)
device.addCharacteristicIfDoesNotExist(powerService, device.Characteristic.EveCurrentConsumption)
device.addCharacteristicIfDoesNotExist(powerService, device.Characteristic.EveTotalConsumption)
const consumption = powerService.getCharacteristic(device.Characteristic.EveCurrentConsumption)
const totalConsumption = powerService.getCharacteristic(device.Characteristic.EveTotalConsumption)
const resetTotal = historyService.getCharacteristic(device.Characteristic.EveResetTotal)
const entry = { power: consumption.value }
const firstStartTimeout = 1000 * 60 * 3
const minimumGapTimeout = 1000 * 60 * 10
let updateTimeout = null
function updateEntry(timeout = 0) {
device.clearTimeout(updateTimeout)
updateTimeout = device.setTimeout(() => {
historyService.addEntry({ time: timestamp(), ...entry })
// Set new timeout to avoid breaks in charts
updateEntry(minimumGapTimeout)
}, timeout)
}
resetTotal.on('set', (value, callback) => {
totalConsumption.setValue(0)
callback(null)
})
consumption.on('change', ({ newValue }) => {
entry.power = newValue
updateEntry()
})
updateEntry(firstStartTimeout)
}
module.exports = {
getHistoryServices,
createService,
mount,
}