@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.
@@ -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
- return (s || '').trim().toLowerCase();
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
- // non impostare stati qui: l'annuncio device deve avvenire prima
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 p = payload?.toString() ?? '';
34120
- const np = normalize(p);
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 = true;
34131
+ this.setAndEmit('online', true, sdk_1.ScryptedInterface.Online);
34125
34132
  if (falsy(np) || np === 'offline')
34126
- this.online = false;
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
- this.tampered = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
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 = false;
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
- const n = clamp(parseFloat(p), 0, 100);
34140
- if (isFinite(n))
34141
- this.batteryLevel = n;
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
- // Solo se abbiamo lowBattery (booleano) ma NON batteryLevel:
34145
- // True -> 10% (warning)
34146
- // False -> 100% (ok)
34147
- this.batteryLevel = truthy(np) ? 10 : 100;
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, p);
34162
+ this.handlePrimary(topic, np, raw);
34151
34163
  }
34152
34164
  }
34153
34165
  class ContactMqttSensor extends BaseMqttSensor {
34154
- constructor(nativeId, cfg) {
34155
- super(nativeId, cfg);
34156
- }
34157
- handlePrimary(topic, np, _raw) {
34166
+ handlePrimary(topic, np) {
34158
34167
  if (topic === this.cfg.topics.contact) {
34159
- this.entryOpen = truthy(np);
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
- constructor(nativeId, cfg) {
34165
- super(nativeId, cfg);
34166
- }
34167
- handlePrimary(topic, np, _raw) {
34180
+ handlePrimary(topic, np) {
34168
34181
  if (topic === this.cfg.topics.motion) {
34169
- this.motionDetected = truthy(np);
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
- constructor(nativeId, cfg) {
34175
- super(nativeId, cfg);
34176
- }
34177
- handlePrimary(topic, np, _raw) {
34194
+ handlePrimary(topic, np) {
34178
34195
  if (topic === this.cfg.topics.occupancy) {
34179
- this.occupied = truthy(np);
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) Prepara i manifest (niente istanze qui)
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 solo se previsto
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
- // Default “OK” se abbiamo Battery ma nessun valore ancora ricevuto
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(Boolean)
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', () => { this.console.log('MQTT closed'); this.online = false; });
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
- if (falsy(np) || np === 'offline')
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
- this.tampered = ['cover', 'intrusion'].find(x => x === np) || true;
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; // memorizza target, ma NON cambiare il current
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rfranzoi/scrypted-mqtt-securitysystem",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
4
4
  "description": "Scrypted plugin: Paradox Security System via MQTT (PAI/PAI-MQTT style).",
5
5
  "license": "MIT",
6
6
  "main": "dist/main.nodejs.js",