@rfranzoi/scrypted-mqtt-securitysystem 1.0.37 → 1.0.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.nodejs.js +132 -116
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
package/dist/main.nodejs.js
CHANGED
|
@@ -34062,6 +34062,24 @@ function falsy(v) {
|
|
|
34062
34062
|
}
|
|
34063
34063
|
function normalize(s) { return (s || '').trim().toLowerCase(); }
|
|
34064
34064
|
function clamp(n, min, max) { return Math.max(min, Math.min(max, n)); }
|
|
34065
|
+
function deepEqual(a, b) {
|
|
34066
|
+
try {
|
|
34067
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
34068
|
+
}
|
|
34069
|
+
catch {
|
|
34070
|
+
return a === b;
|
|
34071
|
+
}
|
|
34072
|
+
}
|
|
34073
|
+
/** Imposta una property e, se cambia, emette anche l'evento Scrypted corrispondente */
|
|
34074
|
+
function setAndEmit(dev, key, value, iface) {
|
|
34075
|
+
if (dev[key] === value)
|
|
34076
|
+
return;
|
|
34077
|
+
dev[key] = value;
|
|
34078
|
+
try {
|
|
34079
|
+
dev.onDeviceEvent?.(iface, value);
|
|
34080
|
+
}
|
|
34081
|
+
catch { }
|
|
34082
|
+
}
|
|
34065
34083
|
/** Outgoing predefiniti (PAI-like). Chiavi numeriche per compatibilità enum */
|
|
34066
34084
|
const DEFAULT_OUTGOING = {
|
|
34067
34085
|
[SecuritySystemMode.Disarmed]: 'disarm',
|
|
@@ -34094,157 +34112,145 @@ class BaseMqttSensor extends ScryptedDeviceBase {
|
|
|
34094
34112
|
handleMqtt(topic, payload) {
|
|
34095
34113
|
const p = payload?.toString() ?? '';
|
|
34096
34114
|
const np = normalize(p);
|
|
34115
|
+
// Online
|
|
34097
34116
|
if (topic === this.cfg.topics.online) {
|
|
34098
34117
|
if (truthy(np) || np === 'online')
|
|
34099
|
-
this
|
|
34118
|
+
setAndEmit(this, 'online', true, ScryptedInterface.Online);
|
|
34100
34119
|
if (falsy(np) || np === 'offline')
|
|
34101
|
-
this
|
|
34120
|
+
setAndEmit(this, 'online', false, ScryptedInterface.Online);
|
|
34102
34121
|
}
|
|
34122
|
+
// Tamper
|
|
34103
34123
|
if (topic === this.cfg.topics.tamper) {
|
|
34104
34124
|
if (truthy(np) || ['tamper', 'intrusion', 'cover', 'motion', 'magnetic'].includes(np)) {
|
|
34105
|
-
|
|
34125
|
+
const t = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
|
|
34126
|
+
setAndEmit(this, 'tampered', t, ScryptedInterface.TamperSensor);
|
|
34106
34127
|
}
|
|
34107
34128
|
else if (falsy(np))
|
|
34108
|
-
this
|
|
34129
|
+
setAndEmit(this, 'tampered', false, ScryptedInterface.TamperSensor);
|
|
34109
34130
|
}
|
|
34131
|
+
// Battery
|
|
34110
34132
|
if (topic === this.cfg.topics.batteryLevel) {
|
|
34111
34133
|
const n = clamp(parseFloat(p), 0, 100);
|
|
34112
34134
|
if (isFinite(n))
|
|
34113
|
-
this
|
|
34135
|
+
setAndEmit(this, 'batteryLevel', n, ScryptedInterface.Battery);
|
|
34114
34136
|
}
|
|
34115
34137
|
else if (topic === this.cfg.topics.lowBattery && !this.cfg.topics.batteryLevel) {
|
|
34116
|
-
|
|
34138
|
+
const n = truthy(np) ? 10 : 100;
|
|
34139
|
+
setAndEmit(this, 'batteryLevel', n, ScryptedInterface.Battery);
|
|
34117
34140
|
}
|
|
34141
|
+
// Primary
|
|
34118
34142
|
this.handlePrimary(topic, np, p);
|
|
34119
34143
|
}
|
|
34120
34144
|
}
|
|
34121
|
-
/** === SENSORI CON PARSING ROBUSTO
|
|
34145
|
+
/** === SENSORI CON PARSING ROBUSTO + EMISSIONE EVENTI === */
|
|
34122
34146
|
class ContactMqttSensor extends BaseMqttSensor {
|
|
34123
34147
|
handlePrimary(topic, np, raw) {
|
|
34124
34148
|
if (topic !== this.cfg.topics.contact)
|
|
34125
34149
|
return;
|
|
34150
|
+
let val;
|
|
34126
34151
|
// stringhe comuni
|
|
34127
|
-
if (['open', 'opened', '1', 'true', 'on', 'yes'].includes(np))
|
|
34128
|
-
|
|
34129
|
-
|
|
34130
|
-
|
|
34131
|
-
if (['closed', 'close', '0', 'false', 'off', 'no', 'shut'].includes(np)) {
|
|
34132
|
-
this.entryOpen = false;
|
|
34133
|
-
return;
|
|
34134
|
-
}
|
|
34152
|
+
if (['open', 'opened', '1', 'true', 'on', 'yes'].includes(np))
|
|
34153
|
+
val = true;
|
|
34154
|
+
else if (['closed', 'close', '0', 'false', 'off', 'no', 'shut'].includes(np))
|
|
34155
|
+
val = false;
|
|
34135
34156
|
// JSON comuni
|
|
34136
|
-
|
|
34137
|
-
|
|
34138
|
-
|
|
34139
|
-
|
|
34140
|
-
|
|
34141
|
-
|
|
34142
|
-
|
|
34143
|
-
|
|
34144
|
-
|
|
34145
|
-
|
|
34146
|
-
|
|
34147
|
-
|
|
34148
|
-
|
|
34149
|
-
|
|
34150
|
-
|
|
34151
|
-
const s = String(j.state).toLowerCase();
|
|
34152
|
-
if (s === 'open') {
|
|
34153
|
-
this.entryOpen = true;
|
|
34154
|
-
return;
|
|
34155
|
-
}
|
|
34156
|
-
if (s === 'closed') {
|
|
34157
|
-
this.entryOpen = false;
|
|
34158
|
-
return;
|
|
34157
|
+
if (val === undefined) {
|
|
34158
|
+
try {
|
|
34159
|
+
const j = JSON.parse(raw);
|
|
34160
|
+
if (typeof j?.open === 'boolean')
|
|
34161
|
+
val = !!j.open;
|
|
34162
|
+
else if (typeof j?.opened === 'boolean')
|
|
34163
|
+
val = !!j.opened;
|
|
34164
|
+
else if (typeof j?.contact === 'boolean')
|
|
34165
|
+
val = !j.contact; // contact:false => OPEN
|
|
34166
|
+
else if (typeof j?.state === 'string') {
|
|
34167
|
+
const s = String(j.state).toLowerCase();
|
|
34168
|
+
if (s === 'open')
|
|
34169
|
+
val = true;
|
|
34170
|
+
if (s === 'closed')
|
|
34171
|
+
val = false;
|
|
34159
34172
|
}
|
|
34160
34173
|
}
|
|
34174
|
+
catch { }
|
|
34175
|
+
}
|
|
34176
|
+
if (val !== undefined) {
|
|
34177
|
+
setAndEmit(this, 'entryOpen', val, ScryptedInterface.EntrySensor);
|
|
34178
|
+
}
|
|
34179
|
+
else {
|
|
34180
|
+
this.console?.debug?.(`Contact payload non gestito (${this.cfg.id}): "${raw}"`);
|
|
34161
34181
|
}
|
|
34162
|
-
catch { }
|
|
34163
|
-
this.console?.debug?.(`Contact payload non gestito (${this.cfg.id}): "${raw}"`);
|
|
34164
34182
|
}
|
|
34165
34183
|
}
|
|
34166
34184
|
class MotionMqttSensor extends BaseMqttSensor {
|
|
34167
34185
|
handlePrimary(topic, np, raw) {
|
|
34168
34186
|
if (topic !== this.cfg.topics.motion)
|
|
34169
34187
|
return;
|
|
34170
|
-
|
|
34171
|
-
|
|
34172
|
-
|
|
34173
|
-
|
|
34174
|
-
|
|
34175
|
-
|
|
34176
|
-
|
|
34177
|
-
|
|
34178
|
-
|
|
34179
|
-
|
|
34180
|
-
|
|
34181
|
-
|
|
34182
|
-
|
|
34183
|
-
|
|
34184
|
-
|
|
34185
|
-
|
|
34186
|
-
|
|
34187
|
-
|
|
34188
|
-
|
|
34189
|
-
|
|
34190
|
-
return;
|
|
34191
|
-
}
|
|
34192
|
-
if (typeof j?.state === 'string') {
|
|
34193
|
-
const s = String(j.state).toLowerCase();
|
|
34194
|
-
if (['on', 'motion', 'detected', 'active'].includes(s)) {
|
|
34195
|
-
this.motionDetected = true;
|
|
34196
|
-
return;
|
|
34197
|
-
}
|
|
34198
|
-
if (['off', 'clear', 'inactive'].includes(s)) {
|
|
34199
|
-
this.motionDetected = false;
|
|
34200
|
-
return;
|
|
34188
|
+
let val;
|
|
34189
|
+
if (['motion', 'detected', 'active', '1', 'true', 'on', 'yes'].includes(np))
|
|
34190
|
+
val = true;
|
|
34191
|
+
else if (['clear', 'inactive', 'no_motion', 'none', '0', 'false', 'off', 'no'].includes(np))
|
|
34192
|
+
val = false;
|
|
34193
|
+
if (val === undefined) {
|
|
34194
|
+
try {
|
|
34195
|
+
const j = JSON.parse(raw);
|
|
34196
|
+
if (typeof j?.motion === 'boolean')
|
|
34197
|
+
val = !!j.motion;
|
|
34198
|
+
else if (typeof j?.occupancy === 'boolean')
|
|
34199
|
+
val = !!j.occupancy;
|
|
34200
|
+
else if (typeof j?.presence === 'boolean')
|
|
34201
|
+
val = !!j.presence;
|
|
34202
|
+
else if (typeof j?.state === 'string') {
|
|
34203
|
+
const s = String(j.state).toLowerCase();
|
|
34204
|
+
if (['on', 'motion', 'detected', 'active'].includes(s))
|
|
34205
|
+
val = true;
|
|
34206
|
+
if (['off', 'clear', 'inactive'].includes(s))
|
|
34207
|
+
val = false;
|
|
34201
34208
|
}
|
|
34202
34209
|
}
|
|
34210
|
+
catch { }
|
|
34211
|
+
}
|
|
34212
|
+
if (val !== undefined) {
|
|
34213
|
+
setAndEmit(this, 'motionDetected', val, ScryptedInterface.MotionSensor);
|
|
34214
|
+
}
|
|
34215
|
+
else {
|
|
34216
|
+
this.console?.debug?.(`Motion payload non gestito (${this.cfg.id}): "${raw}"`);
|
|
34203
34217
|
}
|
|
34204
|
-
catch { }
|
|
34205
|
-
this.console?.debug?.(`Motion payload non gestito (${this.cfg.id}): "${raw}"`);
|
|
34206
34218
|
}
|
|
34207
34219
|
}
|
|
34208
34220
|
class OccupancyMqttSensor extends BaseMqttSensor {
|
|
34209
34221
|
handlePrimary(topic, np, raw) {
|
|
34210
34222
|
if (topic !== this.cfg.topics.occupancy)
|
|
34211
34223
|
return;
|
|
34212
|
-
|
|
34213
|
-
|
|
34214
|
-
|
|
34215
|
-
|
|
34216
|
-
|
|
34217
|
-
|
|
34218
|
-
|
|
34219
|
-
|
|
34220
|
-
|
|
34221
|
-
|
|
34222
|
-
|
|
34223
|
-
|
|
34224
|
-
|
|
34225
|
-
|
|
34226
|
-
|
|
34227
|
-
|
|
34228
|
-
|
|
34229
|
-
|
|
34230
|
-
|
|
34231
|
-
|
|
34232
|
-
return;
|
|
34233
|
-
}
|
|
34234
|
-
if (typeof j?.state === 'string') {
|
|
34235
|
-
const s = String(j.state).toLowerCase();
|
|
34236
|
-
if (['occupied', 'presence', 'present', 'on'].includes(s)) {
|
|
34237
|
-
this.occupied = true;
|
|
34238
|
-
return;
|
|
34239
|
-
}
|
|
34240
|
-
if (['vacant', 'absent', 'clear', 'off'].includes(s)) {
|
|
34241
|
-
this.occupied = false;
|
|
34242
|
-
return;
|
|
34224
|
+
let val;
|
|
34225
|
+
if (['occupied', 'presence', 'present', '1', 'true', 'on', 'yes'].includes(np))
|
|
34226
|
+
val = true;
|
|
34227
|
+
else if (['unoccupied', 'vacant', 'absent', '0', 'false', 'off', 'no', 'clear'].includes(np))
|
|
34228
|
+
val = false;
|
|
34229
|
+
if (val === undefined) {
|
|
34230
|
+
try {
|
|
34231
|
+
const j = JSON.parse(raw);
|
|
34232
|
+
if (typeof j?.occupied === 'boolean')
|
|
34233
|
+
val = !!j.occupied;
|
|
34234
|
+
else if (typeof j?.presence === 'boolean')
|
|
34235
|
+
val = !!j.presence;
|
|
34236
|
+
else if (typeof j?.occupancy === 'boolean')
|
|
34237
|
+
val = !!j.occupancy;
|
|
34238
|
+
else if (typeof j?.state === 'string') {
|
|
34239
|
+
const s = String(j.state).toLowerCase();
|
|
34240
|
+
if (['occupied', 'presence', 'present', 'on'].includes(s))
|
|
34241
|
+
val = true;
|
|
34242
|
+
if (['vacant', 'absent', 'clear', 'off'].includes(s))
|
|
34243
|
+
val = false;
|
|
34243
34244
|
}
|
|
34244
34245
|
}
|
|
34246
|
+
catch { }
|
|
34247
|
+
}
|
|
34248
|
+
if (val !== undefined) {
|
|
34249
|
+
setAndEmit(this, 'occupied', val, ScryptedInterface.OccupancySensor);
|
|
34250
|
+
}
|
|
34251
|
+
else {
|
|
34252
|
+
this.console?.debug?.(`Occupancy payload non gestito (${this.cfg.id}): "${raw}"`);
|
|
34245
34253
|
}
|
|
34246
|
-
catch { }
|
|
34247
|
-
this.console?.debug?.(`Occupancy payload non gestito (${this.cfg.id}): "${raw}"`);
|
|
34248
34254
|
}
|
|
34249
34255
|
}
|
|
34250
34256
|
/** ----------------- Main Plugin ----------------- */
|
|
@@ -34491,7 +34497,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34491
34497
|
}
|
|
34492
34498
|
const hasBattery = !!(cfg.topics.batteryLevel || cfg.topics.lowBattery);
|
|
34493
34499
|
if (hasBattery && dev.batteryLevel === undefined)
|
|
34494
|
-
dev
|
|
34500
|
+
setAndEmit(dev, 'batteryLevel', 100, ScryptedInterface.Battery);
|
|
34495
34501
|
}
|
|
34496
34502
|
// 4) Cleanup
|
|
34497
34503
|
const announced = new Set(manifests.map(m => m.nativeId));
|
|
@@ -34556,7 +34562,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34556
34562
|
const tOnline = this.storage.getItem('topicOnline') || '';
|
|
34557
34563
|
client.on('connect', () => {
|
|
34558
34564
|
this.console.log('MQTT connected');
|
|
34559
|
-
this
|
|
34565
|
+
setAndEmit(this, 'online', true, ScryptedInterface.Online);
|
|
34560
34566
|
if (subs.length)
|
|
34561
34567
|
client.subscribe(subs, { qos: 0 }, (err) => { if (err)
|
|
34562
34568
|
this.console.error('subscribe error', err); });
|
|
@@ -34564,7 +34570,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34564
34570
|
this.safeDiscoverSensors(true);
|
|
34565
34571
|
});
|
|
34566
34572
|
client.on('reconnect', () => this.console.log('MQTT reconnecting...'));
|
|
34567
|
-
client.on('close', () => { this.console.log('MQTT closed'); this
|
|
34573
|
+
client.on('close', () => { this.console.log('MQTT closed'); setAndEmit(this, 'online', false, ScryptedInterface.Online); });
|
|
34568
34574
|
client.on('error', (e) => { this.console.error('MQTT error', e); });
|
|
34569
34575
|
client.on('message', (topic, payload) => {
|
|
34570
34576
|
try {
|
|
@@ -34572,23 +34578,26 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34572
34578
|
const np = normalize(p);
|
|
34573
34579
|
if (topic === tOnline) {
|
|
34574
34580
|
if (truthy(np) || np === 'online')
|
|
34575
|
-
this
|
|
34581
|
+
setAndEmit(this, 'online', true, ScryptedInterface.Online);
|
|
34576
34582
|
if (falsy(np) || np === 'offline')
|
|
34577
|
-
this
|
|
34583
|
+
setAndEmit(this, 'online', false, ScryptedInterface.Online);
|
|
34578
34584
|
return;
|
|
34579
34585
|
}
|
|
34580
34586
|
if (topic === tTamper) {
|
|
34581
|
-
if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np))
|
|
34582
|
-
|
|
34583
|
-
|
|
34584
|
-
|
|
34587
|
+
if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np)) {
|
|
34588
|
+
const t = ['cover', 'intrusion'].find(x => x === np) || true;
|
|
34589
|
+
setAndEmit(this, 'tampered', t, ScryptedInterface.TamperSensor);
|
|
34590
|
+
}
|
|
34591
|
+
else if (falsy(np)) {
|
|
34592
|
+
setAndEmit(this, 'tampered', false, ScryptedInterface.TamperSensor);
|
|
34593
|
+
}
|
|
34585
34594
|
return;
|
|
34586
34595
|
}
|
|
34587
34596
|
if (topic === tCurrent) {
|
|
34588
34597
|
const mode = payloadToMode(payload);
|
|
34589
34598
|
const isAlarm = ['alarm', 'triggered'].includes(np);
|
|
34590
34599
|
const current = this.securitySystemState || { mode: SecuritySystemMode.Disarmed };
|
|
34591
|
-
|
|
34600
|
+
const newState = {
|
|
34592
34601
|
mode: mode ?? current.mode,
|
|
34593
34602
|
supportedModes: current.supportedModes ?? [
|
|
34594
34603
|
SecuritySystemMode.Disarmed,
|
|
@@ -34598,6 +34607,13 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34598
34607
|
],
|
|
34599
34608
|
triggered: isAlarm || undefined,
|
|
34600
34609
|
};
|
|
34610
|
+
if (!deepEqual(this.securitySystemState, newState)) {
|
|
34611
|
+
this.securitySystemState = newState;
|
|
34612
|
+
try {
|
|
34613
|
+
this.onDeviceEvent?.(ScryptedInterface.SecuritySystem, newState);
|
|
34614
|
+
}
|
|
34615
|
+
catch { }
|
|
34616
|
+
}
|
|
34601
34617
|
return;
|
|
34602
34618
|
}
|
|
34603
34619
|
if (topic === tTarget) {
|
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED