@rfranzoi/scrypted-mqtt-securitysystem 1.0.45 → 1.0.47
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 +108 -65
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
package/dist/main.nodejs.js
CHANGED
|
@@ -34077,12 +34077,8 @@ function falsy(v) {
|
|
|
34077
34077
|
const s = v.toString().trim().toLowerCase();
|
|
34078
34078
|
return s === '0' || s === 'false' || s === 'offline' || s === 'no' || s === 'off';
|
|
34079
34079
|
}
|
|
34080
|
-
function normalize(s) {
|
|
34081
|
-
|
|
34082
|
-
}
|
|
34083
|
-
function clamp(n, min, max) {
|
|
34084
|
-
return Math.max(min, Math.min(max, n));
|
|
34085
|
-
}
|
|
34080
|
+
function normalize(s) { return (s || '').trim().toLowerCase(); }
|
|
34081
|
+
function clamp(n, min, max) { return Math.max(min, Math.min(max, n)); }
|
|
34086
34082
|
/** SecuritySystem outgoing defaults (PAI-like) */
|
|
34087
34083
|
const DEFAULT_OUTGOING = {
|
|
34088
34084
|
[sdk_1.SecuritySystemMode.Disarmed]: 'disarm',
|
|
@@ -34103,7 +34099,6 @@ function payloadToMode(payload) {
|
|
|
34103
34099
|
return sdk_1.SecuritySystemMode.AwayArmed;
|
|
34104
34100
|
if (['arm_night', 'night', 'armed_night', 'sleep', 'arm_sleep', 'armed_sleep'].includes(p))
|
|
34105
34101
|
return sdk_1.SecuritySystemMode.NightArmed;
|
|
34106
|
-
// transitori: non cambiano il mode
|
|
34107
34102
|
if (['entry_delay', 'exit_delay', 'pending', 'arming', 'disarming'].includes(p))
|
|
34108
34103
|
return undefined;
|
|
34109
34104
|
return undefined;
|
|
@@ -34112,71 +34107,100 @@ class BaseMqttSensor extends sdk_1.ScryptedDeviceBase {
|
|
|
34112
34107
|
constructor(nativeId, cfg) {
|
|
34113
34108
|
super(nativeId);
|
|
34114
34109
|
this.cfg = cfg;
|
|
34115
|
-
|
|
34110
|
+
}
|
|
34111
|
+
/** setter centralizzato: cambia lo stato SOLO se diverso e poi emette l'evento relativo */
|
|
34112
|
+
setAndEmit(prop, val, iface) {
|
|
34113
|
+
const prev = this[prop];
|
|
34114
|
+
if (prev === val)
|
|
34115
|
+
return;
|
|
34116
|
+
this[prop] = val;
|
|
34117
|
+
try {
|
|
34118
|
+
this.onDeviceEvent(iface, val);
|
|
34119
|
+
}
|
|
34120
|
+
catch (e) {
|
|
34121
|
+
this.console?.warn?.('onDeviceEvent error', iface, e);
|
|
34122
|
+
}
|
|
34116
34123
|
}
|
|
34117
34124
|
/** Called by parent on each MQTT message */
|
|
34118
34125
|
handleMqtt(topic, payload) {
|
|
34119
|
-
const
|
|
34120
|
-
const np = normalize(
|
|
34126
|
+
const raw = payload?.toString() ?? '';
|
|
34127
|
+
const np = normalize(raw);
|
|
34121
34128
|
// online
|
|
34122
34129
|
if (topic === this.cfg.topics.online) {
|
|
34123
34130
|
if (truthy(np) || np === 'online')
|
|
34124
|
-
this.online
|
|
34131
|
+
this.setAndEmit('online', true, sdk_1.ScryptedInterface.Online);
|
|
34125
34132
|
if (falsy(np) || np === 'offline')
|
|
34126
|
-
this.online
|
|
34133
|
+
this.setAndEmit('online', false, sdk_1.ScryptedInterface.Online);
|
|
34127
34134
|
}
|
|
34128
34135
|
// tamper
|
|
34129
34136
|
if (topic === this.cfg.topics.tamper) {
|
|
34130
34137
|
if (truthy(np) || ['tamper', 'intrusion', 'cover', 'motion', 'magnetic'].includes(np)) {
|
|
34131
|
-
|
|
34138
|
+
const value = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
|
|
34139
|
+
this.setAndEmit('tampered', value, sdk_1.ScryptedInterface.TamperSensor);
|
|
34132
34140
|
}
|
|
34133
34141
|
else if (falsy(np)) {
|
|
34134
|
-
this.tampered
|
|
34142
|
+
this.setAndEmit('tampered', false, sdk_1.ScryptedInterface.TamperSensor);
|
|
34135
34143
|
}
|
|
34136
34144
|
}
|
|
34137
34145
|
// battery
|
|
34138
34146
|
if (topic === this.cfg.topics.batteryLevel) {
|
|
34139
|
-
|
|
34140
|
-
|
|
34141
|
-
|
|
34147
|
+
// aggiorna solo con numeri validi 0..100
|
|
34148
|
+
const n = clamp(parseFloat(raw), 0, 100);
|
|
34149
|
+
if (Number.isFinite(n))
|
|
34150
|
+
this.setAndEmit('batteryLevel', n, sdk_1.ScryptedInterface.Battery);
|
|
34142
34151
|
}
|
|
34143
34152
|
else if (topic === this.cfg.topics.lowBattery && !this.cfg.topics.batteryLevel) {
|
|
34144
|
-
//
|
|
34145
|
-
|
|
34146
|
-
|
|
34147
|
-
|
|
34153
|
+
// LowBattery booleano: quando TRUE -> 10%; quando FALSE non forzare nulla, lascia il valore precedente
|
|
34154
|
+
if (truthy(np))
|
|
34155
|
+
this.setAndEmit('batteryLevel', 10, sdk_1.ScryptedInterface.Battery);
|
|
34156
|
+
else if (falsy(np) && this.batteryLevel === undefined) {
|
|
34157
|
+
// se è il primo messaggio ed è "ok", metti 100 (altrimenti resta undefined)
|
|
34158
|
+
this.setAndEmit('batteryLevel', 100, sdk_1.ScryptedInterface.Battery);
|
|
34159
|
+
}
|
|
34148
34160
|
}
|
|
34149
34161
|
// primary handled by subclasses
|
|
34150
|
-
this.handlePrimary(topic, np,
|
|
34162
|
+
this.handlePrimary(topic, np, raw);
|
|
34151
34163
|
}
|
|
34152
34164
|
}
|
|
34153
34165
|
class ContactMqttSensor extends BaseMqttSensor {
|
|
34154
|
-
|
|
34155
|
-
super(nativeId, cfg);
|
|
34156
|
-
}
|
|
34157
|
-
handlePrimary(topic, np, _raw) {
|
|
34166
|
+
handlePrimary(topic, np) {
|
|
34158
34167
|
if (topic === this.cfg.topics.contact) {
|
|
34159
|
-
|
|
34168
|
+
const v = truthy(np);
|
|
34169
|
+
this.setAndEmit?.('entryOpen', v, sdk_1.ScryptedInterface.EntrySensor);
|
|
34170
|
+
if (this.setAndEmit === undefined) {
|
|
34171
|
+
if (this.entryOpen !== v) {
|
|
34172
|
+
this.entryOpen = v;
|
|
34173
|
+
this.onDeviceEvent(sdk_1.ScryptedInterface.EntrySensor, v);
|
|
34174
|
+
}
|
|
34175
|
+
}
|
|
34160
34176
|
}
|
|
34161
34177
|
}
|
|
34162
34178
|
}
|
|
34163
34179
|
class MotionMqttSensor extends BaseMqttSensor {
|
|
34164
|
-
|
|
34165
|
-
super(nativeId, cfg);
|
|
34166
|
-
}
|
|
34167
|
-
handlePrimary(topic, np, _raw) {
|
|
34180
|
+
handlePrimary(topic, np) {
|
|
34168
34181
|
if (topic === this.cfg.topics.motion) {
|
|
34169
|
-
|
|
34182
|
+
const v = truthy(np);
|
|
34183
|
+
this.setAndEmit?.('motionDetected', v, sdk_1.ScryptedInterface.MotionSensor);
|
|
34184
|
+
if (this.setAndEmit === undefined) {
|
|
34185
|
+
if (this.motionDetected !== v) {
|
|
34186
|
+
this.motionDetected = v;
|
|
34187
|
+
this.onDeviceEvent(sdk_1.ScryptedInterface.MotionSensor, v);
|
|
34188
|
+
}
|
|
34189
|
+
}
|
|
34170
34190
|
}
|
|
34171
34191
|
}
|
|
34172
34192
|
}
|
|
34173
34193
|
class OccupancyMqttSensor extends BaseMqttSensor {
|
|
34174
|
-
|
|
34175
|
-
super(nativeId, cfg);
|
|
34176
|
-
}
|
|
34177
|
-
handlePrimary(topic, np, _raw) {
|
|
34194
|
+
handlePrimary(topic, np) {
|
|
34178
34195
|
if (topic === this.cfg.topics.occupancy) {
|
|
34179
|
-
|
|
34196
|
+
const v = truthy(np);
|
|
34197
|
+
this.setAndEmit?.('occupied', v, sdk_1.ScryptedInterface.OccupancySensor);
|
|
34198
|
+
if (this.setAndEmit === undefined) {
|
|
34199
|
+
if (this.occupied !== v) {
|
|
34200
|
+
this.occupied = v;
|
|
34201
|
+
this.onDeviceEvent(sdk_1.ScryptedInterface.OccupancySensor, v);
|
|
34202
|
+
}
|
|
34203
|
+
}
|
|
34180
34204
|
}
|
|
34181
34205
|
}
|
|
34182
34206
|
}
|
|
@@ -34277,7 +34301,6 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34277
34301
|
return out;
|
|
34278
34302
|
}
|
|
34279
34303
|
async putSetting(key, value) {
|
|
34280
|
-
// salva sempre nella storage la value del campo (così resta in UI)
|
|
34281
34304
|
this.storage.setItem(key, String(value));
|
|
34282
34305
|
// --- Add Sensor workflow ---
|
|
34283
34306
|
if (key === 'new.create' && String(value) === 'true') {
|
|
@@ -34294,7 +34317,6 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34294
34317
|
}
|
|
34295
34318
|
this.sensorsCfg.push({ id, name, kind, topics: {} });
|
|
34296
34319
|
this.saveSensorsToStorage();
|
|
34297
|
-
// pulisci i campi "new.*"
|
|
34298
34320
|
this.storage.removeItem('new.id');
|
|
34299
34321
|
this.storage.removeItem('new.name');
|
|
34300
34322
|
this.storage.removeItem('new.kind');
|
|
@@ -34314,7 +34336,6 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34314
34336
|
return;
|
|
34315
34337
|
}
|
|
34316
34338
|
if (prop === 'remove' && String(value) === 'true') {
|
|
34317
|
-
// elimina
|
|
34318
34339
|
this.sensorsCfg = this.sensorsCfg.filter(s => s.id !== sid);
|
|
34319
34340
|
this.saveSensorsToStorage();
|
|
34320
34341
|
try {
|
|
@@ -34322,18 +34343,15 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34322
34343
|
deviceManager.onDeviceRemoved?.(`sensor:${sid}`);
|
|
34323
34344
|
}
|
|
34324
34345
|
catch { }
|
|
34325
|
-
// pulisci flag
|
|
34326
34346
|
this.storage.removeItem(key);
|
|
34327
34347
|
await this.discoverSensors();
|
|
34328
34348
|
await this.connectMqtt(true);
|
|
34329
34349
|
return;
|
|
34330
34350
|
}
|
|
34331
|
-
if (prop === 'name')
|
|
34351
|
+
if (prop === 'name')
|
|
34332
34352
|
cfg.name = String(value);
|
|
34333
|
-
|
|
34334
|
-
else if (prop === 'kind') {
|
|
34353
|
+
else if (prop === 'kind')
|
|
34335
34354
|
cfg.kind = String(value);
|
|
34336
|
-
}
|
|
34337
34355
|
else if (prop.startsWith('topic.')) {
|
|
34338
34356
|
const tk = prop.substring('topic.'.length);
|
|
34339
34357
|
cfg.topics[tk] = String(value).trim();
|
|
@@ -34345,7 +34363,6 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34345
34363
|
}
|
|
34346
34364
|
// --- Altro (MQTT / Alarm settings) ---
|
|
34347
34365
|
if (key === 'sensorsJson') {
|
|
34348
|
-
// non più mostrato, ma se presente da vecchie versioni
|
|
34349
34366
|
this.loadSensorsFromStorage();
|
|
34350
34367
|
await this.discoverSensors();
|
|
34351
34368
|
await this.connectMqtt(true);
|
|
@@ -34361,9 +34378,8 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34361
34378
|
async releaseDevice(_id, nativeId) {
|
|
34362
34379
|
try {
|
|
34363
34380
|
const dev = this.devices.get(nativeId);
|
|
34364
|
-
if (dev)
|
|
34381
|
+
if (dev)
|
|
34365
34382
|
this.devices.delete(nativeId);
|
|
34366
|
-
}
|
|
34367
34383
|
try {
|
|
34368
34384
|
deviceManager.onDeviceRemoved?.(nativeId);
|
|
34369
34385
|
}
|
|
@@ -34377,7 +34393,6 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34377
34393
|
try {
|
|
34378
34394
|
const raw = this.storage.getItem('sensorsJson') || '[]';
|
|
34379
34395
|
const parsed = JSON.parse(raw);
|
|
34380
|
-
// sanitize
|
|
34381
34396
|
this.sensorsCfg = (parsed || []).filter(x => x && x.id && x.name && x.kind && x.topics);
|
|
34382
34397
|
}
|
|
34383
34398
|
catch (e) {
|
|
@@ -34387,23 +34402,21 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34387
34402
|
}
|
|
34388
34403
|
/** ===== discoverSensors: annuncia PRIMA, istanzia DOPO ===== */
|
|
34389
34404
|
async discoverSensors() {
|
|
34390
|
-
// 1)
|
|
34405
|
+
// 1) Manifests
|
|
34391
34406
|
const manifests = this.sensorsCfg.map(cfg => {
|
|
34392
34407
|
const nativeId = `sensor:${cfg.id}`;
|
|
34393
34408
|
const t = cfg.topics || {};
|
|
34394
34409
|
const interfaces = [sdk_1.ScryptedInterface.Online];
|
|
34395
|
-
// Tamper solo se c'è un topic tamper
|
|
34396
34410
|
if (t.tamper)
|
|
34397
34411
|
interfaces.push(sdk_1.ScryptedInterface.TamperSensor);
|
|
34398
|
-
// Interfaccia primaria
|
|
34399
34412
|
if (cfg.kind === 'contact')
|
|
34400
34413
|
interfaces.unshift(sdk_1.ScryptedInterface.EntrySensor);
|
|
34401
34414
|
else if (cfg.kind === 'motion')
|
|
34402
34415
|
interfaces.unshift(sdk_1.ScryptedInterface.MotionSensor);
|
|
34403
34416
|
else
|
|
34404
34417
|
interfaces.unshift(sdk_1.ScryptedInterface.OccupancySensor);
|
|
34405
|
-
// Battery
|
|
34406
|
-
if (t.batteryLevel || t.lowBattery) {
|
|
34418
|
+
// Espone Battery SOLTANTO se è configurato almeno un topic batteria (stringa non vuota)
|
|
34419
|
+
if ((t.batteryLevel && t.batteryLevel.trim()) || (t.lowBattery && t.lowBattery.trim())) {
|
|
34407
34420
|
interfaces.push(sdk_1.ScryptedInterface.Battery);
|
|
34408
34421
|
}
|
|
34409
34422
|
return { nativeId, name: cfg.name, type: sdk_1.ScryptedDeviceType.Sensor, interfaces };
|
|
@@ -34436,11 +34449,7 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34436
34449
|
else {
|
|
34437
34450
|
dev.cfg = cfg;
|
|
34438
34451
|
}
|
|
34439
|
-
//
|
|
34440
|
-
const hasBattery = !!(cfg.topics.batteryLevel || cfg.topics.lowBattery);
|
|
34441
|
-
if (hasBattery && dev.batteryLevel === undefined) {
|
|
34442
|
-
dev.batteryLevel = 100;
|
|
34443
|
-
}
|
|
34452
|
+
// <<< RIMOSSO il default batteryLevel=100: niente valore finché non arriva un messaggio >>>
|
|
34444
34453
|
}
|
|
34445
34454
|
// 4) Rimuovi quelli spariti
|
|
34446
34455
|
const announced = new Set(manifests.map(m => m.nativeId));
|
|
@@ -34488,7 +34497,7 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34488
34497
|
for (const s of this.sensorsCfg) {
|
|
34489
34498
|
const t = s.topics || {};
|
|
34490
34499
|
[t.contact, t.motion, t.occupancy, t.batteryLevel, t.lowBattery, t.tamper, t.online]
|
|
34491
|
-
.filter(
|
|
34500
|
+
.filter((x) => !!x && String(x).trim().length > 0)
|
|
34492
34501
|
.forEach(x => subs.add(String(x)));
|
|
34493
34502
|
}
|
|
34494
34503
|
return Array.from(subs);
|
|
@@ -34517,6 +34526,10 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34517
34526
|
client.on('connect', () => {
|
|
34518
34527
|
this.console.log('MQTT connected');
|
|
34519
34528
|
this.online = true;
|
|
34529
|
+
try {
|
|
34530
|
+
this.onDeviceEvent(sdk_1.ScryptedInterface.Online, true);
|
|
34531
|
+
}
|
|
34532
|
+
catch { }
|
|
34520
34533
|
if (subs.length) {
|
|
34521
34534
|
client.subscribe(subs, { qos: 0 }, (err) => {
|
|
34522
34535
|
if (err)
|
|
@@ -34525,7 +34538,14 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34525
34538
|
}
|
|
34526
34539
|
});
|
|
34527
34540
|
client.on('reconnect', () => this.console.log('MQTT reconnecting...'));
|
|
34528
|
-
client.on('close', () => {
|
|
34541
|
+
client.on('close', () => {
|
|
34542
|
+
this.console.log('MQTT closed');
|
|
34543
|
+
this.online = false;
|
|
34544
|
+
try {
|
|
34545
|
+
this.onDeviceEvent(sdk_1.ScryptedInterface.Online, false);
|
|
34546
|
+
}
|
|
34547
|
+
catch { }
|
|
34548
|
+
});
|
|
34529
34549
|
client.on('error', (e) => { this.console.error('MQTT error', e); });
|
|
34530
34550
|
client.on('message', (topic, payload) => {
|
|
34531
34551
|
try {
|
|
@@ -34533,18 +34553,37 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34533
34553
|
const np = normalize(p);
|
|
34534
34554
|
// ---- Alarm handling ----
|
|
34535
34555
|
if (topic === tOnline) {
|
|
34536
|
-
if (truthy(np) || np === 'online')
|
|
34556
|
+
if (truthy(np) || np === 'online') {
|
|
34537
34557
|
this.online = true;
|
|
34538
|
-
|
|
34558
|
+
try {
|
|
34559
|
+
this.onDeviceEvent(sdk_1.ScryptedInterface.Online, true);
|
|
34560
|
+
}
|
|
34561
|
+
catch { }
|
|
34562
|
+
}
|
|
34563
|
+
if (falsy(np) || np === 'offline') {
|
|
34539
34564
|
this.online = false;
|
|
34565
|
+
try {
|
|
34566
|
+
this.onDeviceEvent(sdk_1.ScryptedInterface.Online, false);
|
|
34567
|
+
}
|
|
34568
|
+
catch { }
|
|
34569
|
+
}
|
|
34540
34570
|
return;
|
|
34541
34571
|
}
|
|
34542
34572
|
if (topic === tTamper) {
|
|
34543
34573
|
if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np)) {
|
|
34544
|
-
|
|
34574
|
+
const val = ['cover', 'intrusion'].find(x => x === np) || true;
|
|
34575
|
+
this.tampered = val;
|
|
34576
|
+
try {
|
|
34577
|
+
this.onDeviceEvent(sdk_1.ScryptedInterface.TamperSensor, val);
|
|
34578
|
+
}
|
|
34579
|
+
catch { }
|
|
34545
34580
|
}
|
|
34546
34581
|
else if (falsy(np)) {
|
|
34547
34582
|
this.tampered = false;
|
|
34583
|
+
try {
|
|
34584
|
+
this.onDeviceEvent(sdk_1.ScryptedInterface.TamperSensor, false);
|
|
34585
|
+
}
|
|
34586
|
+
catch { }
|
|
34548
34587
|
}
|
|
34549
34588
|
return;
|
|
34550
34589
|
}
|
|
@@ -34563,6 +34602,10 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34563
34602
|
triggered: isAlarm || undefined,
|
|
34564
34603
|
};
|
|
34565
34604
|
this.securitySystemState = newState;
|
|
34605
|
+
try {
|
|
34606
|
+
this.onDeviceEvent(sdk_1.ScryptedInterface.SecuritySystem, newState);
|
|
34607
|
+
}
|
|
34608
|
+
catch { }
|
|
34566
34609
|
return;
|
|
34567
34610
|
}
|
|
34568
34611
|
if (topic === tTarget) {
|
|
@@ -34598,7 +34641,7 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34598
34641
|
async armSecuritySystem(mode) {
|
|
34599
34642
|
const payload = this.getOutgoing(mode);
|
|
34600
34643
|
this.console.log('armSecuritySystem', mode, '->', payload);
|
|
34601
|
-
this.pendingTarget = mode;
|
|
34644
|
+
this.pendingTarget = mode;
|
|
34602
34645
|
this.publishSetTarget(payload);
|
|
34603
34646
|
}
|
|
34604
34647
|
async disarmSecuritySystem() {
|
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED