@rfranzoi/scrypted-mqtt-securitysystem 1.0.37 → 1.0.39

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.
@@ -34029,7 +34029,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
34029
34029
  };
34030
34030
  Object.defineProperty(exports, "__esModule", ({ value: true }));
34031
34031
  // --- Preload: silenzia SOLO il warning facoltativo di sdk.json ---
34032
- // Copre console.error e console.warn (alcune versioni usano warn).
34033
34032
  (() => {
34034
34033
  const swallow = (orig) => (...args) => {
34035
34034
  const txt = args.map(a => typeof a === 'string' ? a : (a?.message || '')).join(' ');
@@ -34040,36 +34039,73 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
34040
34039
  console.error = swallow(console.error.bind(console));
34041
34040
  console.warn = swallow(console.warn.bind(console));
34042
34041
  })();
34043
- // Carica lo SDK (runtime only: niente import ESM per evitare che il bundler lo esegua prima del preload)
34042
+ // Runtime SDK via require (evita esecuzione anticipata del bundler)
34044
34043
  const sdk = __webpack_require__(/*! @scrypted/sdk */ "./node_modules/@scrypted/sdk/dist/src/index.js");
34045
- // Valori runtime dal modulo SDK
34046
- const { ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, // enum (valori)
34047
- SecuritySystemMode, // enum (valori)
34048
- systemManager, } = sdk;
34044
+ const { ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, SecuritySystemMode, systemManager, } = sdk;
34049
34045
  const mqtt_1 = __importDefault(__webpack_require__(/*! mqtt */ "./node_modules/mqtt/build/index.js"));
34050
34046
  /** utils */
34047
+ function normalize(s) { return (s || '').trim().toLowerCase(); }
34051
34048
  function truthy(v) {
34052
34049
  if (!v)
34053
34050
  return false;
34054
- const s = v.toString().trim().toLowerCase();
34055
- return s === '1' || s === 'true' || s === 'online' || s === 'yes' || s === 'on' || s === 'ok';
34051
+ const s = normalize(v);
34052
+ return s === '1' || s === 'true' || s === 'online' || s === 'yes' || s === 'on' || s === 'ok' || s === 'open';
34056
34053
  }
34057
34054
  function falsy(v) {
34058
34055
  if (!v)
34059
34056
  return false;
34060
- const s = v.toString().trim().toLowerCase();
34061
- return s === '0' || s === 'false' || s === 'offline' || s === 'no' || s === 'off';
34057
+ const s = normalize(v);
34058
+ return s === '0' || s === 'false' || s === 'offline' || s === 'no' || s === 'off' || s === 'closed' || s === 'close';
34062
34059
  }
34063
- function normalize(s) { return (s || '').trim().toLowerCase(); }
34064
34060
  function clamp(n, min, max) { return Math.max(min, Math.min(max, n)); }
34065
- /** Outgoing predefiniti (PAI-like). Chiavi numeriche per compatibilità enum */
34061
+ function deepEqual(a, b) { try {
34062
+ return JSON.stringify(a) === JSON.stringify(b);
34063
+ }
34064
+ catch {
34065
+ return a === b;
34066
+ } }
34067
+ /** Match topic con wildcard MQTT (+, #) oppure confronto esatto */
34068
+ function topicMatches(topic, pattern) {
34069
+ if (!pattern)
34070
+ return false;
34071
+ if (pattern === topic)
34072
+ return true;
34073
+ if (!pattern.includes('+') && !pattern.includes('#'))
34074
+ return false;
34075
+ // Escapa tutto tranne '/'
34076
+ const esc = pattern.replace(/[-/\\^$*?.()|[\]{}]/g, '\\$&');
34077
+ const rx = '^' + esc
34078
+ .replace(/\\\+/g, '[^/]+') // '+' => un segmento
34079
+ .replace(/\\\#/g, '.+'); // '#" => qualsiasi suffisso
34080
+ try {
34081
+ return new RegExp(rx + '$').test(topic);
34082
+ }
34083
+ catch {
34084
+ return false;
34085
+ }
34086
+ }
34087
+ /** set + emit evento scrypted */
34088
+ function setAndEmit(dev, key, value, iface, log) {
34089
+ if (dev[key] === value)
34090
+ return;
34091
+ dev[key] = value;
34092
+ try {
34093
+ dev.onDeviceEvent?.(iface, value);
34094
+ }
34095
+ catch { }
34096
+ try {
34097
+ if (log)
34098
+ dev.console?.log?.(log);
34099
+ }
34100
+ catch { }
34101
+ }
34102
+ /** Outgoing predefiniti (PAI-like). Chiavi numeriche per compat enum */
34066
34103
  const DEFAULT_OUTGOING = {
34067
34104
  [SecuritySystemMode.Disarmed]: 'disarm',
34068
34105
  [SecuritySystemMode.HomeArmed]: 'arm_home',
34069
34106
  [SecuritySystemMode.AwayArmed]: 'arm_away',
34070
34107
  [SecuritySystemMode.NightArmed]: 'arm_night',
34071
34108
  };
34072
- /** Parse incoming payload -> final mode (ignora transitori) */
34073
34109
  function payloadToMode(payload) {
34074
34110
  if (payload == null)
34075
34111
  return;
@@ -34092,159 +34128,148 @@ class BaseMqttSensor extends ScryptedDeviceBase {
34092
34128
  this.cfg = cfg;
34093
34129
  }
34094
34130
  handleMqtt(topic, payload) {
34095
- const p = payload?.toString() ?? '';
34096
- const np = normalize(p);
34097
- if (topic === this.cfg.topics.online) {
34131
+ const raw = payload?.toString() ?? '';
34132
+ const np = normalize(raw);
34133
+ // Online
34134
+ if (topicMatches(topic, this.cfg.topics.online)) {
34098
34135
  if (truthy(np) || np === 'online')
34099
- this.online = true;
34100
- if (falsy(np) || np === 'offline')
34101
- this.online = false;
34136
+ setAndEmit(this, 'online', true, ScryptedInterface.Online, `[${this.name}] online=true`);
34137
+ else if (falsy(np) || np === 'offline')
34138
+ setAndEmit(this, 'online', false, ScryptedInterface.Online, `[${this.name}] online=false`);
34102
34139
  }
34103
- if (topic === this.cfg.topics.tamper) {
34140
+ // Tamper
34141
+ if (topicMatches(topic, this.cfg.topics.tamper)) {
34104
34142
  if (truthy(np) || ['tamper', 'intrusion', 'cover', 'motion', 'magnetic'].includes(np)) {
34105
- this.tampered = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
34143
+ const t = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
34144
+ setAndEmit(this, 'tampered', t, ScryptedInterface.TamperSensor, `[${this.name}] tampered=${t}`);
34145
+ }
34146
+ else if (falsy(np)) {
34147
+ setAndEmit(this, 'tampered', false, ScryptedInterface.TamperSensor, `[${this.name}] tampered=false`);
34106
34148
  }
34107
- else if (falsy(np))
34108
- this.tampered = false;
34109
34149
  }
34110
- if (topic === this.cfg.topics.batteryLevel) {
34111
- const n = clamp(parseFloat(p), 0, 100);
34150
+ // Battery
34151
+ if (topicMatches(topic, this.cfg.topics.batteryLevel)) {
34152
+ const n = clamp(parseFloat(raw), 0, 100);
34112
34153
  if (isFinite(n))
34113
- this.batteryLevel = n;
34154
+ setAndEmit(this, 'batteryLevel', n, ScryptedInterface.Battery, `[${this.name}] batteryLevel=${n}`);
34114
34155
  }
34115
- else if (topic === this.cfg.topics.lowBattery && !this.cfg.topics.batteryLevel) {
34116
- this.batteryLevel = truthy(np) ? 10 : 100;
34156
+ else if (topicMatches(topic, this.cfg.topics.lowBattery) && !this.cfg.topics.batteryLevel) {
34157
+ const n = truthy(np) ? 10 : 100;
34158
+ setAndEmit(this, 'batteryLevel', n, ScryptedInterface.Battery, `[${this.name}] batteryLevel=${n} (lowBattery)`);
34117
34159
  }
34118
- this.handlePrimary(topic, np, p);
34160
+ // Primario
34161
+ this.handlePrimary(topic, np, raw);
34119
34162
  }
34120
34163
  }
34121
- /** === SENSORI CON PARSING ROBUSTO PER HOMEKIT === */
34164
+ /** === SENSORI: parsing robusto + eventi === */
34122
34165
  class ContactMqttSensor extends BaseMqttSensor {
34123
34166
  handlePrimary(topic, np, raw) {
34124
- if (topic !== this.cfg.topics.contact)
34125
- return;
34126
- // stringhe comuni
34127
- if (['open', 'opened', '1', 'true', 'on', 'yes'].includes(np)) {
34128
- this.entryOpen = true;
34129
- return;
34130
- }
34131
- if (['closed', 'close', '0', 'false', 'off', 'no', 'shut'].includes(np)) {
34132
- this.entryOpen = false;
34167
+ if (!topicMatches(topic, this.cfg.topics.contact))
34133
34168
  return;
34134
- }
34169
+ let val;
34170
+ // stringhe comuni (True/False compresi via normalize)
34171
+ if (['open', 'opened', '1', 'true', 'on', 'yes'].includes(np))
34172
+ val = true;
34173
+ else if (['closed', 'close', '0', 'false', 'off', 'no', 'shut'].includes(np))
34174
+ val = false;
34135
34175
  // JSON comuni
34136
- try {
34137
- const j = JSON.parse(raw);
34138
- if (typeof j?.open === 'boolean') {
34139
- this.entryOpen = !!j.open;
34140
- return;
34141
- }
34142
- if (typeof j?.opened === 'boolean') {
34143
- this.entryOpen = !!j.opened;
34144
- return;
34145
- }
34146
- if (typeof j?.contact === 'boolean') {
34147
- this.entryOpen = !j.contact;
34148
- return;
34149
- } // contact:false => OPEN
34150
- if (typeof j?.state === 'string') {
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;
34176
+ if (val === undefined) {
34177
+ try {
34178
+ const j = JSON.parse(raw);
34179
+ if (typeof j?.open === 'boolean')
34180
+ val = !!j.open;
34181
+ else if (typeof j?.opened === 'boolean')
34182
+ val = !!j.opened;
34183
+ else if (typeof j?.contact === 'boolean')
34184
+ val = !j.contact; // contact:false => aperto
34185
+ else if (typeof j?.state === 'string') {
34186
+ const s = normalize(j.state);
34187
+ if (s === 'open')
34188
+ val = true;
34189
+ if (s === 'closed')
34190
+ val = false;
34159
34191
  }
34160
34192
  }
34193
+ catch { }
34194
+ }
34195
+ if (val !== undefined) {
34196
+ setAndEmit(this, 'entryOpen', val, ScryptedInterface.EntrySensor, `[${this.name}] entryOpen=${val} (${topic})`);
34197
+ }
34198
+ else {
34199
+ this.console?.debug?.(`Contact payload non gestito (${this.cfg.id}) topic=${topic} raw="${raw}"`);
34161
34200
  }
34162
- catch { }
34163
- this.console?.debug?.(`Contact payload non gestito (${this.cfg.id}): "${raw}"`);
34164
34201
  }
34165
34202
  }
34166
34203
  class MotionMqttSensor extends BaseMqttSensor {
34167
34204
  handlePrimary(topic, np, raw) {
34168
- if (topic !== this.cfg.topics.motion)
34205
+ if (!topicMatches(topic, this.cfg.topics.motion))
34169
34206
  return;
34170
- if (['motion', 'detected', 'active', '1', 'true', 'on', 'yes'].includes(np)) {
34171
- this.motionDetected = true;
34172
- return;
34173
- }
34174
- if (['clear', 'inactive', 'no_motion', 'none', '0', 'false', 'off', 'no'].includes(np)) {
34175
- this.motionDetected = false;
34176
- return;
34177
- }
34178
- try {
34179
- const j = JSON.parse(raw);
34180
- if (typeof j?.motion === 'boolean') {
34181
- this.motionDetected = !!j.motion;
34182
- return;
34183
- }
34184
- if (typeof j?.occupancy === 'boolean') {
34185
- this.motionDetected = !!j.occupancy;
34186
- return;
34187
- }
34188
- if (typeof j?.presence === 'boolean') {
34189
- this.motionDetected = !!j.presence;
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;
34207
+ let val;
34208
+ if (['motion', 'detected', 'active', '1', 'true', 'on', 'yes'].includes(np))
34209
+ val = true;
34210
+ else if (['clear', 'inactive', 'no_motion', 'none', '0', 'false', 'off', 'no'].includes(np))
34211
+ val = false;
34212
+ if (val === undefined) {
34213
+ try {
34214
+ const j = JSON.parse(raw);
34215
+ if (typeof j?.motion === 'boolean')
34216
+ val = !!j.motion;
34217
+ else if (typeof j?.occupancy === 'boolean')
34218
+ val = !!j.occupancy;
34219
+ else if (typeof j?.presence === 'boolean')
34220
+ val = !!j.presence;
34221
+ else if (typeof j?.state === 'string') {
34222
+ const s = normalize(j.state);
34223
+ if (['on', 'motion', 'detected', 'active'].includes(s))
34224
+ val = true;
34225
+ if (['off', 'clear', 'inactive'].includes(s))
34226
+ val = false;
34201
34227
  }
34202
34228
  }
34229
+ catch { }
34230
+ }
34231
+ if (val !== undefined) {
34232
+ setAndEmit(this, 'motionDetected', val, ScryptedInterface.MotionSensor, `[${this.name}] motionDetected=${val} (${topic})`);
34233
+ }
34234
+ else {
34235
+ this.console?.debug?.(`Motion payload non gestito (${this.cfg.id}) topic=${topic} raw="${raw}"`);
34203
34236
  }
34204
- catch { }
34205
- this.console?.debug?.(`Motion payload non gestito (${this.cfg.id}): "${raw}"`);
34206
34237
  }
34207
34238
  }
34208
34239
  class OccupancyMqttSensor extends BaseMqttSensor {
34209
34240
  handlePrimary(topic, np, raw) {
34210
- if (topic !== this.cfg.topics.occupancy)
34211
- return;
34212
- if (['occupied', 'presence', 'present', '1', 'true', 'on', 'yes'].includes(np)) {
34213
- this.occupied = true;
34241
+ if (!topicMatches(topic, this.cfg.topics.occupancy))
34214
34242
  return;
34215
- }
34216
- if (['unoccupied', 'vacant', 'absent', '0', 'false', 'off', 'no', 'clear'].includes(np)) {
34217
- this.occupied = false;
34218
- return;
34219
- }
34220
- try {
34221
- const j = JSON.parse(raw);
34222
- if (typeof j?.occupied === 'boolean') {
34223
- this.occupied = !!j.occupied;
34224
- return;
34225
- }
34226
- if (typeof j?.presence === 'boolean') {
34227
- this.occupied = !!j.presence;
34228
- return;
34229
- }
34230
- if (typeof j?.occupancy === 'boolean') {
34231
- this.occupied = !!j.occupancy;
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;
34243
+ let val;
34244
+ if (['occupied', 'presence', 'present', '1', 'true', 'on', 'yes'].includes(np))
34245
+ val = true;
34246
+ else if (['unoccupied', 'vacant', 'absent', '0', 'false', 'off', 'no', 'clear'].includes(np))
34247
+ val = false;
34248
+ if (val === undefined) {
34249
+ try {
34250
+ const j = JSON.parse(raw);
34251
+ if (typeof j?.occupied === 'boolean')
34252
+ val = !!j.occupied;
34253
+ else if (typeof j?.presence === 'boolean')
34254
+ val = !!j.presence;
34255
+ else if (typeof j?.occupancy === 'boolean')
34256
+ val = !!j.occupancy;
34257
+ else if (typeof j?.state === 'string') {
34258
+ const s = normalize(j.state);
34259
+ if (['occupied', 'presence', 'present', 'on'].includes(s))
34260
+ val = true;
34261
+ if (['vacant', 'absent', 'clear', 'off'].includes(s))
34262
+ val = false;
34243
34263
  }
34244
34264
  }
34265
+ catch { }
34266
+ }
34267
+ if (val !== undefined) {
34268
+ setAndEmit(this, 'occupied', val, ScryptedInterface.OccupancySensor, `[${this.name}] occupied=${val} (${topic})`);
34269
+ }
34270
+ else {
34271
+ this.console?.debug?.(`Occupancy payload non gestito (${this.cfg.id}) topic=${topic} raw="${raw}"`);
34245
34272
  }
34246
- catch { }
34247
- this.console?.debug?.(`Occupancy payload non gestito (${this.cfg.id}): "${raw}"`);
34248
34273
  }
34249
34274
  }
34250
34275
  /** ----------------- Main Plugin ----------------- */
@@ -34253,7 +34278,6 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34253
34278
  super();
34254
34279
  this.sensorsCfg = [];
34255
34280
  this.devices = new Map();
34256
- // Evita loop di log: tenta una volta finché deviceManager non c’è, poi riprova su eventi utili.
34257
34281
  this.discoveryPostponed = false;
34258
34282
  // Tipo in UI (best-effort)
34259
34283
  setTimeout(() => {
@@ -34273,12 +34297,9 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34273
34297
  ],
34274
34298
  };
34275
34299
  this.online = this.online ?? false;
34276
- // Config sensori e (tentativo) announce
34277
34300
  this.loadSensorsFromStorage();
34278
- this.safeDiscoverSensors(); // non spamma se deviceManager non c'è
34279
- // Connect MQTT
34301
+ this.safeDiscoverSensors();
34280
34302
  this.connectMqtt().catch((e) => this.console.error('MQTT connect error:', e));
34281
- // Shutdown pulito
34282
34303
  try {
34283
34304
  process.once('SIGTERM', () => { try {
34284
34305
  this.client?.end(true);
@@ -34322,12 +34343,11 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34322
34343
  ];
34323
34344
  // Add Sensor
34324
34345
  out.push({ group: 'Add Sensor', key: 'new.id', title: 'New Sensor ID', placeholder: 'porta-ingresso', value: this.storage.getItem('new.id') || '' }, { group: 'Add Sensor', key: 'new.name', title: 'Name', placeholder: 'Porta Ingresso', value: this.storage.getItem('new.name') || '' }, { group: 'Add Sensor', key: 'new.kind', title: 'Type', value: this.storage.getItem('new.kind') || 'contact', choices: ['contact', 'motion', 'occupancy'] }, { group: 'Add Sensor', key: 'new.create', title: 'Create sensor', type: 'boolean', description: 'Toggle ON to create the sensor.' });
34325
- // Sensors esistenti
34326
34346
  for (const cfg of this.sensorsCfg) {
34327
34347
  const gid = `Sensor: ${cfg.name} [${cfg.id}]`;
34328
34348
  out.push({ group: gid, key: `sensor.${cfg.id}.name`, title: 'Name', value: cfg.name }, { group: gid, key: `sensor.${cfg.id}.kind`, title: 'Type', value: cfg.kind, choices: ['contact', 'motion', 'occupancy'] });
34329
34349
  if (cfg.kind === 'contact')
34330
- out.push({ group: gid, key: `sensor.${cfg.id}.topic.contact`, title: 'Contact State Topic', value: cfg.topics.contact || '' });
34350
+ out.push({ group: gid, key: `sensor.${cfg.id}.topic.contact`, title: 'Contact State Topic', value: cfg.topics.contact || '', placeholder: 'paradox/states/zones/XYZ/open (supporta +/#)' });
34331
34351
  else if (cfg.kind === 'motion')
34332
34352
  out.push({ group: gid, key: `sensor.${cfg.id}.topic.motion`, title: 'Motion Detected Topic', value: cfg.topics.motion || '' });
34333
34353
  else
@@ -34430,11 +34450,9 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34430
34450
  this.sensorsCfg = [];
34431
34451
  }
34432
34452
  }
34433
- /** Annuncia i sensori SOLO se deviceManager è pronto. Niente loop infinito. */
34434
34453
  safeDiscoverSensors(triggeredByChange = false) {
34435
34454
  const dmAny = sdk?.deviceManager;
34436
34455
  if (!dmAny) {
34437
- // Posticipa una sola volta; poi riproviamo su connect MQTT e al primo messaggio
34438
34456
  if (!this.discoveryPostponed) {
34439
34457
  this.console.log('Device discovery postponed: deviceManager not ready yet.');
34440
34458
  this.discoveryPostponed = true;
@@ -34446,9 +34464,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34446
34464
  if (triggeredByChange)
34447
34465
  this.console.log('Sensors discovered/updated.');
34448
34466
  }
34449
- /** discoverSensors con deviceManager garantito */
34450
34467
  discoverSensors(dmAny) {
34451
- // 1) Manifests
34452
34468
  const manifests = this.sensorsCfg.map(cfg => {
34453
34469
  const nativeId = `sensor:${cfg.id}`;
34454
34470
  const t = cfg.topics || {};
@@ -34465,15 +34481,11 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34465
34481
  interfaces.push(ScryptedInterface.Battery);
34466
34482
  return { nativeId, name: cfg.name, type: ScryptedDeviceType.Sensor, interfaces };
34467
34483
  });
34468
- // 2) Annuncio
34469
- if (typeof dmAny.onDevicesChanged === 'function') {
34484
+ if (typeof dmAny.onDevicesChanged === 'function')
34470
34485
  dmAny.onDevicesChanged({ devices: manifests });
34471
- }
34472
- else if (typeof dmAny.onDeviceDiscovered === 'function') {
34486
+ else if (typeof dmAny.onDeviceDiscovered === 'function')
34473
34487
  for (const m of manifests)
34474
34488
  dmAny.onDeviceDiscovered(m);
34475
- }
34476
- // 3) Istanzia/aggiorna
34477
34489
  for (const cfg of this.sensorsCfg) {
34478
34490
  const nativeId = `sensor:${cfg.id}`;
34479
34491
  let dev = this.devices.get(nativeId);
@@ -34491,9 +34503,8 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34491
34503
  }
34492
34504
  const hasBattery = !!(cfg.topics.batteryLevel || cfg.topics.lowBattery);
34493
34505
  if (hasBattery && dev.batteryLevel === undefined)
34494
- dev.batteryLevel = 100;
34506
+ setAndEmit(dev, 'batteryLevel', 100, ScryptedInterface.Battery, `[${cfg.name}] batteryLevel=100 (default)`);
34495
34507
  }
34496
- // 4) Cleanup
34497
34508
  const announced = new Set(manifests.map(m => m.nativeId));
34498
34509
  for (const [nativeId] of this.devices) {
34499
34510
  if (!announced.has(nativeId)) {
@@ -34556,39 +34567,41 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34556
34567
  const tOnline = this.storage.getItem('topicOnline') || '';
34557
34568
  client.on('connect', () => {
34558
34569
  this.console.log('MQTT connected');
34559
- this.online = true;
34570
+ setAndEmit(this, 'online', true, ScryptedInterface.Online, `[Alarm] online=true`);
34560
34571
  if (subs.length)
34561
34572
  client.subscribe(subs, { qos: 0 }, (err) => { if (err)
34562
34573
  this.console.error('subscribe error', err); });
34563
- // Al primo connect riprova (silenziosamente) ad annunciare i sensori
34564
34574
  this.safeDiscoverSensors(true);
34565
34575
  });
34566
34576
  client.on('reconnect', () => this.console.log('MQTT reconnecting...'));
34567
- client.on('close', () => { this.console.log('MQTT closed'); this.online = false; });
34577
+ client.on('close', () => { this.console.log('MQTT closed'); setAndEmit(this, 'online', false, ScryptedInterface.Online, `[Alarm] online=false`); });
34568
34578
  client.on('error', (e) => { this.console.error('MQTT error', e); });
34569
34579
  client.on('message', (topic, payload) => {
34570
34580
  try {
34571
- const p = payload?.toString() ?? '';
34572
- const np = normalize(p);
34573
- if (topic === tOnline) {
34581
+ const raw = payload?.toString() ?? '';
34582
+ const np = normalize(raw);
34583
+ if (topicMatches(topic, tOnline)) {
34574
34584
  if (truthy(np) || np === 'online')
34575
- this.online = true;
34576
- if (falsy(np) || np === 'offline')
34577
- this.online = false;
34585
+ setAndEmit(this, 'online', true, ScryptedInterface.Online, `[Alarm] online=true (${topic})`);
34586
+ else if (falsy(np) || np === 'offline')
34587
+ setAndEmit(this, 'online', false, ScryptedInterface.Online, `[Alarm] online=false (${topic})`);
34578
34588
  return;
34579
34589
  }
34580
- if (topic === tTamper) {
34581
- if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np))
34582
- this.tampered = ['cover', 'intrusion'].find(x => x === np) || true;
34583
- else if (falsy(np))
34584
- this.tampered = false;
34590
+ if (topicMatches(topic, tTamper)) {
34591
+ if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np)) {
34592
+ const t = ['cover', 'intrusion'].find(x => x === np) || true;
34593
+ setAndEmit(this, 'tampered', t, ScryptedInterface.TamperSensor, `[Alarm] tampered=${t} (${topic})`);
34594
+ }
34595
+ else if (falsy(np)) {
34596
+ setAndEmit(this, 'tampered', false, ScryptedInterface.TamperSensor, `[Alarm] tampered=false (${topic})`);
34597
+ }
34585
34598
  return;
34586
34599
  }
34587
- if (topic === tCurrent) {
34600
+ if (topicMatches(topic, tCurrent)) {
34588
34601
  const mode = payloadToMode(payload);
34589
34602
  const isAlarm = ['alarm', 'triggered'].includes(np);
34590
34603
  const current = this.securitySystemState || { mode: SecuritySystemMode.Disarmed };
34591
- this.securitySystemState = {
34604
+ const newState = {
34592
34605
  mode: mode ?? current.mode,
34593
34606
  supportedModes: current.supportedModes ?? [
34594
34607
  SecuritySystemMode.Disarmed,
@@ -34598,15 +34611,22 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34598
34611
  ],
34599
34612
  triggered: isAlarm || undefined,
34600
34613
  };
34614
+ if (!deepEqual(this.securitySystemState, newState)) {
34615
+ this.securitySystemState = newState;
34616
+ try {
34617
+ this.onDeviceEvent?.(ScryptedInterface.SecuritySystem, newState);
34618
+ }
34619
+ catch { }
34620
+ this.console.log(`[Alarm] currentState=${JSON.stringify(newState)} (${topic})`);
34621
+ }
34601
34622
  return;
34602
34623
  }
34603
- if (topic === tTarget) {
34624
+ if (topicMatches(topic, tTarget)) {
34604
34625
  this.pendingTarget = payloadToMode(payload);
34605
- this.console.log('Target state reported:', p, '->', this.pendingTarget);
34626
+ this.console.log(`[Alarm] target reported: "${raw}" -> ${this.pendingTarget} (${topic})`);
34606
34627
  return;
34607
34628
  }
34608
- // Dispatch ai sensori
34609
- // (E prova ad annunciare se era stato posticipato e ora il manager è pronto)
34629
+ // Sensors: se discovery rimandata, riprova
34610
34630
  if (this.discoveryPostponed)
34611
34631
  this.safeDiscoverSensors(true);
34612
34632
  for (const dev of this.devices.values())
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.37",
3
+ "version": "1.0.39",
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",