homebridge-meraki-mt-sensor
Version:
Homebridge plugin (https://github.com/homebridge/homebridge) for Meraki MT Sensors
325 lines (280 loc) • 12.1 kB
JavaScript
'use strict';
const axios = require('axios').default;
const fs = require('fs');
const path = require('path');
const PLUGIN_NAME = 'homebridge-meraki-mt-sensor';
const PLATFORM_NAME = 'MerakiMT';
let Accessory, Characteristic, Service, Categories, UUID;
module.exports = (api) => {
Accessory = api.platformAccessory;
Characteristic = api.hap.Characteristic;
Service = api.hap.Service;
Categories = api.hap.Categories;
UUID = api.hap.uuid;
api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, merakiMTPlatform, true);
}
class merakiMTPlatform {
constructor(log, config, api) {
// only load if configured
if (!config || !Array.isArray(config.devices)) {
log('No configuration found for %s', PLUGIN_NAME);
return;
}
this.log = log;
this.config = config;
this.api = api;
this.devices = config.devices || [];
this.api.on('didFinishLaunching', () => {
this.log.debug('didFinishLaunching');
for (let i = 0, len = this.devices.length; i < len; i++) {
let device = this.devices[i];
if (!device.name) {
this.log.warn('Device Name Missing');
} else {
new merakiMTDevice(this.log, device, this.api);
}
}
});
}
configureAccessory(platformAccessory) {
this.log.debug('configurePlatformAccessory');
}
removeAccessory(platformAccessory) {
this.log.debug('removePlatformAccessory');
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [platformAccessory]);
}
}
class merakiMTDevice {
constructor(log, config, api) {
this.log = log;
this.api = api;
this.config = config;
//network configuration
this.name = config.name;
this.host = config.host;
this.apiKey = config.apiKey;
this.organizationId = config.organizationId;
this.networkId = config.networkId;
this.type = config.type;
this.refreshInterval = config.refreshInterval || 10;
//get Device info
this.manufacturer = config.manufacturer || 'Cisco Meraki';
this.modelName = config.modelName || '-';
this.serialNumber = config.serial || '-';
this.firmwareRevision = config.firmwareRevision || '-';
//setup variables
this.checkDeviceState = false;
this.prefDir = path.join(api.user.storagePath(), 'meraki');
this.productTypeUrl = this.host + '/api/v1/networks/' + this.networkId;
this.mtUrl = this.host + '/api/v1/devices/' + this.networkId + '/sensors';
this.mtStatsUrl = this.host + '/api/v1/networks/' + this.networkId + '/sensors/stats/latestBySensor';
this.devicesUrl = this.host + '/api/v1/networks/' + this.networkId + '/devices';
this.meraki = axios.create({
baseURL: this.host,
headers: {
'X-Cisco-Meraki-API-Key': this.apiKey,
'Content-Type': 'application/json; charset=utf-8',
'Accept': 'application/json'
}
});
//check if prefs directory ends with a /, if not then add it
if (this.prefDir.endsWith('/') === false) {
this.prefDir = this.prefDir + '/';
}
//check if the directory exists, if not then create it
if (fs.existsSync(this.prefDir) === false) {
fs.mkdir(this.prefDir, { recursive: false }, (error) => {
if (error) {
this.log.error('Device: %s , create directory: %s, error: %s', this.name, this.prefDir, error);
} else {
this.log.debug('Device: %s , create directory successful: %s', this.name, this.prefDir);
}
});
}
//Check device state
setInterval(function () {
if (this.checkDeviceState) {
this.updateDeviceState();
}
}.bind(this), this.refreshInterval * 1000);
this.prepareAccessory();
}
//Prepare accessory
prepareAccessory() {
this.log.debug('prepareAccessory');
const accessoryName = this.name;
const accessoryUUID = UUID.generate(accessoryName);
const accessoryCategory = Categories.AIRPORT;
this.accessory = new Accessory(accessoryName, accessoryUUID, accessoryCategory);
this.prepareInformationService();
this.prepareMerakiService();
this.log.debug('Device: %s %s, publishExternalAccessories.', this.host, accessoryName);
this.api.publishExternalAccessories(PLUGIN_NAME, [this.accessory]);
}
//Prepare information service
prepareInformationService() {
this.log.debug('prepareInformationService');
this.getDeviceInfo();
let manufacturer = this.manufacturer;
let modelName = this.modelName;
let serialNumber = this.serialNumber;
let firmwareRevision = this.firmwareRevision;
this.accessory.removeService(this.accessory.getService(Service.AccessoryInformation));
this.informationService = new Service.AccessoryInformation();
this.informationService
.setCharacteristic(Characteristic.Name, this.name)
.setCharacteristic(Characteristic.Manufacturer, manufacturer)
.setCharacteristic(Characteristic.Model, modelName)
.setCharacteristic(Characteristic.SerialNumber, serialNumber)
.setCharacteristic(Characteristic.FirmwareRevision, firmwareRevision);
this.accessory.addService(this.informationService);
}
//Prepare service
async prepareMerakiService() {
this.log.debug('prepareMerakiService');
try {
if (this.type == "tempSensor") {
this.merakiService1 = new Service.TemperatureSensor(this.name, 'merakiService1');
this.merakiService1.getCharacteristic(Characteristic.CurrentTemperature)
.setProps({
minValue: -100,
maxValue: 100
})
.on('get', this.getTemperature.bind(this))
.on('set', this.getTemperature.bind(this));
}
if (this.type == "humiditySensor") {
this.merakiService1 = new Service.HumiditySensor(this.name, 'merakiService1');
this.merakiService1.getCharacteristic(Characteristic.CurrentRelativeHumidity)
.on('get', this.getHumidity.bind(this))
.on('set', this.getHumidity.bind(this));
}
if (this.type == "doorSensor") {
this.merakiService1 = new Service.ContactSensor(this.name, 'merakiService1');
this.merakiService1.getCharacteristic(Characteristic.ContactSensorState)
.on('get', this.getContactState.bind(this))
.on('set', this.getContactState.bind(this));
}
if (this.type == "waterSensor") {
this.merakiService1 = new Service.LeakSensor(this.name, 'merakiService1');
this.merakiService1.getCharacteristic(Characteristic.LeakDetected)
.on('get', this.getWaterState.bind(this))
.on('set', this.getWaterState.bind(this));
}
this.accessory.addService(this.merakiService1);
this.checkDeviceState = true;
} catch (error) {
this.log.debug('Device: %s, state Offline, read Device error: %s', this.name, error);
};
}
async getDeviceInfo() {
var me = this;
try {
me.log.info('Device: %s, state: Online.', me.name);
me.log('-------- %s --------', me.name);
me.log('Manufacturer: %s', me.manufacturer);
me.log('Model: %s', me.modelName);
me.log('Serialnr: %s', me.serialNumber);
me.log('Firmware: %s', me.firmwareRevision);
me.log('----------------------------------');
me.updateDeviceState();
} catch (error) {
me.log.error('Device: %s, getDeviceInfo error: %s', me.name, error);
}
}
async updateDeviceState() {
var me = this;
try {
if (this.type == "tempSensor") {
if (me.merakiService1) {
const response = await me.meraki.get(me.mtStatsUrl + '?metric=temperature', { data: { serials: [me.serialNumber]} });
me.log.info('got response %s', response.data[0]);
let value = (response.data[0].value);
me.log.info('Network: %s, Sensor: %s Value: %s', me.name, me.name, value);
me.merakiService1.updateCharacteristic(Characteristic.CurrentTemperature, value);
}
}
if (this.type == "humiditySensor") {
if (me.merakiService1) {
const humresponse = await me.meraki.get(me.mtStatsUrl + '?metric=humidity', { data: { serials: [me.serialNumber]} });
me.log.info('got response %s', humresponse.data[0]);
let humvalue = (humresponse.data[0].value);
me.log.info('Network: %s, Sensor: %s Value: %s', me.name, me.name, humvalue);
me.merakiService1.updateCharacteristic(Characteristic.CurrentRelativeHumidity, humvalue);
}
}
if (this.type == "doorSensor") {
if (me.merakiService1) {
const response = await me.meraki.get(me.mtStatsUrl + '?metric=door', { data: { serials: [me.serialNumber]} });
me.log.info('got response %s', response.data[0]);
let value = (response.data[0].value);
me.log.info('Network: %s, Sensor: %s Value: %s', me.name, me.name, value);
me.merakiService1.updateCharacteristic(Characteristic.ContactSensorState, value);
}
}
if (this.type == "waterSensor") {
if (me.merakiService1) {
const response = await me.meraki.get(me.mtStatsUrl + '?metric=water_detection', { data: { serials: [me.serialNumber]} });
me.log.info('got response %s', response.data[0]);
let value = (response.data[0].value);
me.log.info('Network: %s, Sensor: %s Value: %s', me.name, me.name, value);
me.merakiService1.updateCharacteristic(Characteristic.LeakDetected, value);
}
}
if (me.serialNumber != "-" && me.modelName == "-") {
// go get model numbers for devices we have serials for
const response = await me.meraki.get(me.devicesUrl);
var picked = response.data.find(o => o.serial === me.serialNumber);
me.informationService.setCharacteristic(Characteristic.Model, picked['model'])
me.modelName = picked['model'];
me.log.info('%s: updated model to: %s', me.serialNumber, me.modelName);
}
} catch (error) {
me.log.error('UpdateDeviceState() - Device: %s, update status error: %s, state: Offline', me.name, error);
}
}
async getTemperature(callback) {
var me = this;
try {
const response = await me.meraki.get(me.mtStatsUrl + '?metric=temperature', { data: { serials: [me.serialNumber]} });
let value = (response.data[0].value);
me.log.info('getTemperature() - Network: %s, Sensor: %s Value: %s', me.name, me.name, value);
callback(null, value);
} catch (error) {
me.log.debug('Device: %s, Serial: %s get state error: %s', me.name, me.serial, error);
};
}
async getHumidity(callback) {
var me = this;
try {
const response = await me.meraki.get(me.mtStatsUrl + '?metric=humidity', { data: { serials: [me.serialNumber]} });
let value = (response.data[0].value);
me.log.info('getHumidity() - Network: %s, Sensor: %s Value: %s', me.name, me.name, value);
callback(null, value);
} catch (error) {
me.log.debug('Device: %s, Serial: %s get state error: %s', me.name, me.serial, error);
};
}
async getContactState(callback) {
var me = this;
try {
const response = await me.meraki.get(me.mtStatsUrl + '?metric=door', { data: { serials: [me.serialNumber]} });
let value = (response.data[0].value);
me.log.info('getContactState() - Network: %s, Sensor: %s Value: %s', me.name, me.name, value);
callback(null, value);
} catch (error) {
me.log.debug('Device: %s, Serial: %s get state error: %s', me.name, me.serial, error);
};
}
async getWaterState(callback) {
var me = this;
try {
const response = await me.meraki.get(me.mtStatsUrl + '?metric=water_detection', { data: { serials: [me.serialNumber]} });
let value = (response.data[0].value);
me.log.info('getContactState() - Network: %s, Sensor: %s Value: %s', me.name, me.name, value);
callback(null, value);
} catch (error) {
me.log.debug('Device: %s, Serial: %s get state error: %s', me.name, me.serial, error);
};
}
}