@rfranzoi/scrypted-mqtt-securitysystem 1.0.36 → 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 +162 -35
- 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,46 +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
|
}
|
|
34145
|
+
/** === SENSORI CON PARSING ROBUSTO + EMISSIONE EVENTI === */
|
|
34121
34146
|
class ContactMqttSensor extends BaseMqttSensor {
|
|
34122
|
-
handlePrimary(topic, np) {
|
|
34123
|
-
if (topic
|
|
34124
|
-
|
|
34147
|
+
handlePrimary(topic, np, raw) {
|
|
34148
|
+
if (topic !== this.cfg.topics.contact)
|
|
34149
|
+
return;
|
|
34150
|
+
let val;
|
|
34151
|
+
// stringhe comuni
|
|
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;
|
|
34156
|
+
// JSON comuni
|
|
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;
|
|
34172
|
+
}
|
|
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}"`);
|
|
34181
|
+
}
|
|
34125
34182
|
}
|
|
34126
34183
|
}
|
|
34127
34184
|
class MotionMqttSensor extends BaseMqttSensor {
|
|
34128
|
-
handlePrimary(topic, np) {
|
|
34129
|
-
if (topic
|
|
34130
|
-
|
|
34185
|
+
handlePrimary(topic, np, raw) {
|
|
34186
|
+
if (topic !== this.cfg.topics.motion)
|
|
34187
|
+
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;
|
|
34208
|
+
}
|
|
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}"`);
|
|
34217
|
+
}
|
|
34131
34218
|
}
|
|
34132
34219
|
}
|
|
34133
34220
|
class OccupancyMqttSensor extends BaseMqttSensor {
|
|
34134
|
-
handlePrimary(topic, np) {
|
|
34135
|
-
if (topic
|
|
34136
|
-
|
|
34221
|
+
handlePrimary(topic, np, raw) {
|
|
34222
|
+
if (topic !== this.cfg.topics.occupancy)
|
|
34223
|
+
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;
|
|
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}"`);
|
|
34253
|
+
}
|
|
34137
34254
|
}
|
|
34138
34255
|
}
|
|
34139
34256
|
/** ----------------- Main Plugin ----------------- */
|
|
@@ -34142,8 +34259,8 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34142
34259
|
super();
|
|
34143
34260
|
this.sensorsCfg = [];
|
|
34144
34261
|
this.devices = new Map();
|
|
34145
|
-
//
|
|
34146
|
-
this.
|
|
34262
|
+
// Evita loop di log: tenta una volta finché deviceManager non c’è, poi riprova su eventi utili.
|
|
34263
|
+
this.discoveryPostponed = false;
|
|
34147
34264
|
// Tipo in UI (best-effort)
|
|
34148
34265
|
setTimeout(() => {
|
|
34149
34266
|
try {
|
|
@@ -34323,17 +34440,17 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34323
34440
|
safeDiscoverSensors(triggeredByChange = false) {
|
|
34324
34441
|
const dmAny = sdk?.deviceManager;
|
|
34325
34442
|
if (!dmAny) {
|
|
34326
|
-
|
|
34443
|
+
// Posticipa una sola volta; poi riproviamo su connect MQTT e al primo messaggio
|
|
34444
|
+
if (!this.discoveryPostponed) {
|
|
34327
34445
|
this.console.log('Device discovery postponed: deviceManager not ready yet.');
|
|
34328
|
-
this.
|
|
34446
|
+
this.discoveryPostponed = true;
|
|
34329
34447
|
}
|
|
34330
|
-
// Riprovaremo in due casi: a) settaggi cambiati (già chiama safeDiscoverSensors)
|
|
34331
|
-
// b) al primo messaggio MQTT (vedi handler sotto).
|
|
34332
34448
|
return;
|
|
34333
34449
|
}
|
|
34334
|
-
|
|
34335
|
-
this.triedDiscoveryOnce = false;
|
|
34450
|
+
this.discoveryPostponed = false;
|
|
34336
34451
|
this.discoverSensors(dmAny);
|
|
34452
|
+
if (triggeredByChange)
|
|
34453
|
+
this.console.log('Sensors discovered/updated.');
|
|
34337
34454
|
}
|
|
34338
34455
|
/** discoverSensors con deviceManager garantito */
|
|
34339
34456
|
discoverSensors(dmAny) {
|
|
@@ -34380,7 +34497,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34380
34497
|
}
|
|
34381
34498
|
const hasBattery = !!(cfg.topics.batteryLevel || cfg.topics.lowBattery);
|
|
34382
34499
|
if (hasBattery && dev.batteryLevel === undefined)
|
|
34383
|
-
dev
|
|
34500
|
+
setAndEmit(dev, 'batteryLevel', 100, ScryptedInterface.Battery);
|
|
34384
34501
|
}
|
|
34385
34502
|
// 4) Cleanup
|
|
34386
34503
|
const announced = new Set(manifests.map(m => m.nativeId));
|
|
@@ -34445,7 +34562,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34445
34562
|
const tOnline = this.storage.getItem('topicOnline') || '';
|
|
34446
34563
|
client.on('connect', () => {
|
|
34447
34564
|
this.console.log('MQTT connected');
|
|
34448
|
-
this
|
|
34565
|
+
setAndEmit(this, 'online', true, ScryptedInterface.Online);
|
|
34449
34566
|
if (subs.length)
|
|
34450
34567
|
client.subscribe(subs, { qos: 0 }, (err) => { if (err)
|
|
34451
34568
|
this.console.error('subscribe error', err); });
|
|
@@ -34453,7 +34570,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34453
34570
|
this.safeDiscoverSensors(true);
|
|
34454
34571
|
});
|
|
34455
34572
|
client.on('reconnect', () => this.console.log('MQTT reconnecting...'));
|
|
34456
|
-
client.on('close', () => { this.console.log('MQTT closed'); this
|
|
34573
|
+
client.on('close', () => { this.console.log('MQTT closed'); setAndEmit(this, 'online', false, ScryptedInterface.Online); });
|
|
34457
34574
|
client.on('error', (e) => { this.console.error('MQTT error', e); });
|
|
34458
34575
|
client.on('message', (topic, payload) => {
|
|
34459
34576
|
try {
|
|
@@ -34461,23 +34578,26 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34461
34578
|
const np = normalize(p);
|
|
34462
34579
|
if (topic === tOnline) {
|
|
34463
34580
|
if (truthy(np) || np === 'online')
|
|
34464
|
-
this
|
|
34581
|
+
setAndEmit(this, 'online', true, ScryptedInterface.Online);
|
|
34465
34582
|
if (falsy(np) || np === 'offline')
|
|
34466
|
-
this
|
|
34583
|
+
setAndEmit(this, 'online', false, ScryptedInterface.Online);
|
|
34467
34584
|
return;
|
|
34468
34585
|
}
|
|
34469
34586
|
if (topic === tTamper) {
|
|
34470
|
-
if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np))
|
|
34471
|
-
|
|
34472
|
-
|
|
34473
|
-
|
|
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
|
+
}
|
|
34474
34594
|
return;
|
|
34475
34595
|
}
|
|
34476
34596
|
if (topic === tCurrent) {
|
|
34477
34597
|
const mode = payloadToMode(payload);
|
|
34478
34598
|
const isAlarm = ['alarm', 'triggered'].includes(np);
|
|
34479
34599
|
const current = this.securitySystemState || { mode: SecuritySystemMode.Disarmed };
|
|
34480
|
-
|
|
34600
|
+
const newState = {
|
|
34481
34601
|
mode: mode ?? current.mode,
|
|
34482
34602
|
supportedModes: current.supportedModes ?? [
|
|
34483
34603
|
SecuritySystemMode.Disarmed,
|
|
@@ -34487,6 +34607,13 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34487
34607
|
],
|
|
34488
34608
|
triggered: isAlarm || undefined,
|
|
34489
34609
|
};
|
|
34610
|
+
if (!deepEqual(this.securitySystemState, newState)) {
|
|
34611
|
+
this.securitySystemState = newState;
|
|
34612
|
+
try {
|
|
34613
|
+
this.onDeviceEvent?.(ScryptedInterface.SecuritySystem, newState);
|
|
34614
|
+
}
|
|
34615
|
+
catch { }
|
|
34616
|
+
}
|
|
34490
34617
|
return;
|
|
34491
34618
|
}
|
|
34492
34619
|
if (topic === tTarget) {
|
|
@@ -34495,8 +34622,8 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34495
34622
|
return;
|
|
34496
34623
|
}
|
|
34497
34624
|
// Dispatch ai sensori
|
|
34498
|
-
// (E prova ad annunciare se
|
|
34499
|
-
if (this.
|
|
34625
|
+
// (E prova ad annunciare se era stato posticipato e ora il manager è pronto)
|
|
34626
|
+
if (this.discoveryPostponed)
|
|
34500
34627
|
this.safeDiscoverSensors(true);
|
|
34501
34628
|
for (const dev of this.devices.values())
|
|
34502
34629
|
dev.handleMqtt(topic, payload);
|
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED