node-red-node-ademco
Version:
A Node-RED node to Parse Ademco Panel Messages.
314 lines (254 loc) • 8.16 kB
JavaScript
/**
* Copyright 2015 Jay Long
*
* @JayLong https://github.com/jlong23
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
module.exports = function(RED) {
"use strict";
var CurrentState = new Array(2);
var bitPositions = {
BIT_READY : 0,
BIT_ARMED_AWAY : 1,
BIT_ARMED_STAY : 2,
BIT_BACKLIGHT : 3,
BIT_PROGRAMMING : 4,
BIT_BEEP_COUNT : 5,
BIT_ZONE_BYPASS : 6,
BIT_AC_POWER : 7,
BIT_CHIME_ENABLED : 8,
BIT_ALARM_STICKY : 9,
BIT_ALARM_SOUNDER : 10,
BIT_BAT_LOW : 11,
BIT_ENTRY_DELAY_OFF : 12,
BIT_FIRE : 13,
BIT_ZONE_FAULT : 14,
BIT_PERIMETER_ONLY : 15
};
var AlarmStateEnum = {
DISARMED : 0,
NOT_READY : 1,
READY : 2,
ARMED_AWAY : 3,
ARMED_STAY : 4,
ARMED_INSTANT : 5,
ALARM : 6,
FIRE_ALARM : 7
};
function AdemcoListener(n) {
RED.nodes.createNode(this, n);
this.config = RED.nodes.getNode(n.config);
var node = this;
if (CurrentState === null) {
CurrentState = new Array(2);
}
node.status({});
node.on( "input", function(msg) {
this.report = n.report || msg.report || "all";
var payload = "";
if( typeof msg.payload == 'object' ) {
payload = msg.payload.toString();
} else {
payload = msg.payload;
}
if( payload !== undefined && payload.length > 0 && payload.substring(0,1) == '[' ) {
var segments = payload.split(',');
var panels = parsePanels(segments[2]);
var partitionNum = 1;
for( var i = 0; i < panels.length; i ++ ) {
if( panels[i] === 17 ) {
partitionNum = 2;
}
}
var AlarmPartitionState = getPartition( CurrentState, partitionNum );
AlarmPartitionState.lastUpdated = new Date();
AlarmPartitionState.panels = panels;
// Make a backup of the Current Partition State
AlarmPartitionState.lastSystemState = AlarmPartitionState.systemState;
var bitsRaw = segments[0];
for (var i = 1; i < 16; i++) {
var value = (bitsRaw.substring(i, i + 1) == '1');
switch (i - 1) {
case bitPositions.BIT_READY:
AlarmPartitionState.partitionReady = value;
break;
case bitPositions.BIT_ARMED_AWAY:
AlarmPartitionState.partitionArmedAway = value;
break;
case bitPositions.BIT_ARMED_STAY:
AlarmPartitionState.partitionArmedStay = value;
break;
case bitPositions.BIT_BACKLIGHT:
AlarmPartitionState.backlight = value;
break;
case bitPositions.BIT_PROGRAMMING:
AlarmPartitionState.programmingMode = value;
break;
case bitPositions.BIT_BEEP_COUNT:
AlarmPartitionState.beepCount = bitsRaw
.substring(i, i + 1);
break;
case bitPositions.BIT_ZONE_BYPASS:
AlarmPartitionState.zoneBypass = value;
break;
case bitPositions.BIT_AC_POWER:
AlarmPartitionState.linePower = value;
break;
case bitPositions.BIT_CHIME_ENABLED:
AlarmPartitionState.chimeEnabled = value;
break;
case bitPositions.BIT_ALARM_STICKY:
AlarmPartitionState.alarmOccurred = value;
break;
case bitPositions.BIT_ALARM_SOUNDER:
AlarmPartitionState.alarmSounding = value;
break;
case bitPositions.BIT_BAT_LOW:
AlarmPartitionState.batteryLow = value;
break;
case bitPositions.BIT_ENTRY_DELAY_OFF:
AlarmPartitionState.delayOff = value;
break;
case bitPositions.BIT_FIRE:
AlarmPartitionState.fireAlarm = value;
break;
case bitPositions.BIT_ZONE_FAULT:
AlarmPartitionState.zoneFaulted = value;
break;
case bitPositions.BIT_PERIMETER_ONLY:
AlarmPartitionState.perimeterOnly = value;
break;
default:
break;
}
// Update the Current Partition State
// By Default, the System/Partition is Disarmed
AlarmPartitionState.systemState = AlarmStateEnum.DISARMED;
// Disabled, spams quite a bit as zones go in an
// out of ready state
if (!AlarmPartitionState.partitionReady) {
AlarmPartitionState.systemState = AlarmStateEnum.NOT_READY;
} else {
AlarmPartitionState.systemState = AlarmStateEnum.READY;
}
if (AlarmPartitionState.partitionArmedAway) {
AlarmPartitionState.systemState = AlarmStateEnum.ARMED_AWAY;
}
if (AlarmPartitionState.partitionArmedStay) {
AlarmPartitionState.systemState = AlarmStateEnum.ARMED_STAY;
}
if (AlarmPartitionState.partitionArmedStay
&& AlarmPartitionState.delayOff) {
AlarmPartitionState.systemState = AlarmStateEnum.ARMED_INSTANT;
}
if (AlarmPartitionState.alarmSounding) {
AlarmPartitionState.systemState = AlarmStateEnum.ALARM;
}
if (AlarmPartitionState.fireAlarm) {
AlarmPartitionState.systemState = AlarmStateEnum.FIRE_ALARM;
}
if (AlarmPartitionState.lastSystemState != AlarmPartitionState.systemState) {
AlarmPartitionState.lastTransitionDate = AlarmPartitionState.lastUpdated;
AlarmPartitionState.stateChange = true;
} else {
AlarmPartitionState.stateChange = false;
}
}
AlarmPartitionState.messageLine1 = segments[3].substring(1, 17).trim();
AlarmPartitionState.messageLine2 = segments[3].substring(17, 32).trim();
CurrentState[partitionNum] = AlarmPartitionState;
var sendMessage = false;
if( this.report === "state" ) {
if( AlarmPartitionState.stateChange ) {
sendMessage = true;
}
} else {
sendMessage = true;
}
if( sendMessage ) {
msg.topic = "iot/evt/alarm/fmt/json";
msg.payload = AlarmPartitionState;
node.send(msg);
}
node.status({});
}
});
}
RED.nodes.registerType("AdemcoListener", AdemcoListener);
function getPartition( systemState, partitionNumber ) {
var partitionState ={
partitionNumber : 1,
messageLine1 : "",
messageLine2 : "",
lastUpdated : null,
systemState : AlarmStateEnum.NOT_READY,
lastTransitionDate : null,
lastSystemState : AlarmStateEnum.NOT_READY,
panels : [],
zoneFaults : [],
partitionReady : false,
partitionArmedAway : false,
partitionArmedStay : false,
backlight : false,
programmingMode : false,
beepCount : 0,
zoneBypass : false,
linePower : false,
chimeEnabled : false,
alarmOccurred : false,
alarmSounding : false,
batteryLow : false,
delayOff : false,
fireAlarm : false,
zoneFaulted : false,
perimeterOnly : false,
stateChange : false
};
partitionState.partitionNumber = partitionNumber;
if( systemState[partitionNumber] != null ) {
partitionState = systemState[partitionNumber];
}
return partitionState;
}
function parsePanels(data) {
var panels = [];
var parsedMask = "11111111111111111111111111111111";
var padMask = "00000000000000000000000000000000";
var parsedEvent = data;
if (parsedEvent !== undefined && parsedEvent.length > 12) {
parsedMask = parseInt(parsedEvent.substring(3, 11), 16).toString(2);
}
var panelMaskBitmap = (padMask + parsedMask)
.substring(parsedMask.length);
for (var x = 0; x < panelMaskBitmap.length; x++) {
if (panelMaskBitmap.substring(x, x + 1) == "1") {
panels.push(x - 5);
}
}
return panels;
}
function AdemcoStatus(n) {
RED.nodes.createNode(this, n);
this.config = RED.nodes.getNode(n.config);
var node = this;
node.status({});
node.on( "input", function(msg) {
this.partition = msg.partition || n.partition || "1";
msg.topic = "iot/evt/alarm/fmt/json";
msg.payload = CurrentState[this.partition];
node.send(msg);
});
}
RED.nodes.registerType("AdemcoStatus", AdemcoStatus);
}