homebridge-risco-platform
Version:
RiscoAlarm Plugin Platform for Homebridge
1,108 lines (1,013 loc) • 71.8 kB
JavaScript
'use strict';
var pjson = require('./package.json');
var waitUntil = require('wait-until');
var pollingtoevent = require('polling-to-event');
const JSONreplacer = () => {
const visited = new WeakSet();
return (key, value) => {
if (typeof value === 'object' && value !== null) {
if (visited.has(value)) {
return;
}
visited.add(value);
}
return value;
};
};
class RiscoCPPartitions {
constructor(log, accConfig, api, accessory, TypeOfAcc = 'partition') {
this.log = log;
this.name = accConfig.context.name;
this.RiscoSession = accConfig.RiscoSession;
this.TypeOfAcc = TypeOfAcc;
this.RiscoPartId = accConfig.context.Id;
this.polling = accConfig.polling || false;
this.pollInterval = accConfig.pollInterval || 30000;
this.accessory = accessory;
this.api = api;
this.OccupancyPreventArming = accConfig.OccupancyPreventArming || true;
this.Service = this.api.hap.Service;
this.Characteristic = this.api.hap.Characteristic;
this.mainService = this.accessory.getService(this.Service.SecuritySystem, this.accessory.displayName);
this.mainService
.getCharacteristic(this.Characteristic.SecuritySystemCurrentState)
.on('get', this.getCurrentState.bind(this));
this.mainService
.getCharacteristic(this.Characteristic.SecuritySystemTargetState)
.on('get', this.getTargetState.bind(this))
.on('set', this.setTargetState.bind(this));
if ((this.TypeOfAcc == 'partition') && (this.OccupancyPreventArming)) {
this.OccupancyService = this.accessory.getService(this.Service.OccupancySensor, this.accessory.displayName);
this.OccupancyService
.getCharacteristic(this.Characteristic.OccupancyDetected)
.on('get', this.getCurrentOccupancyState.bind(this));
this.OccupancyState;
}
if (this.TypeOfAcc == 'partition') {
this.long_event_name = `long_part_${this.RiscoPartId}_${(this.name.toLowerCase()).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/ /g, '_')}`;
} else {
this.long_event_name = `long_group_${this.RiscoPartId}_${((this.name.toLowerCase()).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/ /g, '_'))}`;
}
// Default Value
this.riscoCurrentState; // Do not set default. Looks like plugin get restarted after some time. Generates false alarms.
this.riscoTargetState = this.riscoCurrentState;
//avoid maxlistener warning
const MaxApiListeners = this.api.getMaxListeners();
const ActualListeners = this.api.listenerCount('shutdown');
this.log.debug('Api Event Shutdown : \nActual Listener :%s for Maximum :%s',this.api.listenerCount('shutdown'),MaxApiListeners);
if (ActualListeners >= MaxApiListeners) {
//give one more for other process
this.api.setMaxListeners(ActualListeners + 2);
this.log.debug('Max Listener Exceeded. Set To :%s', (ActualListeners + 2));
}
this.api.once('shutdown', () => {
this.log.debug('Cleaning Before Exit.\nRemove All Listeners for %s', this.name);
this.mainService
.getCharacteristic(this.Characteristic.SecuritySystemCurrentState)
.removeListener('get', this.getCurrentState);
this.mainService
.getCharacteristic(this.Characteristic.SecuritySystemTargetState)
.removeListener('get', this.getTargetState)
.removeListener('set', this.setTargetState);
if ((this.TypeOfAcc == 'partition') && (this.OccupancyPreventArming)) {
this.OccupancyService
.getCharacteristic(this.Characteristic.OccupancyDetected)
.removeListener('get', this.getCurrentOccupancyState);
}
});
//initialize Security System States
this.getRefreshState();
this.PollingLoop();
}
translateState(aState) {
var self = this;
var translatedSate = 'UNKNOWN';
switch (aState) {
case self.Characteristic.SecuritySystemTargetState.STAY_ARM:
translatedSate = 'STAY_ARM';
break;
case self.Characteristic.SecuritySystemTargetState.NIGHT_ARM:
translatedSate = 'NIGHT_ARM';
break;
case self.Characteristic.SecuritySystemTargetState.AWAY_ARM:
translatedSate = 'AWAY_ARM';
break;
case self.Characteristic.SecuritySystemTargetState.DISARM:
translatedSate = 'DISARM';
break;
case 4:
translatedSate = 'ALARM';
break;
};
return translatedSate;
}
PollingLoop() {
var self = this;
// set up polling if requested
if (self.polling) {
self.log.debug('Starting polling with an interval of %s ms', self.pollInterval);
// 0 - Characteristic.SecuritySystemTargetState.STAY_ARM: => Partial Mode
// 1 - Characteristic.SecuritySystemTargetState.AWAY_ARM: => Full Armed Mode
// 2 - Characteristic.SecuritySystemTargetState.NIGHT_ARM: => Partial Mode
// 3 - Characteristic.SecuritySystemTargetState.DISARM: => Really ?? Disarmed
// 4 - Characteristic.SecuritySystemCurrentState.ALARM: => Alarm
var emitter = new pollingtoevent( (done) => {
self.getRefreshState( (err, result) => {
done(err, result);
});
}, {
longpollEventName: self.long_event_name,
longpolling: true,
interval: 1000
});
emitter.on(self.long_event_name, (state) => {
if (state) {
self.log.info('Partition "%s" => New state detected: (%s) -> %s. Notify!', self.name, state[0], self.translateState(state[0]));
self.mainService.updateCharacteristic(self.Characteristic.SecuritySystemCurrentState, state[0]);
self.mainService.updateCharacteristic(self.Characteristic.SecuritySystemTargetState, state[1]);
self.riscoCurrentState = state[0];
self.riscoTargetState = state[1];
if ((this.TypeOfAcc == 'partition') && (this.OccupancyPreventArming)) {
self.log.info('Partition "%s" is %sOccupied. Notify!', self.name, ((state[2] == 0) ? 'not ' : ''));
self.OccupancyService.updateCharacteristic(self.Characteristic.OccupancyDetected, state[2]);
self.OccupancyState = state[2];
}
}
});
emitter.on('err', (err) => {
self.log.error('Polling failed, error was %s', err);
});
self.api.once('shutdown', () => {
self.log.debug('Remove Polling Listeners for %s', self.name);
emitter.removeAllListeners();
});
}
}
async getTargetState(callback) {
var self = this;
if (self.polling) {
callback(null, self.riscoTargetState);
} else {
self.log.debug('Getting target state...');
self.getState(callback);
}
}
async setTargetState(state, callback) {
var self = this;
self.log.debug('Setting "%s" state to (%s) -> %s', self.name, state, self.translateState(state));
try {
var PartId;
var armedState;
const ArmedValues = {
'disarmed': 1,
'partially': 2,
'armed': 3
};
if (self.TypeOfAcc == 'group') {
if (state != self.riscoTargetState) {
self.log.debug('Actual state: ' + self.riscoCurrentState);
self.log.debug('Futur state: ' + state);
if ((state == 3) && (self.riscoCurrentState != 3)) {
self.log.debug('The system is armed and you want to disarm. Because RiscoCloud can not afford it, it is necessary to disarm the parent(s) partition.');
self.log.debug('All other child groups in this partition will also be disarmed.');
self.log.debug('Parent Part of %s: %s', (self.RiscoPartId || 'null'), JSON.stringify((self.RiscoSession.DiscoveredAccessories.Groups[self.RiscoPartId]).parentPart, JSONreplacer(), 4));
armedState = ArmedValues['disarmed'];
} else {
self.log.debug('Add Cmd: "%sG%s:armed"', self.RiscoPartId);
armedState = ArmedValues['armed'];
}
const ArmResp = await self.RiscoSession.armDisarm(self.RiscoPartId, armedState, true);
if (ArmResp) {
if (!self.polling) {
self.log.info('Group "%s" => Set new state: (%s) -> %s',self.name, state, self.translateState(state));
}
self.riscoCurrentState = state;
self.riscoTargetState = state;
self.mainService.updateCharacteristic(self.Characteristic.SecuritySystemCurrentState, self.riscoCurrentState);
callback !== null && typeof callback === 'function' && callback(null);
return;
} else {
//treat case when parentPart of group is already armed
self.log.debug('Error on armDisarm!!! Maybe a sensor is active and system cannot be armed')
throw new Error('Error on armDisarm!!!');
}
} else {
self.log.debug('Identical state. No change');
callback !== null && typeof callback === 'function' && callback(null);
return;
}
} else {
//if arm, away or night are customized, then pass occupancy verification
//Disable Occupancy Test While best method are develloped
if (!(self.RiscoSession.Custom_Cmd) && (this.OccupancyPreventArming)) {
switch (state) {
case 0:
case 2:
//If partition are not ready to partial arm, then don't change anything
if (!(self.RiscoSession.DiscoveredAccessories.Partitions[self.RiscoPartId].PReady)) {
state = ((self.riscoCurrentState == 4 ) ? self.riscoTargetState : self.riscoCurrentState);
self.mainService.updateCharacteristic(self.Characteristic.SecuritySystemTargetState, self.riscoTargetState);
callback !== null && typeof callback === 'function' && callback(null);
return;
}
break;
case 1:
//If partition are not ready to full arm, then don't change anything
if (!(self.RiscoSession.DiscoveredAccessories.Partitions[self.RiscoPartId].Ready)) {
state = ((self.riscoCurrentState == 4 ) ? self.riscoTargetState : self.riscoCurrentState);
self.mainService.updateCharacteristic(self.Characteristic.SecuritySystemTargetState, self.riscoTargetState);
callback !== null && typeof callback === 'function' && callback(null);
return;
}
break;
}
}
if (state != self.riscoTargetState) {
if (self.RiscoPartId != null ){
PartId = self.RiscoPartId;
} else {
PartId = 0;
}
if ((state != 3) && (self.riscoTargetState != 3)) {
self.log.debug('The system is already armed and you want to change the arming type. It is necessary to disarm the system beforehand.');
await self.setTargetState(3, null);
}
switch (state) {
case 0:
// Stay_Arm = 0
armedState = ArmedValues[self.RiscoSession.DiscoveredAccessories.Partitions[PartId].homeCommand];
break;
case 1:
// Away_Arm = 1
armedState = ArmedValues[self.RiscoSession.DiscoveredAccessories.Partitions[PartId].armCommand];
break;
case 2:
// Night_Arm = 2
armedState = ArmedValues[self.RiscoSession.DiscoveredAccessories.Partitions[PartId].nightCommand];
break;
case 3:
// Disarm = 3
armedState = ArmedValues[self.RiscoSession.DiscoveredAccessories.Partitions[PartId].disarmCommand];
break;
};
const [error, newState] = await self.GetSetArmState(self.RiscoPartId, armedState, state);
if (error !== null) {
throw new Error(error);
} else {
self.riscoCurrentState = newState;
self.riscoTargetState = newState;
self.mainService.updateCharacteristic(self.Characteristic.SecuritySystemCurrentState, self.riscoCurrentState);
callback !== null && typeof callback === 'function' && callback(null);
return;
}
}
callback !== null && typeof callback === 'function' && callback(null);
return;
}
} catch(err) {
self.log.error('Error on RiscoCPPartitions/setTargetState:\n%s', err);
callback !== null && typeof callback === 'function' && callback(err);
return;
}
}
async getCurrentState(callback) {
var self = this;
self.log.debug('Entering on RiscoCPPartitions/getCurrentState function');
try {
if (self.polling) {
callback(null, self.riscoCurrentState);
} else {
self.log.info('Partition "%s" =>Getting current state - delayed...', self.name);
waitUntil()
.interval(500)
.times(15)
.condition(function () {
return (self.riscoCurrentState ? true : false);
})
.done(async function (result) {
await self.RiscoSession.getCPStates();
await self.getRefreshState(callback);
if (self.TypeOfAcc == 'group') {
self.log.info('Partition "%s" => Actual state is: (%s) -> %s', self.name, self.riscoCurrentState, self.translateState(self.riscoCurrentState));
} else {
self.log.info('Group "%s" => Actual state is: (%s) -> %s',self.name, self.riscoCurrentState, self.translateState(self.riscoCurrentState));
}
self.mainService.updateCharacteristic(self.Characteristic.SecuritySystemCurrentState, self.riscoCurrentState);
self.mainService.updateCharacteristic(self.Characteristic.SecuritySystemTargetState, self.riscoTargetState);
return
});
}
} catch (err) {
self.log.error('Error on RiscoCPPartitions/getCurrentState:\n%s', err);
self.mainService.updateCharacteristic(self.Characteristic.SecuritySystemCurrentState, self.riscoCurrentState);
callback(null, self.riscoCurrentState);
return;
}
}
async getCurrentOccupancyState(callback) {
var self = this;
try {
if (self.polling) {
callback(null, self.OccupancyState);
} else {
self.log.info('Partition "%s" =>Getting Occupancy current state - delayed...', self.name);
waitUntil()
.interval(500)
.times(15)
.condition( () => {
return self.OccupancyState;
})
.done(async function (result) {
await self.RiscoSession.getCPStates();
await self.getRefreshState(callback);
self.log.info('Partition "%s" => Actual Occupancy state is: (%s) -> %s', self.name, self.OccupancyState, ((self.OccupancyState == 0) ? 'Not Occupied':'Occupied'));
self.OccupancyService.updateCharacteristic(self.Characteristic.OccupancyDetected, self.OccupancyState);
return;
});
}
} catch (err) {
self.log.error('Error on RiscoCPPartitions/getCurrentOccupancyState:\n%s', err);
self.OccupancyService.updateCharacteristic(self.Characteristic.OccupancyDetected, self.OccupancyState);
callback(null, self.OccupancyState);
return;
}
}
async getState(callback) {
var self = this;
try {
await self.getRefreshState(callback);
} catch (err) {
self.log.error('Error on RiscoCPPartitions/getState:\n%s', err);
callback(null, self.riscoCurrentState);
return;
}
}
async GetSetArmState(partId, armedState, state) {
var self = this;
const [ArmResp, ArmReason] = await self.RiscoSession.armDisarm(partId, armedState);
switch (ArmResp) {
case 0:
self.log.debug('Error on armDisarm!!! Maybe a sensor is active and system cannot be armed');
return [null, self.riscoCurrentState];
break;
case 1:
if (!self.polling) {
self.log.info('Partition "%s" => Set new state: (%s) -> %s',self.name, state, self.translateState(state));
}
self.mainService.updateCharacteristic(self.Characteristic.SecuritySystemCurrentState, state);
return [null, state];
break;
case 2:
const ArmDelay = ArmReason;
self.log.debug('The partition will be armed in %s milliseconds', ArmDelay);
self.log.error('The partition will be armed in %s milliseconds', ArmDelay);
setTimeout(() => {self.RiscoSession.UpdateCPStates}, ArmDelay);
if (!self.polling) {
self.log.debug('Partition "%s" State will be refreshed in %s milliseconds', self.name, ArmDelay);
}
return [null, self.riscoCurrentState];
break;
}
}
async getRefreshState(callback) {
var self = this;
try {
const PGsStatesRegistry = {
armed: 1,
partial: 2,
disarmed: 3,
};
var Datas = [];
var ItemStates;
if (self.TypeOfAcc == 'group') {
for (var Group in self.RiscoSession.DiscoveredAccessories.Groups) {
if (Group != 'type') {
Datas.push(self.RiscoSession.DiscoveredAccessories.Groups[Group]);
}
}
ItemStates = Datas.filter(groups => groups.Id == (self.RiscoPartId | ''));
} else {
for (var Parts in self.RiscoSession.DiscoveredAccessories.Partitions) {
if (Parts != 'type') {
Datas.push(self.RiscoSession.DiscoveredAccessories.Partitions[Parts]);
}
}
ItemStates = Datas.filter(parts => parts.Id == (self.RiscoPartId | ''));
}
if (ItemStates.length != 0) {
self.OccupancyState = ((ItemStates[0].Ready)? 0 : 1 );
self.riscoCurrentState = PGsStatesRegistry[ItemStates[0].actualState];
self.riscoTargetState = self.riscoCurrentState;
if (ItemStates[0].OnAlarm == true) {
self.mainService.updateCharacteristic(self.Characteristic.SecuritySystemCurrentState, 4);
self.riscoCurrentState = 4;
}
self.mainService.updateCharacteristic(self.Characteristic.SecuritySystemCurrentState, self.riscoCurrentState);
self.mainService.updateCharacteristic(self.Characteristic.SecuritySystemTargetState, self.riscoTargetState);
if (this.TypeOfAcc == 'partition') {
if (this.OccupancyPreventArming) {
self.OccupancyService.updateCharacteristic(self.Characteristic.OccupancyDetected, self.OccupancyState);
}
typeof callback === 'function' && callback(null, [self.riscoCurrentState, self.riscoTargetState, self.OccupancyState]);
} else {
typeof callback === 'function' && callback(null, [self.riscoCurrentState, self.riscoTargetState, null]);
}
return;
} else {
throw new Error('Error on RefreshState!!!');
}
} catch (err) {
self.log.error('Error on RiscoCPPartitions/getRefreshState:\n%s', err);
if (this.TypeOfAcc == 'partition') {
typeof callback === 'function' && callback(null, [self.riscoCurrentState, self.riscoTargetState, self.OccupancyState]);
} else {
typeof callback === 'function' && callback(null, [self.riscoCurrentState, self.riscoTargetState, null]);
}
return;
}
}
}
class RiscoCPGroups extends RiscoCPPartitions {
constructor(log, accConfig, api, accessory) {
super(log, accConfig, api, accessory, 'group');
}
}
class RiscoCPOutputs {
constructor(log, accConfig, api, accessory) {
this.log = log;
this.name = accConfig.context.name;
this.RiscoSession = accConfig.RiscoSession;
this.RiscoOutputId = accConfig.context.Id;
this.TypePulse = ( () => {
if (accConfig.context.Type == 'pulse') {
return true;
} else {
return false;
}
})();
this.polling = accConfig.polling || false;
this.pollInterval = accConfig.pollInterval || 30000;
this.accessory = accessory;
this.api = api;
this.Service = this.api.hap.Service;
this.Characteristic = this.api.hap.Characteristic;
this.mainService = this.accessory.getService(this.Service.Switch, this.accessory.displayName);
this.mainService
.getCharacteristic(this.Characteristic.On)
.on('get', this.getCurrentState.bind(this))
.on('set', this.setTargetState.bind(this));
// Default Value
this.log.debug('Output "%s" default State: %s', this.name, accConfig.context.State);
this.RiscoOutputState = accConfig.context.State;
this.IsPulsed = false;
this.long_event_name = `long_out_${this.RiscoOutputId}_${(this.name.toLowerCase()).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/ /g, '_')}`;
//avoid maxlistener warning
const MaxApiListeners = this.api.getMaxListeners();
const ActualListeners = this.api.listenerCount('shutdown');
this.log.debug('Api Event Shutdown : \nActual Listener :%s for Maximum :%s',this.api.listenerCount('shutdown'),MaxApiListeners);
if (ActualListeners >= MaxApiListeners) {
//give one more for other process
this.api.setMaxListeners(ActualListeners + 2);
this.log.debug('Max Listener Exceeded. Set To :%s', (ActualListeners + 2));
}
this.api.once('shutdown', () => {
this.log.debug('Cleaning Before Exit.\nRemove All Listeners for %s', this.name);
this.mainService
.getCharacteristic(this.Characteristic.On)
.removeListener('get', this.getCurrentState)
.removeListener('set', this.setTargetState);
});
this.PollingLoop();
}
PollingLoop() {
var self = this;
if (self.TypePulse !== true) {
// set up polling if requested
if (self.polling) {
var emitter = new pollingtoevent( (done) => {
self.getRefreshState( (err, result) => {
done(err, result);
});
}, {
longpollEventName: self.long_event_name,
longpolling: true,
interval: 1000
});
emitter.on(self.long_event_name, (state) => {
self.log.info('Output "%s" => New state detected: (%s). Notify!', self.name, state);
self.RiscoOutputState = state;
self.mainService.updateCharacteristic(self.Characteristic.On, self.RiscoOutputState);
});
emitter.on('err', (err) => {
self.log.error('Polling failed, error was %s', err);
});
self.api.once('shutdown', () => {
self.log.debug('Remove Polling Listeners for %s', self.name);
emitter.removeAllListeners();
});
}
}
}
async getRefreshState(callback) {
var self = this;
try {
var Datas = [];
var ItemStates;
for (var Output in self.RiscoSession.DiscoveredAccessories.Outputs) {
Datas.push(self.RiscoSession.DiscoveredAccessories.Outputs[Output]);
}
ItemStates = Datas.filter(outputs => (outputs.Id == (self.RiscoOutputId | '')));
if (ItemStates.length != 0) {
self.RiscoOutputState = ((ItemStates[0].State == 0) ? false : true);
self.mainService.updateCharacteristic(self.Characteristic.On, self.RiscoOutputState);
callback(null, self.RiscoOutputState);
return;
} else {
throw new Error('Error on Output RefreshState!!!');
}
} catch (err) {
self.log.error('Error on RiscoCPOutputs/getRefreshState:\n%s', err);
callback(null, self.RiscoOutputState);
return;
}
}
async getCurrentState(callback) {
var self = this;
try {
if (self.polling) {
self.log.debug('Output is %s', self.RiscoOutputState);
if (self.TypePulse === false) {
callback(null, self.RiscoOutputState);
return;
} else {
callback(null, false);
return;
}
} else {
if (self.TypePulse === false) {
self.log.info('Output "%s" => Getting current state - delayed...', self.name);
waitUntil()
.interval(500)
.times(15)
.condition(function () {
return (self.RiscoOutputState ? true : false);
})
.done(async function (result) {
await self.RiscoSession.getCPStates();
await self.getRefreshState(callback);
self.log.debug('Output "%s" => Actual state is: (%s)', self.name, self.RiscoOutputState);
self.mainService.updateCharacteristic(self.Characteristic.On, self.RiscoOutputState);
return;
});
} else {
callback(null, false);
}
}
} catch (err) {
self.log.error('Error on RiscoCPOutputs/getCurrentState:\n%s', err);
self.mainService.updateCharacteristic(self.Characteristic.On, self.RiscoOutputState);
callback(null, self.RiscoOutputState);
return;
}
}
async setTargetState(state, callback) {
var self = this;
self.log.debug('Set Output to: ' + state);
self.RiscoOutputState = state;
const deviceType = ((self.TypePulse) ? 2 : 1 );
const lastCommand = ((self.TypePulse) ? 1 : ((state) ? 1 : 0 ));
const deviceId = self.RiscoOutputId;
const HACResp = await self.RiscoSession.OutputCommand(deviceType, lastCommand, deviceId);
if (HACResp){
if (!self.polling) {
self.log.info('Output "%s" => Set new state: (%s)', self.name, state);
}
if (self.TypePulse === false) {
self.log.debug('Not a pulse switch, update it')
self.RiscoOutputState = state;
typeof callback === 'function' && callback(null);
} else {
if (self.IsPulsed) {
self.log.debug('Pulse switch is already pulsed');
self.IsPulsed = false;
typeof callback === 'function' && callback(null);
} else {
self.log.debug('Pulse switch is not already pulsed');
self.IsPulsed = true;
setTimeout(self.ResetPulseSwitchState, 500, self);
self.RiscoOutputState = false;
typeof callback === 'function' && callback(null);
}
}
} else {
self.log.error('Error on HACommand!!!');
typeof callback === 'function' && callback(null);
}
}
async ResetPulseSwitchState(self) {
var self = self;
self.log.debug('Reset Pulse Switch State to %s', self.RiscoOutputState);
self.mainService.updateCharacteristic(self.Characteristic.On, self.RiscoOutputState);
}
}
class RiscoCPBaseDetectors {
constructor(log, accConfig, api, accessory) {
this.log = log;
this.name = accConfig.context.name;
this.RiscoSession = accConfig.RiscoSession;
this.Type = accConfig.context.accessorytype;
this.RiscoDetectorId = accConfig.context.Id;
this.polling = accConfig.polling || false;
this.pollInterval = accConfig.pollInterval || 30000;
this.accessory = accessory;
this.api = api;
this.Service = this.api.hap.Service;
this.Characteristic = this.api.hap.Characteristic;
this.secondCharac = undefined;
this.SetServicesAccessory();
this.SetExcludeServicesAccessory();
this.DefineAccessoryVariable();
this.DetectorReady = false;
// Default Value
this.log.debug('%s "%s" default State: %s',this.sPrefix, this.name, accConfig.context.State);
this.RiscoDetectorState = accConfig.context.State;
this.RiscoDetectorBypassState = accConfig.context.Bypassed;
this.long_event_name = `long_det_${this.RiscoDetectorId}_${(this.name.toLowerCase()).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/ /g, '_')}`;
//avoid maxlistener warning
const MaxApiListeners = this.api.getMaxListeners();
const ActualListeners = this.api.listenerCount('shutdown');
this.log.debug('Api Event Shutdown : \nActual Listener :%s for Maximum :%s',this.api.listenerCount('shutdown'),MaxApiListeners);
if (ActualListeners >= MaxApiListeners) {
//give one more for other process
this.api.setMaxListeners(ActualListeners + 2);
this.log.debug('Max Listener Exceeded. Set To :%s', (ActualListeners + 2));
}
this.api.once('shutdown', () => {
this.log.debug('Cleaning Before Exit.\nRemove All Listeners for %s', this.name);
this.removemainListeners();
this.removeExcludeListeners();
});
this.PollingLoop();
}
PollingLoop() {
var self = this;
// set up polling if requested
if (self.polling) {
var emitter = new pollingtoevent( (done) => {
self.getRefreshState( (err, result) => {
done(err, result);
});
}, {
longpollEventName: self.long_event_name,
longpolling: true,
interval: 1000
});
emitter.on(self.long_event_name, (state) => {
self.log.info('%s "%s" => New state detected: (%s). Notify!', self.sPrefix, self.name, self.GetAccessoryState(state[0], false));
self.log.info('%s "%s" => New Bypass State detected : (%s). Notify!', self.sPrefix, self.name, ((state[1]) ? 'ByPassed' : 'Not ByPassed'));
self.ReportAccessoryState(state);
});
emitter.on('err', (err) => {
self.log.error('Polling failed, error was %s', err);
});
self.api.once('shutdown', () => {
self.log.debug('Remove All Listeners for %s', self.name);
emitter.removeAllListeners();
});
}
}
SetExcludeServicesAccessory() {
var self = this;
self.log.debug('Adding Exclude Switch to %s', this.name);
this.ExcludeService = this.accessory.getService(this.Service.Switch, this.accessory.displayName);
this.ExcludeService
.getCharacteristic(this.Characteristic.On)
.on('get', this.getCurrentExcludeState.bind(this))
.on('set', this.setCurrentExcludeState.bind(this));
}
removeExcludeListeners() {
this.ExcludeService
.getCharacteristic(this.Characteristic.On)
.removeListener('get', this.getCurrentExcludeState)
.removeListener('set', this.setCurrentExcludeState);
}
GetAccessoryState(state, AsHomeKitValue = true) {
/*
Adapt the status of the accessory according to the response expected by Homekit according to the type of accessory
*/
var self = this;
if (AsHomeKitValue) {
return ((state) ? self.ActiveValue : self.InactiveValue);
} else {
return ((state) ? self.ActiveStr : self.InactiveStr);
}
}
ReportAccessoryState(state = null) {
var self = this;
if (state != null) {
self.RiscoDetectorState = state[0];
self.RiscoDetectorBypassState = state[1];
}
try {
self.mainService.updateCharacteristic(self.mainCharac, self.GetAccessoryState(self.RiscoDetectorState));
if (self.secondCharac !== undefined) {
self.mainService.updateCharacteristic(self.secondCharac, self.GetAccessoryState(self.RiscoDetectorState));
}
self.ExcludeService.updateCharacteristic(self.Characteristic.On, self.RiscoDetectorBypassState);
return;
} catch (err) {
self.log.error('Error on RiscoBaseDetectors/ReportAccessoryState:\n%s', err);
return;
}
}
async getRefreshState(callback) {
var self = this;
try {
var Datas = [];
var ItemStates;
for (var Detector in self.RiscoSession.DiscoveredAccessories.Detectors) {
Datas.push(self.RiscoSession.DiscoveredAccessories.Detectors[Detector]);
}
ItemStates = Datas.filter(detectors => (detectors.Id == (self.RiscoDetectorId | '')));
if (ItemStates.length != 0) {
self.RiscoDetectorState = ItemStates[0].State;
self.RiscoDetectorBypassState = ItemStates[0].Bypassed;
self.DetectorReady = true;
self.ReportAccessoryState();
callback(null, [self.RiscoDetectorState, self.RiscoDetectorBypassState]);
return;
} else {
throw new Error(`Error on ${self.sPrefix} RefreshState!!!`);
}
} catch (err) {
self.log.error('Error on RiscoCPBaseDetectors/getRefreshState:\n%s', err);
self.DetectorReady = true;
callback(null, [self.RiscoDetectorState, self.RiscoDetectorBypassState]);
return;
}
}
async getCurrentState(callback) {
var self = this;
try {
if (self.polling) {
self.log.debug('%s "%s" MotionDetected: %s', self.sPrefix, self.name, self.GetAccessoryState(self.RiscoDetectorState, false));
callback(null, self.RiscoDetectorState);
return;
} else {
self.log.info('%s "%s" => Getting current state - delayed...', self.sPrefix, self.name);
waitUntil()
.interval(500)
.times(15)
.condition( () => {
return (self.RiscoDetectorState ? true : false);
})
.done(async function (result) {
await self.RiscoSession.getCPStates();
await self.getRefreshState(callback);
self.log.debug('%s "%s" => Actual Motion state is: (%s)', self.sPrefix, self.name, self.GetAccessoryState(self.RiscoDetectorState, false));
self.DetectorReady = true;
self.ReportAccessoryState();
return
});
}
} catch (err) {
self.log.error('Error on RiscoCPBaseDetectors/getCurrentState:\n%s', err);
self.ReportAccessoryState();
callback(null, self.RiscoDetectorState);
return;
}
}
async getCurrentExcludeState(callback) {
var self = this;
try {
if (self.polling) {
self.log.debug('%s "%s" Exclude State : (%s) => %s', self.sPrefix, self.name, self.RiscoDetectorBypassState, ((self.RiscoDetectorBypassState) ? 'Bypassed': 'Not Bypassed'));
callback(null, (self.RiscoDetectorBypassState)?false : true);
return;
} else {
self.log.info('%s "%s" => Getting current state - delayed...', self.sPrefix, self.name);
waitUntil()
.interval(500)
.times(15)
.condition( () => {
return self.RiscoDetectorBypassState;
})
.done(async function (result) {
await self.RiscoSession.getCPStates();
await self.getRefreshState(callback);
self.log.debug('%s "%s" => Actual Exclude State is: %s', self.sPrefix, self.name, ((self.RiscoDetectorBypassState) ? 'Bypassed': 'Not Bypassed'));
self.DetectorReady = true;
return;
});
}
} catch (err) {
self.log.error('Error on RiscoCPBaseDetectors/getCurrentExcludeState:\n%s', err);
callback(err, (self.RiscoDetectorBypassState)?false : true);
return;
}
}
async setCurrentExcludeState(state, callback) {
var self = this;
try {
if (self.DetectorReady) {
const PartId = self.RiscoSession.DiscoveredAccessories.Detectors[self.RiscoDetectorId].Partition;
const PartStatus = self.RiscoSession.DiscoveredAccessories.Partitions[PartId].actualState
var SBpResp;
if (PartStatus != 'disarmed') {
self.log.info('Cannot Modify Exclude State of Sensor from Armed Partition');
typeof callback === 'function' && callback('Cannot Modify Exclude State of Sensor from Armed Partition', 'Cannot Modify Exclude State of Sensor from Armed Partition');
self.ExcludeService.updateCharacteristic(self.Characteristic.On, self.RiscoDetectorBypassState);
return;
}
self.log.debug('Set Exclude State of %s "%s" to: %s', self.sPrefix, self.name, ((state) ? 'Bypassed': 'Not Bypassed'));
self.log.debug('%s Actual State: %s', self.name, ((self.RiscoDetectorBypassState) ? 'Bypassed': 'Not Bypassed'));
self.log.debug('%s New State: %s', self.name, ((state) ? 'Bypassed': 'Not Bypassed'));
if (self.RiscoDetectorBypassState == state) {
SBpResp = true;
self.log.debug('%s Identical State', self.name);
} else {
SBpResp = await self.RiscoSession.SetBypass(((state) ? 2 : 3), self.RiscoDetectorId);
self.log.debug('%s Different State', self.name);
}
if (SBpResp) {
if (!self.polling) {
self.log.info('%s "%s" => Set new Bypass state: (%s)', self.sPrefix, self.name, state);
}
typeof callback === 'function' && callback(null);
} else {
self.log.error('Error on SetBypass!!!');
typeof callback === 'function' && callback('Error on SetBypass!!!');
}
return;
}
} catch (err) {
self.log.error('Error on RiscoCPBaseDetectors/setCurrentExcludeState:\n%s', err);
self.ExcludeService.updateCharacteristic(self.Characteristic.On, self.RiscoDetectorBypassState);
callback(err);
return;
}
}
}
class RiscoCPDetectors extends RiscoCPBaseDetectors {
constructor(log, accConfig, api, accessory) {
super(log, accConfig, api, accessory);
}
SetServicesAccessory() {
this.mainService = this.accessory.getService(this.Service.MotionSensor, this.accessory.displayName);
this.mainService
.getCharacteristic(this.Characteristic.MotionDetected)
.on('get', this.getCurrentState.bind(this));
this.sPrefix = 'Detector';
}
DefineAccessoryVariable() {
this.mainCharac = this.Characteristic.MotionDetected;
this.ActiveValue = true;
this.InactiveValue = false;
this.ActiveStr = 'Active';
this.InactiveStr = 'Inactive';
}
removemainListeners() {
this.mainService
.getCharacteristic(this.Characteristic.MotionDetected)
.removeListener('get', this.getCurrentState);
}
}
class RiscoCPCDoor extends RiscoCPBaseDetectors {
constructor (log, accConfig, api, accessory) {
super(log, accConfig, api, accessory);
}
SetServicesAccessory() {
this.mainService = this.accessory.getService(this.Service.Door, this.accessory.displayName);
this.mainService
.getCharacteristic(this.Characteristic.CurrentPosition)
.on('get', this.getCurrentState.bind(this));
this.sPrefix = 'Door Contact';
}
DefineAccessoryVariable() {
this.mainCharac = this.Characteristic.CurrentPosition;
this.secondCharac = this.Characteristic.TargetPosition;
this.ActiveValue = 100;
this.InactiveValue = 0;
this.ActiveStr = 'open';
this.InactiveStr = 'closed';
}
removemainListeners() {
this.mainService
.getCharacteristic(this.Characteristic.CurrentPosition)
.removeListener('get', this.getCurrentState);
}
}
class RiscoCPCWindow extends RiscoCPBaseDetectors {
constructor(log, accConfig, api, accessory) {
super(log, accConfig, api, accessory);
}
SetServicesAccessory() {
this.mainService = this.accessory.getService(this.Service.Window, this.accessory.displayName);
this.mainService
.getCharacteristic(this.Characteristic.CurrentPosition)
.on('get', this.getCurrentState.bind(this));
this.sPrefix = 'Window Contact';
}
DefineAccessoryVariable() {
this.mainCharac = this.Characteristic.CurrentPosition;
this.secondCharac = this.Characteristic.TargetPosition;
this.ActiveValue = 100;
this.InactiveValue = 0;
this.ActiveStr = 'open';
this.InactiveStr = 'closed';
}
removemainListeners() {
this.mainService
.getCharacteristic(this.Characteristic.CurrentPosition)
.removeListener('get', this.getCurrentState);
}
}
class RiscoCPCContactSensor extends RiscoCPBaseDetectors {
constructor(log, accConfig, api, accessory) {
super(log, accConfig, api, accessory);
}
SetServicesAccessory() {
this.mainService = this.accessory.getService(this.Service.ContactSensor, this.accessory.displayName);
this.mainService
.getCharacteristic(this.Characteristic.ContactSensorState)
.on('get', this.getCurrentState.bind(this));
this.sPrefix = 'Contact Sensor';
}
DefineAccessoryVariable() {
this.mainCharac = this.Characteristic.ContactSensorState;
this.ActiveValue = true;
this.InactiveValue = false;
this.ActiveStr = 'Active';
this.InactiveStr = 'Inactive';
}
removemainListeners() {
this.mainService
.getCharacteristic(this.Characteristic.ContactSensorState)
.removeListener('get', this.getCurrentState);
}
}
class RiscoCPCVibrateSensor extends RiscoCPBaseDetectors {
constructor(log, accConfig, api, accessory) {
super(log, accConfig, api, accessory);
}
SetServicesAccessory() {
this.mainService = this.accessory.getService(this.Service.MotionSensor, this.accessory.displayName);
this.mainService
.getCharacteristic(this.Characteristic.MotionDetected)
.on('get', this.getCurrentState.bind(this));
this.sPrefix = 'Vibrate Sensor';
}
DefineAccessoryVariable() {
this.mainCharac = this.Characteristic.MotionDetected;
this.ActiveValue = true;
this.InactiveValue = false;
this.ActiveStr = 'Active';
this.InactiveStr = 'Inactive';
}
removemainListeners() {
this.mainService
.getCharacteristic(this.Characteristic.MotionDetected)
.removeListener('get', this.getCurrentState);
}
}
class RiscoCPCSmokeSensor extends RiscoCPBaseDetectors {
constructor (log, accConfig, api, accessory) {
super(log, accConfig, api, accessory);
}
SetServicesAccessory() {
this.mainService = this.accessory.getService(this.Service.SmokeSensor, this.accessory.displayName);
this.mainService
.getCharacteristic(this.Characteristic.SmokeDetected)
.on('get', this.getCurrentState.bind(this));
this.sPrefix = 'Smoke Sensor';
}
DefineAccessoryVariable() {
this.mainCharac = this.Characteristic.SmokeDetected;
this.ActiveValue = 1;
this.InactiveValue = 0;
this.ActiveStr = 'Active';
this.InactiveStr = 'Inactive';
}
removemainListeners() {
this.mainService
.getCharacteristic(this.Characteristic.SmokeDetected)
.removeListener('get', this.getCurrentState);
}
}
class RiscoCPCombDevices {
constructor(log, accConfig, api, accessory) {
this.log = log;
this.name = accConfig.context.name;
this.RiscoSession = accConfig.RiscoSession;
this.Type = accConfig.context.accessorytype;
this.RiscoCombinedId = accConfig.context.Id;
this.RiscoInId = accConfig.context.InId;
this.RiscoOutId = accConfig.context.OutId;
this.OpeningMode = 'startstopstart';
this.Moving = false;
this.MovingDelai = 20000;
this.MovingTimeStep = 1000;
this.MovingTimePosition = 0;
this.TypePulse = ( () => {
if (accConfig.context.OutType == 'pulse') {
return true;