@rfranzoi/scrypted-mqtt-securitysystem 1.0.35 → 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,25 +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,
34048
- // ⚠️ NON destrutturare deviceManager: va letto sempre "al volo" da sdk.deviceManager.
34049
- } = sdk;
34045
+ // Valori runtime dal modulo SDK
34046
+ const { ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, // enum (valori)
34047
+ SecuritySystemMode, // enum (valori)
34048
+ systemManager, } = sdk;
34050
34049
  const mqtt_1 = __importDefault(__webpack_require__(/*! mqtt */ "./node_modules/mqtt/build/index.js"));
34051
34050
  /** utils */
34052
34051
  function truthy(v) {
@@ -34061,21 +34060,16 @@ function falsy(v) {
34061
34060
  const s = v.toString().trim().toLowerCase();
34062
34061
  return s === '0' || s === 'false' || s === 'offline' || s === 'no' || s === 'off';
34063
34062
  }
34064
- function normalize(s) {
34065
- return (s || '').trim().toLowerCase();
34066
- }
34067
- function clamp(n, min, max) {
34068
- return Math.max(min, Math.min(max, n));
34069
- }
34070
- /** SecuritySystem outgoing defaults (PAI-like)
34071
- * 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 */
34072
34066
  const DEFAULT_OUTGOING = {
34073
34067
  [SecuritySystemMode.Disarmed]: 'disarm',
34074
34068
  [SecuritySystemMode.HomeArmed]: 'arm_home',
34075
34069
  [SecuritySystemMode.AwayArmed]: 'arm_away',
34076
34070
  [SecuritySystemMode.NightArmed]: 'arm_night',
34077
34071
  };
34078
- /** Parse incoming payload -> final mode (ignore transition states) */
34072
+ /** Parse incoming payload -> final mode (ignora transitori) */
34079
34073
  function payloadToMode(payload) {
34080
34074
  if (payload == null)
34081
34075
  return;
@@ -34088,7 +34082,6 @@ function payloadToMode(payload) {
34088
34082
  return SecuritySystemMode.AwayArmed;
34089
34083
  if (['arm_night', 'night', 'armed_night', 'sleep', 'arm_sleep', 'armed_sleep'].includes(p))
34090
34084
  return SecuritySystemMode.NightArmed;
34091
- // transitori: non cambiano il mode
34092
34085
  if (['entry_delay', 'exit_delay', 'pending', 'arming', 'disarming'].includes(p))
34093
34086
  return undefined;
34094
34087
  return undefined;
@@ -34098,76 +34091,67 @@ class BaseMqttSensor extends ScryptedDeviceBase {
34098
34091
  super(nativeId);
34099
34092
  this.cfg = cfg;
34100
34093
  }
34101
- /** Called by parent on each MQTT message */
34102
34094
  handleMqtt(topic, payload) {
34103
34095
  const p = payload?.toString() ?? '';
34104
34096
  const np = normalize(p);
34105
- // online
34106
34097
  if (topic === this.cfg.topics.online) {
34107
34098
  if (truthy(np) || np === 'online')
34108
34099
  this.online = true;
34109
34100
  if (falsy(np) || np === 'offline')
34110
34101
  this.online = false;
34111
34102
  }
34112
- // tamper
34113
34103
  if (topic === this.cfg.topics.tamper) {
34114
34104
  if (truthy(np) || ['tamper', 'intrusion', 'cover', 'motion', 'magnetic'].includes(np)) {
34115
34105
  this.tampered = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
34116
34106
  }
34117
- else if (falsy(np)) {
34107
+ else if (falsy(np))
34118
34108
  this.tampered = false;
34119
- }
34120
34109
  }
34121
- // battery
34122
34110
  if (topic === this.cfg.topics.batteryLevel) {
34123
34111
  const n = clamp(parseFloat(p), 0, 100);
34124
34112
  if (isFinite(n))
34125
34113
  this.batteryLevel = n;
34126
34114
  }
34127
34115
  else if (topic === this.cfg.topics.lowBattery && !this.cfg.topics.batteryLevel) {
34128
- // se abbiamo SOLO lowBattery (bool):
34129
34116
  this.batteryLevel = truthy(np) ? 10 : 100;
34130
34117
  }
34131
- // primary handled by subclasses
34132
34118
  this.handlePrimary(topic, np, p);
34133
34119
  }
34134
34120
  }
34135
34121
  class ContactMqttSensor extends BaseMqttSensor {
34136
34122
  handlePrimary(topic, np) {
34137
- if (topic === this.cfg.topics.contact) {
34123
+ if (topic === this.cfg.topics.contact)
34138
34124
  this.entryOpen = truthy(np);
34139
- }
34140
34125
  }
34141
34126
  }
34142
34127
  class MotionMqttSensor extends BaseMqttSensor {
34143
34128
  handlePrimary(topic, np) {
34144
- if (topic === this.cfg.topics.motion) {
34129
+ if (topic === this.cfg.topics.motion)
34145
34130
  this.motionDetected = truthy(np);
34146
- }
34147
34131
  }
34148
34132
  }
34149
34133
  class OccupancyMqttSensor extends BaseMqttSensor {
34150
34134
  handlePrimary(topic, np) {
34151
- if (topic === this.cfg.topics.occupancy) {
34135
+ if (topic === this.cfg.topics.occupancy)
34152
34136
  this.occupied = truthy(np);
34153
- }
34154
34137
  }
34155
34138
  }
34156
34139
  /** ----------------- Main Plugin ----------------- */
34157
34140
  class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34158
34141
  constructor() {
34159
34142
  super();
34160
- // sensor management
34161
34143
  this.sensorsCfg = [];
34162
34144
  this.devices = new Map();
34163
- // (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)
34164
34148
  setTimeout(() => {
34165
34149
  try {
34166
34150
  systemManager.getDeviceById(this.id)?.setType?.(ScryptedDeviceType.SecuritySystem);
34167
34151
  }
34168
34152
  catch { }
34169
34153
  });
34170
- // Default state
34154
+ // Stato di default
34171
34155
  this.securitySystemState = this.securitySystemState || {
34172
34156
  mode: SecuritySystemMode.Disarmed,
34173
34157
  supportedModes: [
@@ -34178,12 +34162,12 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34178
34162
  ],
34179
34163
  };
34180
34164
  this.online = this.online ?? false;
34181
- // Load sensors config and announce devices
34165
+ // Config sensori e (tentativo) announce
34182
34166
  this.loadSensorsFromStorage();
34183
- this.discoverSensors().catch((e) => this.console.error('discoverSensors error', e));
34184
- // Connect on start
34167
+ this.safeDiscoverSensors(); // non spamma se deviceManager non c'è
34168
+ // Connect MQTT
34185
34169
  this.connectMqtt().catch((e) => this.console.error('MQTT connect error:', e));
34186
- // chiusura pulita del client MQTT ai reload/stop del plugin
34170
+ // Shutdown pulito
34187
34171
  try {
34188
34172
  process.once('SIGTERM', () => { try {
34189
34173
  this.client?.end(true);
@@ -34200,7 +34184,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34200
34184
  }
34201
34185
  catch { }
34202
34186
  }
34203
- // helpers persistenza
34187
+ /** ---- Settings ---- */
34204
34188
  saveSensorsToStorage() {
34205
34189
  try {
34206
34190
  this.storage.setItem('sensorsJson', JSON.stringify(this.sensorsCfg));
@@ -34209,17 +34193,14 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34209
34193
  this.console.error('saveSensorsToStorage error', e);
34210
34194
  }
34211
34195
  }
34212
- /** ---- Settings UI ---- */
34213
34196
  async getSettings() {
34214
34197
  const out = [
34215
- // MQTT Core
34216
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' },
34217
34199
  { group: 'MQTT', key: 'username', title: 'Username', type: 'string', value: this.storage.getItem('username') || '' },
34218
34200
  { group: 'MQTT', key: 'password', title: 'Password', type: 'password', value: this.storage.getItem('password') || '' },
34219
34201
  { group: 'MQTT', key: 'clientId', title: 'Client ID', placeholder: 'scrypted-paradox', value: this.storage.getItem('clientId') || 'scrypted-paradox' },
34220
34202
  { group: 'MQTT', key: 'tls', title: 'Use TLS', type: 'boolean', value: this.storage.getItem('tls') === 'true' },
34221
34203
  { group: 'MQTT', key: 'rejectUnauthorized', title: 'Reject Unauthorized (TLS)', type: 'boolean', value: this.storage.getItem('rejectUnauthorized') !== 'false', description: 'Disattiva solo con broker self-signed.' },
34222
- // Alarm Topics
34223
34204
  { group: 'Alarm Topics', key: 'topicSetTarget', title: 'Set Target State (publish)', placeholder: 'paradox/control/partitions/Area_1', value: this.storage.getItem('topicSetTarget') || '' },
34224
34205
  { group: 'Alarm Topics', key: 'topicGetTarget', title: 'Get Target State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/target_state', value: this.storage.getItem('topicGetTarget') || '' },
34225
34206
  { group: 'Alarm Topics', key: 'topicGetCurrent', title: 'Get Current State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/current_state', value: this.storage.getItem('topicGetCurrent') || '' },
@@ -34228,31 +34209,24 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34228
34209
  { group: 'Publish Options', key: 'qos', title: 'QoS', type: 'integer', value: parseInt(this.storage.getItem('qos') || '0') },
34229
34210
  { group: 'Publish Options', key: 'retain', title: 'Retain', type: 'boolean', value: this.storage.getItem('retain') === 'true' },
34230
34211
  ];
34231
- // ---- UI Add Sensor ----
34232
- 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.' });
34233
- // ---- 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
34234
34215
  for (const cfg of this.sensorsCfg) {
34235
34216
  const gid = `Sensor: ${cfg.name} [${cfg.id}]`;
34236
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'] });
34237
- // primary per tipo
34238
- if (cfg.kind === 'contact') {
34239
- out.push({ group: gid, key: `sensor.${cfg.id}.topic.contact`, title: 'Contact State Topic', value: cfg.topics.contact || '', placeholder: 'paradox/states/zones/XYZ/open' });
34240
- }
34241
- else if (cfg.kind === 'motion') {
34242
- out.push({ group: gid, key: `sensor.${cfg.id}.topic.motion`, title: 'Motion Detected Topic', value: cfg.topics.motion || '', placeholder: 'paradox/states/zones/XYZ/open' });
34243
- }
34244
- else {
34245
- out.push({ group: gid, key: `sensor.${cfg.id}.topic.occupancy`, title: 'Occupancy Detected Topic', value: cfg.topics.occupancy || '', placeholder: 'paradox/states/zones/XYZ/open' });
34246
- }
34247
- // 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 || '' });
34248
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' });
34249
34225
  }
34250
34226
  return out;
34251
34227
  }
34252
34228
  async putSetting(key, value) {
34253
- // salva sempre nella storage la value del campo (così resta in UI)
34254
34229
  this.storage.setItem(key, String(value));
34255
- // --- Add Sensor workflow ---
34256
34230
  if (key === 'new.create' && String(value) === 'true') {
34257
34231
  const id = (this.storage.getItem('new.id') || '').trim();
34258
34232
  const name = (this.storage.getItem('new.name') || '').trim() || id;
@@ -34267,16 +34241,14 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34267
34241
  }
34268
34242
  this.sensorsCfg.push({ id, name, kind, topics: {} });
34269
34243
  this.saveSensorsToStorage();
34270
- // pulisci i campi "new.*"
34271
34244
  this.storage.removeItem('new.id');
34272
34245
  this.storage.removeItem('new.name');
34273
34246
  this.storage.removeItem('new.kind');
34274
34247
  this.storage.removeItem('new.create');
34275
- await this.discoverSensors();
34248
+ this.safeDiscoverSensors(true);
34276
34249
  await this.connectMqtt(true);
34277
34250
  return;
34278
34251
  }
34279
- // --- Edit/Remove sensore esistente ---
34280
34252
  const m = key.match(/^sensor\.([^\.]+)\.(.+)$/);
34281
34253
  if (m) {
34282
34254
  const sid = m[1];
@@ -34287,40 +34259,33 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34287
34259
  return;
34288
34260
  }
34289
34261
  if (prop === 'remove' && String(value) === 'true') {
34290
- // elimina
34291
34262
  this.sensorsCfg = this.sensorsCfg.filter(s => s.id !== sid);
34292
34263
  this.saveSensorsToStorage();
34293
34264
  try {
34294
- this.devices.delete(`sensor:${sid}`);
34295
34265
  sdk?.deviceManager?.onDeviceRemoved?.(`sensor:${sid}`);
34296
34266
  }
34297
34267
  catch { }
34298
- // pulisci flag
34299
34268
  this.storage.removeItem(key);
34300
- await this.discoverSensors();
34269
+ this.safeDiscoverSensors(true);
34301
34270
  await this.connectMqtt(true);
34302
34271
  return;
34303
34272
  }
34304
- if (prop === 'name') {
34273
+ if (prop === 'name')
34305
34274
  cfg.name = String(value);
34306
- }
34307
- else if (prop === 'kind') {
34275
+ else if (prop === 'kind')
34308
34276
  cfg.kind = String(value);
34309
- }
34310
34277
  else if (prop.startsWith('topic.')) {
34311
34278
  const tk = prop.substring('topic.'.length);
34312
34279
  cfg.topics[tk] = String(value).trim();
34313
34280
  }
34314
34281
  this.saveSensorsToStorage();
34315
- await this.discoverSensors();
34282
+ this.safeDiscoverSensors(true);
34316
34283
  await this.connectMqtt(true);
34317
34284
  return;
34318
34285
  }
34319
- // --- Altro (MQTT / Alarm settings) ---
34320
34286
  if (key === 'sensorsJson') {
34321
- // non più mostrato, ma se presente da vecchie versioni
34322
34287
  this.loadSensorsFromStorage();
34323
- await this.discoverSensors();
34288
+ this.safeDiscoverSensors(true);
34324
34289
  await this.connectMqtt(true);
34325
34290
  }
34326
34291
  else {
@@ -34328,15 +34293,12 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34328
34293
  }
34329
34294
  }
34330
34295
  /** ---- DeviceProvider ---- */
34331
- async getDevice(nativeId) {
34332
- return this.devices.get(nativeId);
34333
- }
34296
+ async getDevice(nativeId) { return this.devices.get(nativeId); }
34334
34297
  async releaseDevice(_id, nativeId) {
34335
34298
  try {
34336
34299
  const dev = this.devices.get(nativeId);
34337
- if (dev) {
34300
+ if (dev)
34338
34301
  this.devices.delete(nativeId);
34339
- }
34340
34302
  try {
34341
34303
  sdk?.deviceManager?.onDeviceRemoved?.(nativeId);
34342
34304
  }
@@ -34350,7 +34312,6 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34350
34312
  try {
34351
34313
  const raw = this.storage.getItem('sensorsJson') || '[]';
34352
34314
  const parsed = JSON.parse(raw);
34353
- // sanitize
34354
34315
  this.sensorsCfg = (parsed || []).filter(x => x && x.id && x.name && x.kind && x.topics);
34355
34316
  }
34356
34317
  catch (e) {
@@ -34358,15 +34319,25 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34358
34319
  this.sensorsCfg = [];
34359
34320
  }
34360
34321
  }
34361
- /** ===== discoverSensors: annuncia PRIMA, istanzia DOPO (con retry se manager non pronto) ===== */
34362
- async discoverSensors() {
34322
+ /** Annuncia i sensori SOLO se deviceManager è pronto. Niente loop infinito. */
34323
+ safeDiscoverSensors(triggeredByChange = false) {
34363
34324
  const dmAny = sdk?.deviceManager;
34364
34325
  if (!dmAny) {
34365
- this.console.warn('deviceManager not ready yet, retrying in 1s…');
34366
- setTimeout(() => this.discoverSensors().catch(e => this.console.error('discoverSensors retry error', e)), 1000);
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).
34367
34332
  return;
34368
34333
  }
34369
- // 1) Prepara i manifest
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
34370
34341
  const manifests = this.sensorsCfg.map(cfg => {
34371
34342
  const nativeId = `sensor:${cfg.id}`;
34372
34343
  const t = cfg.topics || {};
@@ -34386,18 +34357,10 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34386
34357
  // 2) Annuncio
34387
34358
  if (typeof dmAny.onDevicesChanged === 'function') {
34388
34359
  dmAny.onDevicesChanged({ devices: manifests });
34389
- this.console.log('Annunciati (batch):', manifests.map(m => m.nativeId).join(', '));
34390
34360
  }
34391
34361
  else if (typeof dmAny.onDeviceDiscovered === 'function') {
34392
- for (const m of manifests) {
34362
+ for (const m of manifests)
34393
34363
  dmAny.onDeviceDiscovered(m);
34394
- this.console.log('Annunciato:', m.nativeId);
34395
- }
34396
- }
34397
- else {
34398
- this.console.warn('deviceManager has no discovery methods yet, retrying in 1s…');
34399
- setTimeout(() => this.discoverSensors().catch(e => this.console.error('discoverSensors retry error', e)), 1000);
34400
- return;
34401
34364
  }
34402
34365
  // 3) Istanzia/aggiorna
34403
34366
  for (const cfg of this.sensorsCfg) {
@@ -34416,18 +34379,16 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34416
34379
  dev.cfg = cfg;
34417
34380
  }
34418
34381
  const hasBattery = !!(cfg.topics.batteryLevel || cfg.topics.lowBattery);
34419
- if (hasBattery && dev.batteryLevel === undefined) {
34382
+ if (hasBattery && dev.batteryLevel === undefined)
34420
34383
  dev.batteryLevel = 100;
34421
- }
34422
34384
  }
34423
- // 4) Rimuovi quelli spariti
34385
+ // 4) Cleanup
34424
34386
  const announced = new Set(manifests.map(m => m.nativeId));
34425
34387
  for (const [nativeId] of this.devices) {
34426
34388
  if (!announced.has(nativeId)) {
34427
34389
  try {
34428
34390
  this.devices.delete(nativeId);
34429
- sdk?.deviceManager?.onDeviceRemoved?.(nativeId);
34430
- this.console.log('Rimosso:', nativeId);
34391
+ dmAny.onDeviceRemoved?.(nativeId);
34431
34392
  }
34432
34393
  catch { }
34433
34394
  }
@@ -34441,13 +34402,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34441
34402
  const clientId = this.storage.getItem('clientId') || 'scrypted-paradox';
34442
34403
  const tls = this.storage.getItem('tls') === 'true';
34443
34404
  const rejectUnauthorized = this.storage.getItem('rejectUnauthorized') !== 'false';
34444
- const opts = {
34445
- clientId,
34446
- username,
34447
- password,
34448
- clean: true,
34449
- reconnectPeriod: 3000,
34450
- };
34405
+ const opts = { clientId, username, password, clean: true, reconnectPeriod: 3000 };
34451
34406
  if (tls) {
34452
34407
  opts.protocol = 'mqtts';
34453
34408
  opts.rejectUnauthorized = rejectUnauthorized;
@@ -34456,38 +34411,34 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34456
34411
  }
34457
34412
  collectAllSubscriptions() {
34458
34413
  const subs = new Set();
34459
- // alarm
34460
34414
  for (const k of ['topicGetTarget', 'topicGetCurrent', 'topicTamper', 'topicOnline']) {
34461
34415
  const v = this.storage.getItem(k);
34462
34416
  if (v)
34463
34417
  subs.add(v);
34464
34418
  }
34465
- // sensors
34466
34419
  for (const s of this.sensorsCfg) {
34467
34420
  const t = s.topics || {};
34468
34421
  [t.contact, t.motion, t.occupancy, t.batteryLevel, t.lowBattery, t.tamper, t.online]
34469
- .filter(Boolean)
34470
- .forEach(x => subs.add(String(x)));
34422
+ .filter(Boolean).forEach(x => subs.add(String(x)));
34471
34423
  }
34472
34424
  return Array.from(subs);
34473
34425
  }
34474
34426
  async connectMqtt(_reconnect = false) {
34475
34427
  const subs = this.collectAllSubscriptions();
34476
- if (!subs.length && !this.storage.getItem('topicSetTarget')) {
34428
+ if (!subs.length && !this.storage.getItem('topicSetTarget'))
34477
34429
  this.console.warn('Configura almeno un topic nelle impostazioni.');
34478
- }
34479
34430
  if (this.client) {
34480
34431
  try {
34481
34432
  this.client.end(true);
34482
34433
  }
34483
34434
  catch { }
34435
+ ;
34484
34436
  this.client = undefined;
34485
34437
  }
34486
34438
  const { url, opts } = this.getMqttOptions();
34487
34439
  this.console.log(`Connecting MQTT ${url} ...`);
34488
34440
  const client = mqtt_1.default.connect(url, opts);
34489
34441
  this.client = client;
34490
- // cache alarm topics for fast compare
34491
34442
  const tTarget = this.storage.getItem('topicGetTarget') || '';
34492
34443
  const tCurrent = this.storage.getItem('topicGetCurrent') || '';
34493
34444
  const tTamper = this.storage.getItem('topicTamper') || '';
@@ -34495,12 +34446,11 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34495
34446
  client.on('connect', () => {
34496
34447
  this.console.log('MQTT connected');
34497
34448
  this.online = true;
34498
- if (subs.length) {
34499
- client.subscribe(subs, { qos: 0 }, (err) => {
34500
- if (err)
34501
- this.console.error('subscribe error', err);
34502
- });
34503
- }
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);
34504
34454
  });
34505
34455
  client.on('reconnect', () => this.console.log('MQTT reconnecting...'));
34506
34456
  client.on('close', () => { this.console.log('MQTT closed'); this.online = false; });
@@ -34509,7 +34459,6 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34509
34459
  try {
34510
34460
  const p = payload?.toString() ?? '';
34511
34461
  const np = normalize(p);
34512
- // ---- Alarm handling ----
34513
34462
  if (topic === tOnline) {
34514
34463
  if (truthy(np) || np === 'online')
34515
34464
  this.online = true;
@@ -34518,19 +34467,17 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34518
34467
  return;
34519
34468
  }
34520
34469
  if (topic === tTamper) {
34521
- if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np)) {
34470
+ if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np))
34522
34471
  this.tampered = ['cover', 'intrusion'].find(x => x === np) || true;
34523
- }
34524
- else if (falsy(np)) {
34472
+ else if (falsy(np))
34525
34473
  this.tampered = false;
34526
- }
34527
34474
  return;
34528
34475
  }
34529
34476
  if (topic === tCurrent) {
34530
34477
  const mode = payloadToMode(payload);
34531
34478
  const isAlarm = ['alarm', 'triggered'].includes(np);
34532
34479
  const current = this.securitySystemState || { mode: SecuritySystemMode.Disarmed };
34533
- const newState = {
34480
+ this.securitySystemState = {
34534
34481
  mode: mode ?? current.mode,
34535
34482
  supportedModes: current.supportedModes ?? [
34536
34483
  SecuritySystemMode.Disarmed,
@@ -34540,7 +34487,6 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34540
34487
  ],
34541
34488
  triggered: isAlarm || undefined,
34542
34489
  };
34543
- this.securitySystemState = newState;
34544
34490
  return;
34545
34491
  }
34546
34492
  if (topic === tTarget) {
@@ -34548,10 +34494,12 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34548
34494
  this.console.log('Target state reported:', p, '->', this.pendingTarget);
34549
34495
  return;
34550
34496
  }
34551
- // ---- Sensor dispatch ----
34552
- 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())
34553
34502
  dev.handleMqtt(topic, payload);
34554
- }
34555
34503
  }
34556
34504
  catch (e) {
34557
34505
  this.console.error('MQTT message handler error', e);
@@ -34576,7 +34524,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34576
34524
  async armSecuritySystem(mode) {
34577
34525
  const payload = this.getOutgoing(mode);
34578
34526
  this.console.log('armSecuritySystem', mode, '->', payload);
34579
- this.pendingTarget = mode; // memorizza target, ma NON cambiare il current
34527
+ this.pendingTarget = mode;
34580
34528
  this.publishSetTarget(payload);
34581
34529
  }
34582
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.35",
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",