UNPKG

homebridge-history-contact

Version:

Homebridge fake contact sensor with fakegato history

295 lines (262 loc) 12.9 kB
'use strict'; let Service, Characteristic, HomebridgeAPI, HistoryService; const express = require('express'); const moment = require('moment'); module.exports = function(homebridge) { Service = homebridge.hap.Service; Characteristic = homebridge.hap.Characteristic; HomebridgeAPI = homebridge; HistoryService = require("fakegato-history")(homebridge) // -- ACCESSORY DEFINITION class ResetTotal extends Characteristic { static UUID = "E863F112-079E-48FF-8F27-9C2605A29F52"; constructor() { super("Reset", ResetTotal.UUID, { format: Characteristic.Formats.UINT32, perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY, Characteristic.Perms.WRITE] }); this.value = this.getDefaultValue(); } } class LastActivation extends Characteristic { static UUID = "E863F11A-079E-48FF-8F27-9C2605A29F52"; constructor() { super("LastActivation", LastActivation.UUID, { format: Characteristic.Formats.UINT32, perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] }); this.value = this.getDefaultValue(); } } class TimesOpened extends Characteristic { static UUID = "E863F129-079E-48FF-8F27-9C2605A29F52"; constructor() { super("TimesOpened", TimesOpened.UUID, { format: Characteristic.Formats.UINT32, perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] }); this.value = this.getDefaultValue(); } } class Char118 extends Characteristic { static UUID = "E863F118-079E-48FF-8F27-9C2605A29F52"; constructor() { super("Char118", Char118.UUID, { format: Characteristic.Formats.UINT32, perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] }); this.value = this.getDefaultValue(); } } class Char119 extends Characteristic { static UUID = "E863F119-079E-48FF-8F27-9C2605A29F52"; constructor() { super("Char119", Char119.UUID, { format: Characteristic.Formats.UINT32, perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] }); this.value = this.getDefaultValue(); } } class HistoryContact { constructor(log, config) { this.log = log; this.config = config; this.name = config.name; this.displayName = this.name; this.apiKey = this.config.apiKey; this.httpPort = this.config.httpPort || 8449; this.serialNumber = "HBHC-" + this.config.serialNumber; this.state = false; this.initDone = false; this.firstGet = true; this.numberOpened = 0; this.lastReset = 0; this.lastOpening = 0; this.extraPersistedData = {}; this.cacheDirectory = HomebridgeAPI.user.persistPath(); this.storageID = "HBHistoryContact_" + this.serialNumber; this.storage = require('node-persist'); this.initPromise = this.storage.init({ dir: this.cacheDirectory, forgiveParseErrors: true }) .then(_ => this.storage.getItem(this.storageID)) .then(this.setContactState.bind(this)) .then(_ => this.initDone = true); this.infoService = new Service.AccessoryInformation(); this.infoService.setCharacteristic(Characteristic.Name, this.name) .setCharacteristic(Characteristic.Manufacturer, "Charly") .setCharacteristic(Characteristic.Model, "History Contact") .setCharacteristic(Characteristic.FirmwareRevision, "1.0.0") .setCharacteristic(Characteristic.SerialNumber, this.serialNumber); this.contactService = new Service.ContactSensor(this.name); this.loggingService = new HistoryService("door", this, { storage: "fs", filename: this.serialNumber + "_log.json", path: this.cacheDirectory, log: this.log }); this.loggingService.accessoryName = this.serialNumber; this.contactService.addCharacteristic(LastActivation); this.contactService.addCharacteristic(TimesOpened); this.contactService.addCharacteristic(ResetTotal); this.contactService.addCharacteristic(Char118); this.contactService.addCharacteristic(Char119); this.extraPersistedData = this.loggingService.getExtraPersistedData(); if (this.extraPersistedData != undefined) { this.numberOpened = this.extraPersistedData.numberOpened || 0; this.lastOpening = this.extraPersistedData.lastOpening || 0; this.lastReset = this.extraPersistedData.lastReset || 0; } this.contactService.getCharacteristic(Characteristic.ContactSensorState) .on('get', (callback) => { this.initPromise.then((_ => { this.log.debug(`getContactSensorState ${this.serialNumber} = ${this.state}`); if (this.firstGet) { this.firstGet = false; this.loggingService.addEntry({ time: moment().unix(), status: this.state }); } callback(null, this.state); }).bind(this)); }); this.contactService.getCharacteristic(Characteristic.ContactSensorState) .on('change', (change) => { this.initPromise.then((_ => { this.log.debug(`getContactSensorState ${this.serialNumber} = ${this.state}`); }).bind(this)); }); this.contactService.getCharacteristic(ResetTotal) .on('set', (value, callback) => { this.initPromise.then((_ => { this.extraPersistedData = this.loggingService.getExtraPersistedData(); if (this.extraPersistedData != undefined && this.extraPersistedData.lastOpening != undefined) { this.lastOpening = this.extraPersistedData.lastOpening; } this.numberOpened = 0; this.lastReset = value; this.loggingService.setExtraPersistedData({ numberOpened: this.numberOpened, lastOpening: this.lastOpening, lastReset: this.lastReset }); this.loggingService.addEntry({ time: moment().unix(), status: this.state }); callback(null); }).bind(this)); }) .on('get', (callback) => { this.initPromise.then((_ => { this.extraPersistedData = this.loggingService.getExtraPersistedData(); if (this.extraPersistedData != undefined && this.extraPersistedData.lastReset >= 0) { this.lastReset = this.extraPersistedData.lastReset || 0; } this.log.debug(`getLastReset = ${this.lastReset}`); callback(null, this.lastReset); }).bind(this)); }); this.contactService.getCharacteristic(TimesOpened) .on('get', (callback) => { this.initPromise.then((_ => { this.extraPersistedData = this.loggingService.getExtraPersistedData(); if (this.extraPersistedData != undefined) this.numberOpened = this.extraPersistedData.numberOpened || 0; this.log.debug(`getNumberOpened = ${this.numberOpened}`); callback(null, this.numberOpened); }).bind(this)); }); this.contactService.getCharacteristic(LastActivation) .on('get', (callback) => { this.initPromise.then((_ => { this.extraPersistedData = this.loggingService.getExtraPersistedData(); if (this.extraPersistedData != undefined) this.lastOpening = this.extraPersistedData.lastOpening || 0; this.log.debug(`lastOpening = ${this.lastOpening}`); callback(null, this.lastOpening); }).bind(this)); }); this.app = express(); this.app.use(express.json()); this.app.post('/', this.requestHandler.bind(this)); this.app.listen(this.httpPort); } requestHandler(req, res) { if (req.body.apiKey != this.apiKey) { return res.status(401).json({ success: false, message: "Wrong API Key", error: true, errorMessage: "Forbidden", errorCode: 401 }) } if (!!req.body.clear || !!req.body.clean) { this.loggingService.cleanPersist(); this.numberOpened = 0; this.lastReset = 0; this.lastOpening = 0; this.extraPersistedData = {}; this.loggingService.setExtraPersistedData({ numberOpened: this.numberOpened, lastOpening: this.lastOpening, lastReset: this.lastReset }); this.loggingService.cleanPersist(); return res.status(200).json({ success: true, message: "Success" }) } if (req.body.state !== 0 && req.body.state !== 1) { return res.status(404).json({ success: false, message: "state must be 0 for 'closed' or 1 for 'opened'", error: true, errorMessage: "Bad Request", errorCode: 400 }) } this.setContactState(req.body.state) res.status(200).json({ success: true, message: "Success" }) } stateToHomebridgeConstant(unparsedState) { return !unparsedState ? Characteristic.ContactSensorState.CONTACT_DETECTED : Characteristic.ContactSensorState.CONTACT_NOT_DETECTED } setContactState(newState) { let newParsedState = !newState ? false : true; if (newParsedState == this.state) return; this.state = newParsedState; this.storage.setItem(this.storageID, this.state); if (!this.loggingService || !this.contactService || !this.initDone) return; this.extraPersistedData = this.loggingService.getExtraPersistedData(); if (this.extraPersistedData != undefined && this.extraPersistedData.lastReset != undefined) { this.lastReset = this.extraPersistedData.lastReset || 0; } if (this.extraPersistedData != undefined && this.extraPersistedData.numberOpened != undefined) { this.numberOpened = this.extraPersistedData.numberOpened; } if (this.state) { this.lastOpening = moment().unix() - this.loggingService.getInitialTime(); if (this.extraPersistedData != undefined && this.extraPersistedData.numberOpened != undefined) this.numberOpened = this.extraPersistedData.numberOpened + 1; else this.numberOpened++; } this.loggingService.setExtraPersistedData({ numberOpened: this.numberOpened, lastOpening: this.lastOpening, lastReset: this.lastReset }); this.loggingService.addEntry({ time: moment().unix(), status: this.state }); this.contactService.setCharacteristic(Characteristic.ContactSensorState, this.stateToHomebridgeConstant(newState)); this.contactService.getCharacteristic(Characteristic.ContactSensorState).getValue(null); } getServices() { return [this.infoService, this.contactService, this.loggingService]; } } // -- END ACCESSORY DEFINITION homebridge.registerAccessory("homebridge-history-contact", "HistoryContact", HistoryContact); };