@rfranzoi/scrypted-mqtt-securitysystem 1.0.34 → 1.0.36

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.
@@ -34028,23 +34028,24 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
34028
34028
  return (mod && mod.__esModule) ? mod : { "default": mod };
34029
34029
  };
34030
34030
  Object.defineProperty(exports, "__esModule", ({ value: true }));
34031
- // --- Silence ONLY the optional sdk.json warning from @scrypted/sdk ---
34032
- const __origConsoleError = console.error.bind(console);
34033
- console.error = (...args) => {
34034
- const first = args?.[0];
34035
- const msg = typeof first === 'string' ? first : (first?.message || '');
34036
- if (typeof msg === 'string' && msg.includes('failed to load custom interface descriptors')) {
34037
- // swallow just this warning
34038
- return;
34039
- }
34040
- __origConsoleError(...args);
34041
- };
34042
- // Carica lo SDK (non tipizziamo qui per non perdere le proprietà runtime)
34031
+ // --- Preload: silenzia SOLO il warning facoltativo di sdk.json ---
34032
+ // Copre console.error e console.warn (alcune versioni usano warn).
34033
+ (() => {
34034
+ const swallow = (orig) => (...args) => {
34035
+ const txt = args.map(a => typeof a === 'string' ? a : (a?.message || '')).join(' ');
34036
+ if (txt.includes('failed to load custom interface descriptors'))
34037
+ return;
34038
+ return orig(...args);
34039
+ };
34040
+ console.error = swallow(console.error.bind(console));
34041
+ console.warn = swallow(console.warn.bind(console));
34042
+ })();
34043
+ // Carica lo SDK (runtime only: niente import ESM per evitare che il bundler lo esegua prima del preload)
34043
34044
  const sdk = __webpack_require__(/*! @scrypted/sdk */ "./node_modules/@scrypted/sdk/dist/src/index.js");
34044
- // Valori runtime (enum/classi/manager) dal modulo SDK
34045
- const { ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, // valore (enum)
34046
- SecuritySystemMode, // valore (enum)
34047
- systemManager, deviceManager, } = sdk;
34045
+ // Valori runtime dal modulo SDK
34046
+ const { ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, // enum (valori)
34047
+ SecuritySystemMode, // enum (valori)
34048
+ systemManager, } = sdk;
34048
34049
  const mqtt_1 = __importDefault(__webpack_require__(/*! mqtt */ "./node_modules/mqtt/build/index.js"));
34049
34050
  /** utils */
34050
34051
  function truthy(v) {
@@ -34059,21 +34060,16 @@ function falsy(v) {
34059
34060
  const s = v.toString().trim().toLowerCase();
34060
34061
  return s === '0' || s === 'false' || s === 'offline' || s === 'no' || s === 'off';
34061
34062
  }
34062
- function normalize(s) {
34063
- return (s || '').trim().toLowerCase();
34064
- }
34065
- function clamp(n, min, max) {
34066
- return Math.max(min, Math.min(max, n));
34067
- }
34068
- /** SecuritySystem outgoing defaults (PAI-like)
34069
- * Nota: usare number come chiave evita stranezze con gli enum in TS. */
34063
+ function normalize(s) { return (s || '').trim().toLowerCase(); }
34064
+ function clamp(n, min, max) { return Math.max(min, Math.min(max, n)); }
34065
+ /** Outgoing predefiniti (PAI-like). Chiavi numeriche per compatibilità enum */
34070
34066
  const DEFAULT_OUTGOING = {
34071
34067
  [SecuritySystemMode.Disarmed]: 'disarm',
34072
34068
  [SecuritySystemMode.HomeArmed]: 'arm_home',
34073
34069
  [SecuritySystemMode.AwayArmed]: 'arm_away',
34074
34070
  [SecuritySystemMode.NightArmed]: 'arm_night',
34075
34071
  };
34076
- /** Parse incoming payload -> final mode (ignore transition states) */
34072
+ /** Parse incoming payload -> final mode (ignora transitori) */
34077
34073
  function payloadToMode(payload) {
34078
34074
  if (payload == null)
34079
34075
  return;
@@ -34086,7 +34082,6 @@ function payloadToMode(payload) {
34086
34082
  return SecuritySystemMode.AwayArmed;
34087
34083
  if (['arm_night', 'night', 'armed_night', 'sleep', 'arm_sleep', 'armed_sleep'].includes(p))
34088
34084
  return SecuritySystemMode.NightArmed;
34089
- // transitori: non cambiano il mode
34090
34085
  if (['entry_delay', 'exit_delay', 'pending', 'arming', 'disarming'].includes(p))
34091
34086
  return undefined;
34092
34087
  return undefined;
@@ -34096,76 +34091,67 @@ class BaseMqttSensor extends ScryptedDeviceBase {
34096
34091
  super(nativeId);
34097
34092
  this.cfg = cfg;
34098
34093
  }
34099
- /** Called by parent on each MQTT message */
34100
34094
  handleMqtt(topic, payload) {
34101
34095
  const p = payload?.toString() ?? '';
34102
34096
  const np = normalize(p);
34103
- // online
34104
34097
  if (topic === this.cfg.topics.online) {
34105
34098
  if (truthy(np) || np === 'online')
34106
34099
  this.online = true;
34107
34100
  if (falsy(np) || np === 'offline')
34108
34101
  this.online = false;
34109
34102
  }
34110
- // tamper
34111
34103
  if (topic === this.cfg.topics.tamper) {
34112
34104
  if (truthy(np) || ['tamper', 'intrusion', 'cover', 'motion', 'magnetic'].includes(np)) {
34113
34105
  this.tampered = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
34114
34106
  }
34115
- else if (falsy(np)) {
34107
+ else if (falsy(np))
34116
34108
  this.tampered = false;
34117
- }
34118
34109
  }
34119
- // battery
34120
34110
  if (topic === this.cfg.topics.batteryLevel) {
34121
34111
  const n = clamp(parseFloat(p), 0, 100);
34122
34112
  if (isFinite(n))
34123
34113
  this.batteryLevel = n;
34124
34114
  }
34125
34115
  else if (topic === this.cfg.topics.lowBattery && !this.cfg.topics.batteryLevel) {
34126
- // se abbiamo SOLO lowBattery (bool):
34127
34116
  this.batteryLevel = truthy(np) ? 10 : 100;
34128
34117
  }
34129
- // primary handled by subclasses
34130
34118
  this.handlePrimary(topic, np, p);
34131
34119
  }
34132
34120
  }
34133
34121
  class ContactMqttSensor extends BaseMqttSensor {
34134
34122
  handlePrimary(topic, np) {
34135
- if (topic === this.cfg.topics.contact) {
34123
+ if (topic === this.cfg.topics.contact)
34136
34124
  this.entryOpen = truthy(np);
34137
- }
34138
34125
  }
34139
34126
  }
34140
34127
  class MotionMqttSensor extends BaseMqttSensor {
34141
34128
  handlePrimary(topic, np) {
34142
- if (topic === this.cfg.topics.motion) {
34129
+ if (topic === this.cfg.topics.motion)
34143
34130
  this.motionDetected = truthy(np);
34144
- }
34145
34131
  }
34146
34132
  }
34147
34133
  class OccupancyMqttSensor extends BaseMqttSensor {
34148
34134
  handlePrimary(topic, np) {
34149
- if (topic === this.cfg.topics.occupancy) {
34135
+ if (topic === this.cfg.topics.occupancy)
34150
34136
  this.occupied = truthy(np);
34151
- }
34152
34137
  }
34153
34138
  }
34154
34139
  /** ----------------- Main Plugin ----------------- */
34155
34140
  class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34156
34141
  constructor() {
34157
34142
  super();
34158
- // sensor management
34159
34143
  this.sensorsCfg = [];
34160
34144
  this.devices = new Map();
34161
- // (facoltativo) Imposta il device type in UI
34145
+ // evitiamo spam: ricordiamo se abbiamo già provato ad annunciare
34146
+ this.triedDiscoveryOnce = false;
34147
+ // Tipo in UI (best-effort)
34162
34148
  setTimeout(() => {
34163
34149
  try {
34164
34150
  systemManager.getDeviceById(this.id)?.setType?.(ScryptedDeviceType.SecuritySystem);
34165
34151
  }
34166
34152
  catch { }
34167
34153
  });
34168
- // Default state
34154
+ // Stato di default
34169
34155
  this.securitySystemState = this.securitySystemState || {
34170
34156
  mode: SecuritySystemMode.Disarmed,
34171
34157
  supportedModes: [
@@ -34176,12 +34162,12 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34176
34162
  ],
34177
34163
  };
34178
34164
  this.online = this.online ?? false;
34179
- // Load sensors config and announce devices
34165
+ // Config sensori e (tentativo) announce
34180
34166
  this.loadSensorsFromStorage();
34181
- this.discoverSensors().catch((e) => this.console.error('discoverSensors error', e));
34182
- // Connect on start
34167
+ this.safeDiscoverSensors(); // non spamma se deviceManager non c'è
34168
+ // Connect MQTT
34183
34169
  this.connectMqtt().catch((e) => this.console.error('MQTT connect error:', e));
34184
- // chiusura pulita del client MQTT ai reload/stop del plugin
34170
+ // Shutdown pulito
34185
34171
  try {
34186
34172
  process.once('SIGTERM', () => { try {
34187
34173
  this.client?.end(true);
@@ -34198,7 +34184,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34198
34184
  }
34199
34185
  catch { }
34200
34186
  }
34201
- // helpers persistenza
34187
+ /** ---- Settings ---- */
34202
34188
  saveSensorsToStorage() {
34203
34189
  try {
34204
34190
  this.storage.setItem('sensorsJson', JSON.stringify(this.sensorsCfg));
@@ -34207,17 +34193,14 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34207
34193
  this.console.error('saveSensorsToStorage error', e);
34208
34194
  }
34209
34195
  }
34210
- /** ---- Settings UI ---- */
34211
34196
  async getSettings() {
34212
34197
  const out = [
34213
- // MQTT Core
34214
34198
  { group: 'MQTT', key: 'brokerUrl', title: 'Broker URL', placeholder: 'mqtt://127.0.0.1:1883', value: this.storage.getItem('brokerUrl') || 'mqtt://127.0.0.1:1883' },
34215
34199
  { group: 'MQTT', key: 'username', title: 'Username', type: 'string', value: this.storage.getItem('username') || '' },
34216
34200
  { group: 'MQTT', key: 'password', title: 'Password', type: 'password', value: this.storage.getItem('password') || '' },
34217
34201
  { group: 'MQTT', key: 'clientId', title: 'Client ID', placeholder: 'scrypted-paradox', value: this.storage.getItem('clientId') || 'scrypted-paradox' },
34218
34202
  { group: 'MQTT', key: 'tls', title: 'Use TLS', type: 'boolean', value: this.storage.getItem('tls') === 'true' },
34219
34203
  { group: 'MQTT', key: 'rejectUnauthorized', title: 'Reject Unauthorized (TLS)', type: 'boolean', value: this.storage.getItem('rejectUnauthorized') !== 'false', description: 'Disattiva solo con broker self-signed.' },
34220
- // Alarm Topics
34221
34204
  { group: 'Alarm Topics', key: 'topicSetTarget', title: 'Set Target State (publish)', placeholder: 'paradox/control/partitions/Area_1', value: this.storage.getItem('topicSetTarget') || '' },
34222
34205
  { group: 'Alarm Topics', key: 'topicGetTarget', title: 'Get Target State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/target_state', value: this.storage.getItem('topicGetTarget') || '' },
34223
34206
  { group: 'Alarm Topics', key: 'topicGetCurrent', title: 'Get Current State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/current_state', value: this.storage.getItem('topicGetCurrent') || '' },
@@ -34226,31 +34209,24 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34226
34209
  { group: 'Publish Options', key: 'qos', title: 'QoS', type: 'integer', value: parseInt(this.storage.getItem('qos') || '0') },
34227
34210
  { group: 'Publish Options', key: 'retain', title: 'Retain', type: 'boolean', value: this.storage.getItem('retain') === 'true' },
34228
34211
  ];
34229
- // ---- UI Add Sensor ----
34230
- 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: 'Fill the fields above and toggle this on to create the sensor. After creation, restart this plugin to see the accessory listed below. To show it in HomeKit, restart the HomeKit plugin as well.' });
34231
- // ---- UI per sensori esistenti ----
34212
+ // Add Sensor
34213
+ 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.' });
34214
+ // Sensors esistenti
34232
34215
  for (const cfg of this.sensorsCfg) {
34233
34216
  const gid = `Sensor: ${cfg.name} [${cfg.id}]`;
34234
34217
  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'] });
34235
- // primary per tipo
34236
- if (cfg.kind === 'contact') {
34237
- out.push({ group: gid, key: `sensor.${cfg.id}.topic.contact`, title: 'Contact State Topic', value: cfg.topics.contact || '', placeholder: 'paradox/states/zones/XYZ/open' });
34238
- }
34239
- else if (cfg.kind === 'motion') {
34240
- out.push({ group: gid, key: `sensor.${cfg.id}.topic.motion`, title: 'Motion Detected Topic', value: cfg.topics.motion || '', placeholder: 'paradox/states/zones/XYZ/open' });
34241
- }
34242
- else {
34243
- out.push({ group: gid, key: `sensor.${cfg.id}.topic.occupancy`, title: 'Occupancy Detected Topic', value: cfg.topics.occupancy || '', placeholder: 'paradox/states/zones/XYZ/open' });
34244
- }
34245
- // extra opzionali
34218
+ if (cfg.kind === 'contact')
34219
+ out.push({ group: gid, key: `sensor.${cfg.id}.topic.contact`, title: 'Contact State Topic', value: cfg.topics.contact || '' });
34220
+ else if (cfg.kind === 'motion')
34221
+ out.push({ group: gid, key: `sensor.${cfg.id}.topic.motion`, title: 'Motion Detected Topic', value: cfg.topics.motion || '' });
34222
+ else
34223
+ out.push({ group: gid, key: `sensor.${cfg.id}.topic.occupancy`, title: 'Occupancy Detected Topic', value: cfg.topics.occupancy || '' });
34246
34224
  out.push({ group: gid, key: `sensor.${cfg.id}.topic.batteryLevel`, title: 'Battery Level Topic (0..100)', value: cfg.topics.batteryLevel || '' }, { group: gid, key: `sensor.${cfg.id}.topic.lowBattery`, title: 'Low Battery Topic (bool)', value: cfg.topics.lowBattery || '' }, { group: gid, key: `sensor.${cfg.id}.topic.tamper`, title: 'Tamper Topic', value: cfg.topics.tamper || '' }, { group: gid, key: `sensor.${cfg.id}.topic.online`, title: 'Online Topic', value: cfg.topics.online || '' }, { group: gid, key: `sensor.${cfg.id}.remove`, title: 'Remove sensor', type: 'boolean' });
34247
34225
  }
34248
34226
  return out;
34249
34227
  }
34250
34228
  async putSetting(key, value) {
34251
- // salva sempre nella storage la value del campo (così resta in UI)
34252
34229
  this.storage.setItem(key, String(value));
34253
- // --- Add Sensor workflow ---
34254
34230
  if (key === 'new.create' && String(value) === 'true') {
34255
34231
  const id = (this.storage.getItem('new.id') || '').trim();
34256
34232
  const name = (this.storage.getItem('new.name') || '').trim() || id;
@@ -34265,16 +34241,14 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34265
34241
  }
34266
34242
  this.sensorsCfg.push({ id, name, kind, topics: {} });
34267
34243
  this.saveSensorsToStorage();
34268
- // pulisci i campi "new.*"
34269
34244
  this.storage.removeItem('new.id');
34270
34245
  this.storage.removeItem('new.name');
34271
34246
  this.storage.removeItem('new.kind');
34272
34247
  this.storage.removeItem('new.create');
34273
- await this.discoverSensors();
34248
+ this.safeDiscoverSensors(true);
34274
34249
  await this.connectMqtt(true);
34275
34250
  return;
34276
34251
  }
34277
- // --- Edit/Remove sensore esistente ---
34278
34252
  const m = key.match(/^sensor\.([^\.]+)\.(.+)$/);
34279
34253
  if (m) {
34280
34254
  const sid = m[1];
@@ -34285,40 +34259,33 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34285
34259
  return;
34286
34260
  }
34287
34261
  if (prop === 'remove' && String(value) === 'true') {
34288
- // elimina
34289
34262
  this.sensorsCfg = this.sensorsCfg.filter(s => s.id !== sid);
34290
34263
  this.saveSensorsToStorage();
34291
34264
  try {
34292
- this.devices.delete(`sensor:${sid}`);
34293
- deviceManager.onDeviceRemoved?.(`sensor:${sid}`);
34265
+ sdk?.deviceManager?.onDeviceRemoved?.(`sensor:${sid}`);
34294
34266
  }
34295
34267
  catch { }
34296
- // pulisci flag
34297
34268
  this.storage.removeItem(key);
34298
- await this.discoverSensors();
34269
+ this.safeDiscoverSensors(true);
34299
34270
  await this.connectMqtt(true);
34300
34271
  return;
34301
34272
  }
34302
- if (prop === 'name') {
34273
+ if (prop === 'name')
34303
34274
  cfg.name = String(value);
34304
- }
34305
- else if (prop === 'kind') {
34275
+ else if (prop === 'kind')
34306
34276
  cfg.kind = String(value);
34307
- }
34308
34277
  else if (prop.startsWith('topic.')) {
34309
34278
  const tk = prop.substring('topic.'.length);
34310
34279
  cfg.topics[tk] = String(value).trim();
34311
34280
  }
34312
34281
  this.saveSensorsToStorage();
34313
- await this.discoverSensors();
34282
+ this.safeDiscoverSensors(true);
34314
34283
  await this.connectMqtt(true);
34315
34284
  return;
34316
34285
  }
34317
- // --- Altro (MQTT / Alarm settings) ---
34318
34286
  if (key === 'sensorsJson') {
34319
- // non più mostrato, ma se presente da vecchie versioni
34320
34287
  this.loadSensorsFromStorage();
34321
- await this.discoverSensors();
34288
+ this.safeDiscoverSensors(true);
34322
34289
  await this.connectMqtt(true);
34323
34290
  }
34324
34291
  else {
@@ -34326,17 +34293,14 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34326
34293
  }
34327
34294
  }
34328
34295
  /** ---- DeviceProvider ---- */
34329
- async getDevice(nativeId) {
34330
- return this.devices.get(nativeId);
34331
- }
34296
+ async getDevice(nativeId) { return this.devices.get(nativeId); }
34332
34297
  async releaseDevice(_id, nativeId) {
34333
34298
  try {
34334
34299
  const dev = this.devices.get(nativeId);
34335
- if (dev) {
34300
+ if (dev)
34336
34301
  this.devices.delete(nativeId);
34337
- }
34338
34302
  try {
34339
- deviceManager.onDeviceRemoved?.(nativeId);
34303
+ sdk?.deviceManager?.onDeviceRemoved?.(nativeId);
34340
34304
  }
34341
34305
  catch { }
34342
34306
  }
@@ -34348,7 +34312,6 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34348
34312
  try {
34349
34313
  const raw = this.storage.getItem('sensorsJson') || '[]';
34350
34314
  const parsed = JSON.parse(raw);
34351
- // sanitize
34352
34315
  this.sensorsCfg = (parsed || []).filter(x => x && x.id && x.name && x.kind && x.topics);
34353
34316
  }
34354
34317
  catch (e) {
@@ -34356,42 +34319,50 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34356
34319
  this.sensorsCfg = [];
34357
34320
  }
34358
34321
  }
34359
- /** ===== discoverSensors: annuncia PRIMA, istanzia DOPO ===== */
34360
- async discoverSensors() {
34361
- // 1) Prepara i manifest (niente istanze qui)
34322
+ /** Annuncia i sensori SOLO se deviceManager è pronto. Niente loop infinito. */
34323
+ safeDiscoverSensors(triggeredByChange = false) {
34324
+ const dmAny = sdk?.deviceManager;
34325
+ if (!dmAny) {
34326
+ if (!this.triedDiscoveryOnce) {
34327
+ this.console.log('Device discovery postponed: deviceManager not ready yet.');
34328
+ this.triedDiscoveryOnce = true;
34329
+ }
34330
+ // Riprovaremo in due casi: a) settaggi cambiati (già chiama safeDiscoverSensors)
34331
+ // b) al primo messaggio MQTT (vedi handler sotto).
34332
+ return;
34333
+ }
34334
+ // Se arriviamo qui, il manager c’è: esegui discover.
34335
+ this.triedDiscoveryOnce = false;
34336
+ this.discoverSensors(dmAny);
34337
+ }
34338
+ /** discoverSensors con deviceManager garantito */
34339
+ discoverSensors(dmAny) {
34340
+ // 1) Manifests
34362
34341
  const manifests = this.sensorsCfg.map(cfg => {
34363
34342
  const nativeId = `sensor:${cfg.id}`;
34364
34343
  const t = cfg.topics || {};
34365
34344
  const interfaces = [ScryptedInterface.Online];
34366
- // Tamper solo se c'è un topic tamper
34367
34345
  if (t.tamper)
34368
34346
  interfaces.push(ScryptedInterface.TamperSensor);
34369
- // Interfaccia primaria
34370
34347
  if (cfg.kind === 'contact')
34371
34348
  interfaces.unshift(ScryptedInterface.EntrySensor);
34372
34349
  else if (cfg.kind === 'motion')
34373
34350
  interfaces.unshift(ScryptedInterface.MotionSensor);
34374
34351
  else
34375
34352
  interfaces.unshift(ScryptedInterface.OccupancySensor);
34376
- // Battery solo se previsto
34377
- if (t.batteryLevel || t.lowBattery) {
34353
+ if (t.batteryLevel || t.lowBattery)
34378
34354
  interfaces.push(ScryptedInterface.Battery);
34379
- }
34380
34355
  return { nativeId, name: cfg.name, type: ScryptedDeviceType.Sensor, interfaces };
34381
34356
  });
34382
34357
  // 2) Annuncio
34383
- const dmAny = deviceManager;
34384
34358
  if (typeof dmAny.onDevicesChanged === 'function') {
34385
34359
  dmAny.onDevicesChanged({ devices: manifests });
34386
- this.console.log('Annunciati (batch):', manifests.map(m => m.nativeId).join(', '));
34387
34360
  }
34388
- else {
34389
- for (const m of manifests) {
34390
- deviceManager.onDeviceDiscovered(m);
34391
- this.console.log('Annunciato:', m.nativeId);
34392
- }
34361
+ else if (typeof dmAny.onDeviceDiscovered === 'function') {
34362
+ for (const m of manifests)
34363
+ dmAny.onDeviceDiscovered(m);
34393
34364
  }
34394
- // 3) Istanzia/aggiorna DOPO l’annuncio
34365
+ // 3) Istanzia/aggiorna
34395
34366
  for (const cfg of this.sensorsCfg) {
34396
34367
  const nativeId = `sensor:${cfg.id}`;
34397
34368
  let dev = this.devices.get(nativeId);
@@ -34407,20 +34378,17 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34407
34378
  else {
34408
34379
  dev.cfg = cfg;
34409
34380
  }
34410
- // Default “OK” se abbiamo Battery ma nessun valore ancora ricevuto
34411
34381
  const hasBattery = !!(cfg.topics.batteryLevel || cfg.topics.lowBattery);
34412
- if (hasBattery && dev.batteryLevel === undefined) {
34382
+ if (hasBattery && dev.batteryLevel === undefined)
34413
34383
  dev.batteryLevel = 100;
34414
- }
34415
34384
  }
34416
- // 4) Rimuovi quelli spariti
34385
+ // 4) Cleanup
34417
34386
  const announced = new Set(manifests.map(m => m.nativeId));
34418
34387
  for (const [nativeId] of this.devices) {
34419
34388
  if (!announced.has(nativeId)) {
34420
34389
  try {
34421
34390
  this.devices.delete(nativeId);
34422
- deviceManager.onDeviceRemoved?.(nativeId);
34423
- this.console.log('Rimosso:', nativeId);
34391
+ dmAny.onDeviceRemoved?.(nativeId);
34424
34392
  }
34425
34393
  catch { }
34426
34394
  }
@@ -34434,13 +34402,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34434
34402
  const clientId = this.storage.getItem('clientId') || 'scrypted-paradox';
34435
34403
  const tls = this.storage.getItem('tls') === 'true';
34436
34404
  const rejectUnauthorized = this.storage.getItem('rejectUnauthorized') !== 'false';
34437
- const opts = {
34438
- clientId,
34439
- username,
34440
- password,
34441
- clean: true,
34442
- reconnectPeriod: 3000,
34443
- };
34405
+ const opts = { clientId, username, password, clean: true, reconnectPeriod: 3000 };
34444
34406
  if (tls) {
34445
34407
  opts.protocol = 'mqtts';
34446
34408
  opts.rejectUnauthorized = rejectUnauthorized;
@@ -34449,38 +34411,34 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34449
34411
  }
34450
34412
  collectAllSubscriptions() {
34451
34413
  const subs = new Set();
34452
- // alarm
34453
34414
  for (const k of ['topicGetTarget', 'topicGetCurrent', 'topicTamper', 'topicOnline']) {
34454
34415
  const v = this.storage.getItem(k);
34455
34416
  if (v)
34456
34417
  subs.add(v);
34457
34418
  }
34458
- // sensors
34459
34419
  for (const s of this.sensorsCfg) {
34460
34420
  const t = s.topics || {};
34461
34421
  [t.contact, t.motion, t.occupancy, t.batteryLevel, t.lowBattery, t.tamper, t.online]
34462
- .filter(Boolean)
34463
- .forEach(x => subs.add(String(x)));
34422
+ .filter(Boolean).forEach(x => subs.add(String(x)));
34464
34423
  }
34465
34424
  return Array.from(subs);
34466
34425
  }
34467
34426
  async connectMqtt(_reconnect = false) {
34468
34427
  const subs = this.collectAllSubscriptions();
34469
- if (!subs.length && !this.storage.getItem('topicSetTarget')) {
34428
+ if (!subs.length && !this.storage.getItem('topicSetTarget'))
34470
34429
  this.console.warn('Configura almeno un topic nelle impostazioni.');
34471
- }
34472
34430
  if (this.client) {
34473
34431
  try {
34474
34432
  this.client.end(true);
34475
34433
  }
34476
34434
  catch { }
34435
+ ;
34477
34436
  this.client = undefined;
34478
34437
  }
34479
34438
  const { url, opts } = this.getMqttOptions();
34480
34439
  this.console.log(`Connecting MQTT ${url} ...`);
34481
34440
  const client = mqtt_1.default.connect(url, opts);
34482
34441
  this.client = client;
34483
- // cache alarm topics for fast compare
34484
34442
  const tTarget = this.storage.getItem('topicGetTarget') || '';
34485
34443
  const tCurrent = this.storage.getItem('topicGetCurrent') || '';
34486
34444
  const tTamper = this.storage.getItem('topicTamper') || '';
@@ -34488,12 +34446,11 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34488
34446
  client.on('connect', () => {
34489
34447
  this.console.log('MQTT connected');
34490
34448
  this.online = true;
34491
- if (subs.length) {
34492
- client.subscribe(subs, { qos: 0 }, (err) => {
34493
- if (err)
34494
- this.console.error('subscribe error', err);
34495
- });
34496
- }
34449
+ if (subs.length)
34450
+ client.subscribe(subs, { qos: 0 }, (err) => { if (err)
34451
+ this.console.error('subscribe error', err); });
34452
+ // Al primo connect riprova (silenziosamente) ad annunciare i sensori
34453
+ this.safeDiscoverSensors(true);
34497
34454
  });
34498
34455
  client.on('reconnect', () => this.console.log('MQTT reconnecting...'));
34499
34456
  client.on('close', () => { this.console.log('MQTT closed'); this.online = false; });
@@ -34502,7 +34459,6 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34502
34459
  try {
34503
34460
  const p = payload?.toString() ?? '';
34504
34461
  const np = normalize(p);
34505
- // ---- Alarm handling ----
34506
34462
  if (topic === tOnline) {
34507
34463
  if (truthy(np) || np === 'online')
34508
34464
  this.online = true;
@@ -34511,19 +34467,17 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34511
34467
  return;
34512
34468
  }
34513
34469
  if (topic === tTamper) {
34514
- if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np)) {
34470
+ if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np))
34515
34471
  this.tampered = ['cover', 'intrusion'].find(x => x === np) || true;
34516
- }
34517
- else if (falsy(np)) {
34472
+ else if (falsy(np))
34518
34473
  this.tampered = false;
34519
- }
34520
34474
  return;
34521
34475
  }
34522
34476
  if (topic === tCurrent) {
34523
34477
  const mode = payloadToMode(payload);
34524
34478
  const isAlarm = ['alarm', 'triggered'].includes(np);
34525
34479
  const current = this.securitySystemState || { mode: SecuritySystemMode.Disarmed };
34526
- const newState = {
34480
+ this.securitySystemState = {
34527
34481
  mode: mode ?? current.mode,
34528
34482
  supportedModes: current.supportedModes ?? [
34529
34483
  SecuritySystemMode.Disarmed,
@@ -34533,7 +34487,6 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34533
34487
  ],
34534
34488
  triggered: isAlarm || undefined,
34535
34489
  };
34536
- this.securitySystemState = newState;
34537
34490
  return;
34538
34491
  }
34539
34492
  if (topic === tTarget) {
@@ -34541,10 +34494,12 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34541
34494
  this.console.log('Target state reported:', p, '->', this.pendingTarget);
34542
34495
  return;
34543
34496
  }
34544
- // ---- Sensor dispatch ----
34545
- for (const dev of this.devices.values()) {
34497
+ // Dispatch ai sensori
34498
+ // (E prova ad annunciare se non l’abbiamo ancora fatto e ora il manager è pronto)
34499
+ if (this.triedDiscoveryOnce)
34500
+ this.safeDiscoverSensors(true);
34501
+ for (const dev of this.devices.values())
34546
34502
  dev.handleMqtt(topic, payload);
34547
- }
34548
34503
  }
34549
34504
  catch (e) {
34550
34505
  this.console.error('MQTT message handler error', e);
@@ -34569,7 +34524,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34569
34524
  async armSecuritySystem(mode) {
34570
34525
  const payload = this.getOutgoing(mode);
34571
34526
  this.console.log('armSecuritySystem', mode, '->', payload);
34572
- this.pendingTarget = mode; // memorizza target, ma NON cambiare il current
34527
+ this.pendingTarget = mode;
34573
34528
  this.publishSetTarget(payload);
34574
34529
  }
34575
34530
  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.34",
3
+ "version": "1.0.36",
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",