homebridge-history-contact
Version:
Homebridge fake contact sensor with fakegato history
295 lines (262 loc) • 12.9 kB
JavaScript
'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);
};