thing-it-device-enocean-ip
Version:
[thing-it-node] Device Plugin for EnOcean IP products.
380 lines (325 loc) • 13.9 kB
JavaScript
module.exports = {
metadata: {
plugin: "occupancySensor",
label: "EnOcean IP PIR Sensor",
role: "sensor",
family: "occupancySensor",
deviceTypes: ["enocean-ip/gateway"],
dataTypes: {
sensorType: {
family: "enumeration",
values: [{
id: "SOLARSENSOR",
label: "Solar Sensor"
}, {
id: "BATTERYSENSOR",
label: "Battery Sensor"
}]
}
},
services: [],
events: [{
id: "motionDetected",
label: "Motion detected"
}, {
id: "tic",
label: "Motion Tic"
}, {
id: "noMoreMotion",
label: "No more Motion"
}],
state: [{
label: "Occupied",
id: "occupied",
type: {
id: "boolean"
}
}, {
label: "Ticks/Minute",
id: "ticksPerMinute",
type: {
id: "integer"
}
}, {
label: "Last Motion Timestamp",
id: "lastMotionTimestamp",
type: {
id: "string"
}
}],
configuration: [{
label: "Device ID",
id: "deviceId",
type: {
id: "string"
}
}, {
label: "Ghost Ticks Interval",
id: "ghostTickInterval",
type: {
id: "integer"
},
defaultValue: 2,
unit: "s"
}, {
label: "Release Time",
id: "releaseTime",
type: {
id: "integer"
},
defaultValue: 240,
unit: "s"
}, {
label: "Ticks Counting Interval",
id: "tickCountTimeMinutes",
type: {
id: "integer"
},
defaultValue: 5,
}, {
label: "Sensor Type",
id: "sensorType",
type: {
family: "reference",
id: "sensorType"
},
defaultValue: "SOLARSENSOR"
}]
},
create: function () {
return new OccupancySensor();
}
};
var q = require('q');
var moment = require('moment');
/**
*
*/
function OccupancySensor() {
/**
*
*/
OccupancySensor.prototype.start = function () {
var deferred = q.defer();
// this.logLevel = 'debug';
this.state = {occupied: false, ticksPerMinute: 0};
this.ticksTimestampsArray = [];
this.ticCountArray = [];
this.lastProcessedTS = Date.now();
this.lastReceivedTS = Date.now();
if (!this.configuration.ghostTickInterval) {
this.configuration.ghostTickInterval = 2;
}
if (this.isSimulated()) {
this.interval = setInterval(function () {
this.state.occupied = !this.state.occupied;
if (this.state.occupied) {
this.state.ticksPerMinute = Math.round(Math.random() * 10);
this.state.lastMotionTimestamp = moment().toISOString();
} else {
this.state.ticksPerMinute = 0;
}
this.publishStateChange();
}.bind(this), 20000);
deferred.resolve();
} else {
//TODO this should be only started if the device is initialized and connected
//TODO Interval only if battery sensor
if (this.configuration.sensorType === "BATTERYSENSOR") {
this.ticksUpdateInterval = setInterval(function () {
this.updateTicsOverTime();
this.publishStateChange();
}.bind(this), 60000);
}
this.device.adapter.listeners.push(telegram => {
if (telegram.deviceId === this.configuration.deviceId) {
//console.log('Device ' + telegram.friendlyId + ' is processing ', telegram.functions);
if (this.configuration.sensorType === "BATTERYSENSOR") {
for (var n in telegram.functions) {
if (telegram.functions[n].key === 'motionDetected') {
if (telegram.functions[n].value === 'true') {
this.publishEvent('tic', {});
this.state.lastMotionTimestamp = moment().toISOString();
this.ticksTimestampsArray.unshift(Date.now());
this.updateTicsOverTime();
this.publishStateChange();
if (this.occupancyTimeout) {
clearTimeout(this.occupancyTimeout);
}
if (!this.state.occupied) {
this.state.occupied = true;
this.publishEvent('motionDetected', {});
this.publishStateChange();
}
// Simulates repeated ticks
if (this.configuration.ghostTickInterval) {
this.tickRepeatInterval = setInterval(() => {
this.logDebug('Sending Ghost Tick');
this.publishEvent('tic', {});
this.state.lastMotionTimestamp = moment().toISOString();
this.ticksTimestampsArray.unshift(Date.now());
this.updateTicsOverTime();
this.publishStateChange();
}, this.configuration.ghostTickInterval * 1000);
}
this.occupancyTimeout = setTimeout(() => {
this.state.occupied = false;
this.publishEvent('noMoreMotion', {});
this.publishStateChange();
}, this.configuration.releaseTime * 1000);
} else {
if (this.tickRepeatInterval) {
clearInterval(this.tickRepeatInterval);
}
}
break;
}
}
} else {
let ticCounter;
let tic;
let duration;
let dataArray = [];
dataArray = telegram.telegramInfo.data.match(/.{1,2}/g);
let timeStamp = Date.now();
// "OD" means motion detected? Means if we do not get "0D" we can assume that there is also no motion data?
switch (dataArray[3]) {
case "0D": //MOTION DETECTED
this.state.lastMotionTimestamp = moment().toISOString();
if (!this.state.occupied) {
this.state.occupied = true;
this.publishEvent('motionDetected', {});
this.publishStateChange({occupied: this.state.occupied}); //Only publish the changed state TODO Not really needed here?
}
//RESET OCCUPANCY TIMOUT
if (this.occupancyTimeout) {
clearTimeout(this.occupancyTimeout);
}
//SET NEW OCCUPANCY TIMEOUT
this.occupancyTimeout = setTimeout(() => {
this.state.occupied = false;
this.publishEvent('noMoreMotion', {});
this.publishStateChange({occupied: this.state.occupied}); //Only publish the changed state
}, this.configuration.releaseTime * 1000);
//EXTRACT TICS FROM THE LATEST TELEGRAM
ticCounter = dataArray[1] + dataArray[2];
this.ticCountArray.unshift(parseInt(ticCounter, 16));
if (this.ticCountArray[0] < this.ticCountArray[1]) {
tic = this.ticCountArray[0] + (65535 - this.ticCountArray[1]);
} else {
tic = this.ticCountArray[0] - this.ticCountArray[1];
}
//DEBUG
if (tic > 60) {
this.logDebug("######### TELEGRAM OVERFLOW : ", tic);
this.logDebug("######### TELEGRAM: ", telegram);
tic = 60;
}
//SPREAD "tic" TIMESTAMPS TO tickTimestampArray
let timestampDifference = timeStamp - this.lastReceivedTS;
if (timestampDifference > 120000) timestampDifference = 120000;
for (let i = tic; i >= 0; i--) {
this.ticksTimestampsArray.push(Math.round(timeStamp - (timestampDifference / tic * i)));
}
this.lastReceivedTS = timeStamp;
break;
case "09": //NO MOTION DETECTED. Normally after 10 minutes
this.lastReceivedTS = timeStamp;
break;
default:
this.logDebug("Unknown telegram type...");
break;
}
let ticHistory = this.processHistoricalTics();
if (ticHistory !== null) {
this.publishStateChangeHistory(ticHistory);
}
}
}
});
deferred.resolve();
}
return deferred.promise;
};
/**
*
*/
OccupancySensor.prototype.processHistoricalTics = function () {
const minute = 60000;
if (this.lastReceivedTS - this.lastProcessedTS >= minute) {
let stateHistory = [];
while ((this.lastReceivedTS - this.lastProcessedTS) >= minute) {
if ((this.ticksTimestampsArray.length !== 0) && (this.ticksTimestampsArray[0] <= (this.lastProcessedTS + minute))) {
let ticsInSelectedMinute = [];
while (this.ticksTimestampsArray[0] <= (this.lastProcessedTS + minute)) {
ticsInSelectedMinute.push(this.ticksTimestampsArray.shift());
}
let tic;
//DEBUG
if (ticsInSelectedMinute.length > 60) {
this.logDebug("######### HISTORICAL TIC CALCULATION: ", ticsInSelectedMinute);
tic = 60;
} else {
tic = ticsInSelectedMinute.length;
}
stateHistory.push({
timestamp: new Date(this.lastProcessedTS + minute).toISOString(),
state: {ticksPerMinute: tic},
});
} else {
stateHistory.push({
timestamp: new Date(this.lastProcessedTS + minute).toISOString(),
state: {ticksPerMinute: 0},
});
}
this.lastProcessedTS += 60000;
}
return stateHistory;
} else {
return null;
}
};
/**
*
*/
OccupancySensor.prototype.updateTicsOverTime = function () {
let timestamp = Date.now();
let tickCountTimeMinutes = this.configuration.tickCountTimeMinutes || 5;
while ((timestamp - this.ticksTimestampsArray[this.ticksTimestampsArray.length - 1]) > (tickCountTimeMinutes * 60000)) {
this.ticksTimestampsArray.pop();
}
this.state.ticksPerMinute = Math.round(this.ticksTimestampsArray.length / tickCountTimeMinutes);
};
/**
*
*/
OccupancySensor.prototype.getState = function () {
return this.state;
};
/**
*
*/
OccupancySensor.prototype.setState = function (state) {
this.state = state;
};
/**
*
*/
OccupancySensor.prototype.stop = function () {
if (this.isSimulated()) {
if (this.interval) {
clearInterval(this.interval);
}
} else {
if (this.occupancyTimeout) {
clearTimeout(this.occupancyTimeout);
}
if (this.ticksUpdateInterval) {
clearInterval(this.ticksUpdateInterval);
}
if (this.tickRepeatInterval) {
clearInterval(this.tickRepeatInterval)
}
}
}
}