@rfranzoi/scrypted-mqtt-securitysystem 1.0.40 → 1.0.42

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.
@@ -34024,28 +34024,46 @@ function socketOnError() {
34024
34024
 
34025
34025
  "use strict";
34026
34026
 
34027
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
34028
+ if (k2 === undefined) k2 = k;
34029
+ var desc = Object.getOwnPropertyDescriptor(m, k);
34030
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
34031
+ desc = { enumerable: true, get: function() { return m[k]; } };
34032
+ }
34033
+ Object.defineProperty(o, k2, desc);
34034
+ }) : (function(o, m, k, k2) {
34035
+ if (k2 === undefined) k2 = k;
34036
+ o[k2] = m[k];
34037
+ }));
34038
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
34039
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
34040
+ }) : function(o, v) {
34041
+ o["default"] = v;
34042
+ });
34043
+ var __importStar = (this && this.__importStar) || (function () {
34044
+ var ownKeys = function(o) {
34045
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34046
+ var ar = [];
34047
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34048
+ return ar;
34049
+ };
34050
+ return ownKeys(o);
34051
+ };
34052
+ return function (mod) {
34053
+ if (mod && mod.__esModule) return mod;
34054
+ var result = {};
34055
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34056
+ __setModuleDefault(result, mod);
34057
+ return result;
34058
+ };
34059
+ })();
34027
34060
  var __importDefault = (this && this.__importDefault) || function (mod) {
34028
34061
  return (mod && mod.__esModule) ? mod : { "default": mod };
34029
34062
  };
34030
34063
  Object.defineProperty(exports, "__esModule", ({ value: true }));
34031
- // --- Preload: silenzia SOLO il warning facoltativo di sdk.json ---
34032
- (() => {
34033
- const swallow = (orig) => (...args) => {
34034
- const txt = args.map(a => typeof a === 'string' ? a : (a?.message || '')).join(' ');
34035
- if (txt.includes('failed to load custom interface descriptors'))
34036
- return;
34037
- return orig(...args);
34038
- };
34039
- console.error = swallow(console.error.bind(console));
34040
- console.warn = swallow(console.warn.bind(console));
34041
- })();
34042
- // Carica lo SDK in runtime (evita ESM per garantire l’ordine col preload)
34043
- const sdk = __webpack_require__(/*! @scrypted/sdk */ "./node_modules/@scrypted/sdk/dist/src/index.js");
34044
- // Valori runtime (enum, classi, manager)
34045
- const { ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, // enum: valori
34046
- SecuritySystemMode, // enum: valori
34047
- systemManager, } = sdk;
34064
+ const sdk_1 = __importStar(__webpack_require__(/*! @scrypted/sdk */ "./node_modules/@scrypted/sdk/dist/src/index.js"));
34048
34065
  const mqtt_1 = __importDefault(__webpack_require__(/*! mqtt */ "./node_modules/mqtt/build/index.js"));
34066
+ const { systemManager, deviceManager } = sdk_1.default;
34049
34067
  /** utils */
34050
34068
  function truthy(v) {
34051
34069
  if (!v)
@@ -34059,113 +34077,140 @@ function falsy(v) {
34059
34077
  const s = v.toString().trim().toLowerCase();
34060
34078
  return s === '0' || s === 'false' || s === 'offline' || s === 'no' || s === 'off';
34061
34079
  }
34062
- function normalize(s) { return (s || '').trim().toLowerCase(); }
34063
- function clamp(n, min, max) { return Math.max(min, Math.min(max, n)); }
34064
- /** Outgoing predefiniti (PAI-like). Chiavi numeriche per compatibilità enum */
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
+ }
34086
+ /** SecuritySystem outgoing defaults (PAI-like) */
34065
34087
  const DEFAULT_OUTGOING = {
34066
- [SecuritySystemMode.Disarmed]: 'disarm',
34067
- [SecuritySystemMode.HomeArmed]: 'arm_home',
34068
- [SecuritySystemMode.AwayArmed]: 'arm_away',
34069
- [SecuritySystemMode.NightArmed]: 'arm_night',
34088
+ [sdk_1.SecuritySystemMode.Disarmed]: 'disarm',
34089
+ [sdk_1.SecuritySystemMode.HomeArmed]: 'arm_home',
34090
+ [sdk_1.SecuritySystemMode.AwayArmed]: 'arm_away',
34091
+ [sdk_1.SecuritySystemMode.NightArmed]: 'arm_night',
34070
34092
  };
34071
- /** Parse incoming payload -> final mode (ignora transitori) */
34093
+ /** Parse incoming payload -> final mode (ignore transition states) */
34072
34094
  function payloadToMode(payload) {
34073
34095
  if (payload == null)
34074
34096
  return;
34075
34097
  const p = normalize(payload.toString());
34076
34098
  if (['disarm', 'disarmed', 'off', '0', 'idle', 'ready'].includes(p))
34077
- return SecuritySystemMode.Disarmed;
34099
+ return sdk_1.SecuritySystemMode.Disarmed;
34078
34100
  if (['arm_home', 'home', 'stay', 'armed_home'].includes(p))
34079
- return SecuritySystemMode.HomeArmed;
34101
+ return sdk_1.SecuritySystemMode.HomeArmed;
34080
34102
  if (['arm_away', 'away', 'armed_away', 'away_armed'].includes(p))
34081
- return SecuritySystemMode.AwayArmed;
34103
+ return sdk_1.SecuritySystemMode.AwayArmed;
34082
34104
  if (['arm_night', 'night', 'armed_night', 'sleep', 'arm_sleep', 'armed_sleep'].includes(p))
34083
- return SecuritySystemMode.NightArmed;
34105
+ return sdk_1.SecuritySystemMode.NightArmed;
34106
+ // transitori: non cambiano il mode
34084
34107
  if (['entry_delay', 'exit_delay', 'pending', 'arming', 'disarming'].includes(p))
34085
34108
  return undefined;
34086
34109
  return undefined;
34087
34110
  }
34088
- class BaseMqttSensor extends ScryptedDeviceBase {
34111
+ class BaseMqttSensor extends sdk_1.ScryptedDeviceBase {
34089
34112
  constructor(nativeId, cfg) {
34090
34113
  super(nativeId);
34091
34114
  this.cfg = cfg;
34115
+ // non impostare stati qui: l'annuncio device deve avvenire prima
34092
34116
  }
34117
+ /** Called by parent on each MQTT message */
34093
34118
  handleMqtt(topic, payload) {
34094
34119
  const p = payload?.toString() ?? '';
34095
34120
  const np = normalize(p);
34121
+ // online
34096
34122
  if (topic === this.cfg.topics.online) {
34097
34123
  if (truthy(np) || np === 'online')
34098
34124
  this.online = true;
34099
34125
  if (falsy(np) || np === 'offline')
34100
34126
  this.online = false;
34101
34127
  }
34128
+ // tamper
34102
34129
  if (topic === this.cfg.topics.tamper) {
34103
34130
  if (truthy(np) || ['tamper', 'intrusion', 'cover', 'motion', 'magnetic'].includes(np)) {
34104
34131
  this.tampered = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
34105
34132
  }
34106
- else if (falsy(np))
34133
+ else if (falsy(np)) {
34107
34134
  this.tampered = false;
34135
+ }
34108
34136
  }
34137
+ // battery
34109
34138
  if (topic === this.cfg.topics.batteryLevel) {
34110
34139
  const n = clamp(parseFloat(p), 0, 100);
34111
34140
  if (isFinite(n))
34112
34141
  this.batteryLevel = n;
34113
34142
  }
34114
34143
  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)
34115
34147
  this.batteryLevel = truthy(np) ? 10 : 100;
34116
34148
  }
34149
+ // primary handled by subclasses
34117
34150
  this.handlePrimary(topic, np, p);
34118
34151
  }
34119
34152
  }
34120
34153
  class ContactMqttSensor extends BaseMqttSensor {
34121
- handlePrimary(topic, np) {
34122
- if (topic === this.cfg.topics.contact)
34154
+ constructor(nativeId, cfg) {
34155
+ super(nativeId, cfg);
34156
+ }
34157
+ handlePrimary(topic, np, _raw) {
34158
+ if (topic === this.cfg.topics.contact) {
34123
34159
  this.entryOpen = truthy(np);
34160
+ }
34124
34161
  }
34125
34162
  }
34126
34163
  class MotionMqttSensor extends BaseMqttSensor {
34127
- handlePrimary(topic, np) {
34128
- if (topic === this.cfg.topics.motion)
34164
+ constructor(nativeId, cfg) {
34165
+ super(nativeId, cfg);
34166
+ }
34167
+ handlePrimary(topic, np, _raw) {
34168
+ if (topic === this.cfg.topics.motion) {
34129
34169
  this.motionDetected = truthy(np);
34170
+ }
34130
34171
  }
34131
34172
  }
34132
34173
  class OccupancyMqttSensor extends BaseMqttSensor {
34133
- handlePrimary(topic, np) {
34134
- if (topic === this.cfg.topics.occupancy)
34174
+ constructor(nativeId, cfg) {
34175
+ super(nativeId, cfg);
34176
+ }
34177
+ handlePrimary(topic, np, _raw) {
34178
+ if (topic === this.cfg.topics.occupancy) {
34135
34179
  this.occupied = truthy(np);
34180
+ }
34136
34181
  }
34137
34182
  }
34138
34183
  /** ----------------- Main Plugin ----------------- */
34139
- class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34184
+ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34140
34185
  constructor() {
34141
34186
  super();
34187
+ // sensor management
34142
34188
  this.sensorsCfg = [];
34143
34189
  this.devices = new Map();
34144
- this.triedDiscoveryOnce = false;
34145
- // Tipo in UI (best-effort)
34190
+ // (facoltativo) Imposta il device type in UI
34146
34191
  setTimeout(() => {
34147
34192
  try {
34148
- systemManager.getDeviceById(this.id)?.setType?.(ScryptedDeviceType.SecuritySystem);
34193
+ systemManager.getDeviceById(this.id)?.setType?.(sdk_1.ScryptedDeviceType.SecuritySystem);
34149
34194
  }
34150
34195
  catch { }
34151
34196
  });
34152
- // Stato di default
34197
+ // Default state
34153
34198
  this.securitySystemState = this.securitySystemState || {
34154
- mode: SecuritySystemMode.Disarmed,
34199
+ mode: sdk_1.SecuritySystemMode.Disarmed,
34155
34200
  supportedModes: [
34156
- SecuritySystemMode.Disarmed,
34157
- SecuritySystemMode.HomeArmed,
34158
- SecuritySystemMode.AwayArmed,
34159
- SecuritySystemMode.NightArmed,
34201
+ sdk_1.SecuritySystemMode.Disarmed,
34202
+ sdk_1.SecuritySystemMode.HomeArmed,
34203
+ sdk_1.SecuritySystemMode.AwayArmed,
34204
+ sdk_1.SecuritySystemMode.NightArmed,
34160
34205
  ],
34161
34206
  };
34162
34207
  this.online = this.online ?? false;
34163
- // Config sensori e announce
34208
+ // Load sensors config and announce devices
34164
34209
  this.loadSensorsFromStorage();
34165
- this.safeDiscoverSensors(); // non spamma se deviceManager non c'è
34166
- // Connect MQTT
34167
- this.connectMqtt().catch((e) => this.console.error('MQTT connect error:', e));
34168
- // Shutdown pulito
34210
+ this.discoverSensors().catch(e => this.console.error('discoverSensors error', e));
34211
+ // Connect on start
34212
+ this.connectMqtt().catch(e => this.console.error('MQTT connect error:', e));
34213
+ // chiusura pulita del client MQTT ai reload/stop del plugin
34169
34214
  try {
34170
34215
  process.once('SIGTERM', () => { try {
34171
34216
  this.client?.end(true);
@@ -34182,7 +34227,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34182
34227
  }
34183
34228
  catch { }
34184
34229
  }
34185
- /** ---- Settings ---- */
34230
+ // helpers persistenza
34186
34231
  saveSensorsToStorage() {
34187
34232
  try {
34188
34233
  this.storage.setItem('sensorsJson', JSON.stringify(this.sensorsCfg));
@@ -34191,14 +34236,17 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34191
34236
  this.console.error('saveSensorsToStorage error', e);
34192
34237
  }
34193
34238
  }
34239
+ /** ---- Settings UI ---- */
34194
34240
  async getSettings() {
34195
34241
  const out = [
34242
+ // MQTT Core
34196
34243
  { 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' },
34197
34244
  { group: 'MQTT', key: 'username', title: 'Username', type: 'string', value: this.storage.getItem('username') || '' },
34198
34245
  { group: 'MQTT', key: 'password', title: 'Password', type: 'password', value: this.storage.getItem('password') || '' },
34199
34246
  { group: 'MQTT', key: 'clientId', title: 'Client ID', placeholder: 'scrypted-paradox', value: this.storage.getItem('clientId') || 'scrypted-paradox' },
34200
34247
  { group: 'MQTT', key: 'tls', title: 'Use TLS', type: 'boolean', value: this.storage.getItem('tls') === 'true' },
34201
34248
  { group: 'MQTT', key: 'rejectUnauthorized', title: 'Reject Unauthorized (TLS)', type: 'boolean', value: this.storage.getItem('rejectUnauthorized') !== 'false', description: 'Disattiva solo con broker self-signed.' },
34249
+ // Alarm Topics
34202
34250
  { group: 'Alarm Topics', key: 'topicSetTarget', title: 'Set Target State (publish)', placeholder: 'paradox/control/partitions/Area_1', value: this.storage.getItem('topicSetTarget') || '' },
34203
34251
  { group: 'Alarm Topics', key: 'topicGetTarget', title: 'Get Target State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/target_state', value: this.storage.getItem('topicGetTarget') || '' },
34204
34252
  { group: 'Alarm Topics', key: 'topicGetCurrent', title: 'Get Current State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/current_state', value: this.storage.getItem('topicGetCurrent') || '' },
@@ -34206,69 +34254,32 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34206
34254
  { group: 'Alarm Topics', key: 'topicOnline', title: 'Get Online (subscribe)', placeholder: 'paradox/interface/availability', value: this.storage.getItem('topicOnline') || '' },
34207
34255
  { group: 'Publish Options', key: 'qos', title: 'QoS', type: 'integer', value: parseInt(this.storage.getItem('qos') || '0') },
34208
34256
  { group: 'Publish Options', key: 'retain', title: 'Retain', type: 'boolean', value: this.storage.getItem('retain') === 'true' },
34209
- // ---- Danger Zone ----
34210
- { group: 'Danger Zone', key: 'danger.resetSensors', title: 'Reset all sensors (wipe)', type: 'boolean', description: 'Cancella tutti i sensori salvati e rimuove i device annunciati.' },
34211
- { group: 'Danger Zone', key: 'danger.export', title: 'Export sensors JSON (read-only)', value: JSON.stringify(this.sensorsCfg, null, 2), readonly: true },
34212
- { group: 'Danger Zone', key: 'danger.import', title: 'Import sensors JSON (paste & Save)', type: 'string', placeholder: '[]', description: 'Incolla JSON valido per sovrascrivere l’elenco sensori.' },
34213
34257
  ];
34214
- // ---- Add Sensor
34215
- out.push({ group: 'Add Sensor', key: 'new.id', title: 'New Sensor ID', placeholder: 'porta-sogg', value: this.storage.getItem('new.id') || '' }, { group: 'Add Sensor', key: 'new.name', title: 'Name', placeholder: 'Porta Soggiorno', 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 e Save per creare.' });
34216
- // ---- Sensors esistenti
34258
+ // ---- UI Add Sensor ----
34259
+ 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.' });
34260
+ // ---- UI per sensori esistenti ----
34217
34261
  for (const cfg of this.sensorsCfg) {
34218
34262
  const gid = `Sensor: ${cfg.name} [${cfg.id}]`;
34219
- out.push({ group: gid, key: `sensor.${cfg.id}.id`, title: 'ID (read-only)', value: cfg.id, readonly: true, description: 'L’ID identifica il device. Evita spazi/puntini.' }, { 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'] }, { group: gid, key: `sensor.${cfg.id}.renameTo`, title: 'Clone/Rename → New ID', placeholder: 'es. porta-sogg', description: 'Compila e Save per clonare con nuovo ID e rimuovere il vecchio.' });
34220
- if (cfg.kind === 'contact')
34221
- out.push({ group: gid, key: `sensor.${cfg.id}.topic.contact`, title: 'Contact State Topic', value: cfg.topics.contact || '' });
34222
- else if (cfg.kind === 'motion')
34223
- out.push({ group: gid, key: `sensor.${cfg.id}.topic.motion`, title: 'Motion Detected Topic', value: cfg.topics.motion || '' });
34224
- else
34225
- out.push({ group: gid, key: `sensor.${cfg.id}.topic.occupancy`, title: 'Occupancy Detected Topic', value: cfg.topics.occupancy || '' });
34263
+ 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'] });
34264
+ // primary per tipo
34265
+ if (cfg.kind === 'contact') {
34266
+ out.push({ group: gid, key: `sensor.${cfg.id}.topic.contact`, title: 'Contact State Topic', value: cfg.topics.contact || '', placeholder: 'paradox/states/zones/XYZ/open' });
34267
+ }
34268
+ else if (cfg.kind === 'motion') {
34269
+ out.push({ group: gid, key: `sensor.${cfg.id}.topic.motion`, title: 'Motion Detected Topic', value: cfg.topics.motion || '', placeholder: 'paradox/states/zones/XYZ/open' });
34270
+ }
34271
+ else {
34272
+ out.push({ group: gid, key: `sensor.${cfg.id}.topic.occupancy`, title: 'Occupancy Detected Topic', value: cfg.topics.occupancy || '', placeholder: 'paradox/states/zones/XYZ/open' });
34273
+ }
34274
+ // extra opzionali
34226
34275
  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' });
34227
34276
  }
34228
34277
  return out;
34229
34278
  }
34230
34279
  async putSetting(key, value) {
34280
+ // salva sempre nella storage la value del campo (così resta in UI)
34231
34281
  this.storage.setItem(key, String(value));
34232
- // ---- Danger Zone: reset all ----
34233
- if (key === 'danger.resetSensors' && String(value) === 'true') {
34234
- this.storage.removeItem('danger.resetSensors');
34235
- // rimuovi device annunciati
34236
- try {
34237
- const dm = sdk?.deviceManager;
34238
- for (const nativeId of Array.from(this.devices.keys())) {
34239
- try {
34240
- dm?.onDeviceRemoved?.(nativeId);
34241
- }
34242
- catch { }
34243
- }
34244
- }
34245
- catch { }
34246
- this.devices.clear();
34247
- this.sensorsCfg = [];
34248
- this.saveSensorsToStorage();
34249
- this.safeDiscoverSensors(true);
34250
- await this.connectMqtt(true);
34251
- return;
34252
- }
34253
- // ---- Danger Zone: import JSON ----
34254
- if (key === 'danger.import') {
34255
- try {
34256
- const parsed = JSON.parse(String(value) || '[]');
34257
- if (!Array.isArray(parsed))
34258
- throw new Error('JSON non è un array.');
34259
- // sanifica
34260
- this.sensorsCfg = (parsed || []).filter(x => x && x.id && x.name && x.kind && x.topics);
34261
- this.saveSensorsToStorage();
34262
- this.storage.removeItem('danger.import');
34263
- this.safeDiscoverSensors(true);
34264
- await this.connectMqtt(true);
34265
- }
34266
- catch (e) {
34267
- this.console.error('Import JSON error:', e);
34268
- }
34269
- return;
34270
- }
34271
- // ---- Add Sensor ----
34282
+ // --- Add Sensor workflow ---
34272
34283
  if (key === 'new.create' && String(value) === 'true') {
34273
34284
  const id = (this.storage.getItem('new.id') || '').trim();
34274
34285
  const name = (this.storage.getItem('new.name') || '').trim() || id;
@@ -34283,15 +34294,16 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34283
34294
  }
34284
34295
  this.sensorsCfg.push({ id, name, kind, topics: {} });
34285
34296
  this.saveSensorsToStorage();
34297
+ // pulisci i campi "new.*"
34286
34298
  this.storage.removeItem('new.id');
34287
34299
  this.storage.removeItem('new.name');
34288
34300
  this.storage.removeItem('new.kind');
34289
34301
  this.storage.removeItem('new.create');
34290
- this.safeDiscoverSensors(true);
34302
+ await this.discoverSensors();
34291
34303
  await this.connectMqtt(true);
34292
34304
  return;
34293
34305
  }
34294
- // ---- Edit/Remove/Rename sensore ----
34306
+ // --- Edit/Remove sensore esistente ---
34295
34307
  const m = key.match(/^sensor\.([^\.]+)\.(.+)$/);
34296
34308
  if (m) {
34297
34309
  const sid = m[1];
@@ -34301,60 +34313,41 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34301
34313
  this.console.warn('putSetting: sensor non trovato', sid);
34302
34314
  return;
34303
34315
  }
34304
- // rename/clone
34305
- if (prop === 'renameTo') {
34306
- const newId = String(value).trim();
34307
- this.storage.removeItem(key);
34308
- if (!newId)
34309
- return;
34310
- if (this.sensorsCfg.find(s => s.id === newId)) {
34311
- this.console.warn('renameTo: ID già esistente:', newId);
34312
- return;
34313
- }
34314
- const cloned = { ...cfg, id: newId, name: cfg.name };
34315
- this.sensorsCfg.push(cloned);
34316
- // rimuovi vecchio
34317
- this.sensorsCfg = this.sensorsCfg.filter(s => s !== cfg);
34318
- try {
34319
- sdk?.deviceManager?.onDeviceRemoved?.(`sensor:${sid}`);
34320
- }
34321
- catch { }
34322
- this.saveSensorsToStorage();
34323
- this.safeDiscoverSensors(true);
34324
- await this.connectMqtt(true);
34325
- return;
34326
- }
34327
- // remove
34328
34316
  if (prop === 'remove' && String(value) === 'true') {
34317
+ // elimina
34329
34318
  this.sensorsCfg = this.sensorsCfg.filter(s => s.id !== sid);
34330
34319
  this.saveSensorsToStorage();
34331
34320
  try {
34332
- sdk?.deviceManager?.onDeviceRemoved?.(`sensor:${sid}`);
34321
+ this.devices.delete(`sensor:${sid}`);
34322
+ deviceManager.onDeviceRemoved?.(`sensor:${sid}`);
34333
34323
  }
34334
34324
  catch { }
34325
+ // pulisci flag
34335
34326
  this.storage.removeItem(key);
34336
- this.safeDiscoverSensors(true);
34327
+ await this.discoverSensors();
34337
34328
  await this.connectMqtt(true);
34338
34329
  return;
34339
34330
  }
34340
- // edit
34341
- if (prop === 'name')
34331
+ if (prop === 'name') {
34342
34332
  cfg.name = String(value);
34343
- else if (prop === 'kind')
34333
+ }
34334
+ else if (prop === 'kind') {
34344
34335
  cfg.kind = String(value);
34336
+ }
34345
34337
  else if (prop.startsWith('topic.')) {
34346
34338
  const tk = prop.substring('topic.'.length);
34347
34339
  cfg.topics[tk] = String(value).trim();
34348
34340
  }
34349
34341
  this.saveSensorsToStorage();
34350
- this.safeDiscoverSensors(true);
34342
+ await this.discoverSensors();
34351
34343
  await this.connectMqtt(true);
34352
34344
  return;
34353
34345
  }
34354
- // Altre impostazioni: riconnetti
34346
+ // --- Altro (MQTT / Alarm settings) ---
34355
34347
  if (key === 'sensorsJson') {
34348
+ // non più mostrato, ma se presente da vecchie versioni
34356
34349
  this.loadSensorsFromStorage();
34357
- this.safeDiscoverSensors(true);
34350
+ await this.discoverSensors();
34358
34351
  await this.connectMqtt(true);
34359
34352
  }
34360
34353
  else {
@@ -34362,14 +34355,17 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34362
34355
  }
34363
34356
  }
34364
34357
  /** ---- DeviceProvider ---- */
34365
- async getDevice(nativeId) { return this.devices.get(nativeId); }
34358
+ async getDevice(nativeId) {
34359
+ return this.devices.get(nativeId);
34360
+ }
34366
34361
  async releaseDevice(_id, nativeId) {
34367
34362
  try {
34368
34363
  const dev = this.devices.get(nativeId);
34369
- if (dev)
34364
+ if (dev) {
34370
34365
  this.devices.delete(nativeId);
34366
+ }
34371
34367
  try {
34372
- sdk?.deviceManager?.onDeviceRemoved?.(nativeId);
34368
+ deviceManager.onDeviceRemoved?.(nativeId);
34373
34369
  }
34374
34370
  catch { }
34375
34371
  }
@@ -34381,6 +34377,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34381
34377
  try {
34382
34378
  const raw = this.storage.getItem('sensorsJson') || '[]';
34383
34379
  const parsed = JSON.parse(raw);
34380
+ // sanitize
34384
34381
  this.sensorsCfg = (parsed || []).filter(x => x && x.id && x.name && x.kind && x.topics);
34385
34382
  }
34386
34383
  catch (e) {
@@ -34388,47 +34385,42 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34388
34385
  this.sensorsCfg = [];
34389
34386
  }
34390
34387
  }
34391
- /** Annuncia i sensori SOLO se deviceManager è pronto. */
34392
- safeDiscoverSensors(triggeredByChange = false) {
34393
- const dmAny = sdk?.deviceManager;
34394
- if (!dmAny) {
34395
- if (!this.triedDiscoveryOnce) {
34396
- this.console.log('Device discovery postponed: deviceManager not ready yet.');
34397
- this.triedDiscoveryOnce = true;
34398
- }
34399
- return;
34400
- }
34401
- this.triedDiscoveryOnce = false;
34402
- this.discoverSensors(dmAny);
34403
- }
34404
- /** discoverSensors con deviceManager garantito */
34405
- discoverSensors(dmAny) {
34406
- // 1) Manifests
34388
+ /** ===== discoverSensors: annuncia PRIMA, istanzia DOPO ===== */
34389
+ async discoverSensors() {
34390
+ // 1) Prepara i manifest (niente istanze qui)
34407
34391
  const manifests = this.sensorsCfg.map(cfg => {
34408
34392
  const nativeId = `sensor:${cfg.id}`;
34409
34393
  const t = cfg.topics || {};
34410
- const interfaces = [ScryptedInterface.Online];
34394
+ const interfaces = [sdk_1.ScryptedInterface.Online];
34395
+ // Tamper solo se c'è un topic tamper
34411
34396
  if (t.tamper)
34412
- interfaces.push(ScryptedInterface.TamperSensor);
34397
+ interfaces.push(sdk_1.ScryptedInterface.TamperSensor);
34398
+ // Interfaccia primaria
34413
34399
  if (cfg.kind === 'contact')
34414
- interfaces.unshift(ScryptedInterface.EntrySensor);
34400
+ interfaces.unshift(sdk_1.ScryptedInterface.EntrySensor);
34415
34401
  else if (cfg.kind === 'motion')
34416
- interfaces.unshift(ScryptedInterface.MotionSensor);
34402
+ interfaces.unshift(sdk_1.ScryptedInterface.MotionSensor);
34417
34403
  else
34418
- interfaces.unshift(ScryptedInterface.OccupancySensor);
34419
- if (t.batteryLevel || t.lowBattery)
34420
- interfaces.push(ScryptedInterface.Battery);
34421
- return { nativeId, name: cfg.name, type: ScryptedDeviceType.Sensor, interfaces };
34404
+ interfaces.unshift(sdk_1.ScryptedInterface.OccupancySensor);
34405
+ // Battery solo se previsto
34406
+ if (t.batteryLevel || t.lowBattery) {
34407
+ interfaces.push(sdk_1.ScryptedInterface.Battery);
34408
+ }
34409
+ return { nativeId, name: cfg.name, type: sdk_1.ScryptedDeviceType.Sensor, interfaces };
34422
34410
  });
34423
34411
  // 2) Annuncio
34412
+ const dmAny = deviceManager;
34424
34413
  if (typeof dmAny.onDevicesChanged === 'function') {
34425
34414
  dmAny.onDevicesChanged({ devices: manifests });
34415
+ this.console.log('Annunciati (batch):', manifests.map(m => m.nativeId).join(', '));
34426
34416
  }
34427
- else if (typeof dmAny.onDeviceDiscovered === 'function') {
34428
- for (const m of manifests)
34429
- dmAny.onDeviceDiscovered(m);
34417
+ else {
34418
+ for (const m of manifests) {
34419
+ deviceManager.onDeviceDiscovered(m);
34420
+ this.console.log('Annunciato:', m.nativeId);
34421
+ }
34430
34422
  }
34431
- // 3) Istanzia/aggiorna
34423
+ // 3) Istanzia/aggiorna DOPO l’annuncio
34432
34424
  for (const cfg of this.sensorsCfg) {
34433
34425
  const nativeId = `sensor:${cfg.id}`;
34434
34426
  let dev = this.devices.get(nativeId);
@@ -34444,17 +34436,20 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34444
34436
  else {
34445
34437
  dev.cfg = cfg;
34446
34438
  }
34439
+ // Default “OK” se abbiamo Battery ma nessun valore ancora ricevuto
34447
34440
  const hasBattery = !!(cfg.topics.batteryLevel || cfg.topics.lowBattery);
34448
- if (hasBattery && dev.batteryLevel === undefined)
34441
+ if (hasBattery && dev.batteryLevel === undefined) {
34449
34442
  dev.batteryLevel = 100;
34443
+ }
34450
34444
  }
34451
- // 4) Cleanup
34445
+ // 4) Rimuovi quelli spariti
34452
34446
  const announced = new Set(manifests.map(m => m.nativeId));
34453
34447
  for (const [nativeId] of this.devices) {
34454
34448
  if (!announced.has(nativeId)) {
34455
34449
  try {
34456
34450
  this.devices.delete(nativeId);
34457
- dmAny.onDeviceRemoved?.(nativeId);
34451
+ deviceManager.onDeviceRemoved?.(nativeId);
34452
+ this.console.log('Rimosso:', nativeId);
34458
34453
  }
34459
34454
  catch { }
34460
34455
  }
@@ -34468,7 +34463,13 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34468
34463
  const clientId = this.storage.getItem('clientId') || 'scrypted-paradox';
34469
34464
  const tls = this.storage.getItem('tls') === 'true';
34470
34465
  const rejectUnauthorized = this.storage.getItem('rejectUnauthorized') !== 'false';
34471
- const opts = { clientId, username, password, clean: true, reconnectPeriod: 3000 };
34466
+ const opts = {
34467
+ clientId,
34468
+ username,
34469
+ password,
34470
+ clean: true,
34471
+ reconnectPeriod: 3000,
34472
+ };
34472
34473
  if (tls) {
34473
34474
  opts.protocol = 'mqtts';
34474
34475
  opts.rejectUnauthorized = rejectUnauthorized;
@@ -34477,34 +34478,38 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34477
34478
  }
34478
34479
  collectAllSubscriptions() {
34479
34480
  const subs = new Set();
34481
+ // alarm
34480
34482
  for (const k of ['topicGetTarget', 'topicGetCurrent', 'topicTamper', 'topicOnline']) {
34481
34483
  const v = this.storage.getItem(k);
34482
34484
  if (v)
34483
34485
  subs.add(v);
34484
34486
  }
34487
+ // sensors
34485
34488
  for (const s of this.sensorsCfg) {
34486
34489
  const t = s.topics || {};
34487
34490
  [t.contact, t.motion, t.occupancy, t.batteryLevel, t.lowBattery, t.tamper, t.online]
34488
- .filter(Boolean).forEach(x => subs.add(String(x)));
34491
+ .filter(Boolean)
34492
+ .forEach(x => subs.add(String(x)));
34489
34493
  }
34490
34494
  return Array.from(subs);
34491
34495
  }
34492
34496
  async connectMqtt(_reconnect = false) {
34493
34497
  const subs = this.collectAllSubscriptions();
34494
- if (!subs.length && !this.storage.getItem('topicSetTarget'))
34498
+ if (!subs.length && !this.storage.getItem('topicSetTarget')) {
34495
34499
  this.console.warn('Configura almeno un topic nelle impostazioni.');
34500
+ }
34496
34501
  if (this.client) {
34497
34502
  try {
34498
34503
  this.client.end(true);
34499
34504
  }
34500
34505
  catch { }
34501
- ;
34502
34506
  this.client = undefined;
34503
34507
  }
34504
34508
  const { url, opts } = this.getMqttOptions();
34505
34509
  this.console.log(`Connecting MQTT ${url} ...`);
34506
34510
  const client = mqtt_1.default.connect(url, opts);
34507
34511
  this.client = client;
34512
+ // cache alarm topics for fast compare
34508
34513
  const tTarget = this.storage.getItem('topicGetTarget') || '';
34509
34514
  const tCurrent = this.storage.getItem('topicGetCurrent') || '';
34510
34515
  const tTamper = this.storage.getItem('topicTamper') || '';
@@ -34512,11 +34517,12 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34512
34517
  client.on('connect', () => {
34513
34518
  this.console.log('MQTT connected');
34514
34519
  this.online = true;
34515
- if (subs.length)
34516
- client.subscribe(subs, { qos: 0 }, (err) => { if (err)
34517
- this.console.error('subscribe error', err); });
34518
- // Al primo connect riprova ad annunciare i sensori
34519
- this.safeDiscoverSensors(true);
34520
+ if (subs.length) {
34521
+ client.subscribe(subs, { qos: 0 }, (err) => {
34522
+ if (err)
34523
+ this.console.error('subscribe error', err);
34524
+ });
34525
+ }
34520
34526
  });
34521
34527
  client.on('reconnect', () => this.console.log('MQTT reconnecting...'));
34522
34528
  client.on('close', () => { this.console.log('MQTT closed'); this.online = false; });
@@ -34525,6 +34531,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34525
34531
  try {
34526
34532
  const p = payload?.toString() ?? '';
34527
34533
  const np = normalize(p);
34534
+ // ---- Alarm handling ----
34528
34535
  if (topic === tOnline) {
34529
34536
  if (truthy(np) || np === 'online')
34530
34537
  this.online = true;
@@ -34533,26 +34540,29 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34533
34540
  return;
34534
34541
  }
34535
34542
  if (topic === tTamper) {
34536
- if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np))
34543
+ if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np)) {
34537
34544
  this.tampered = ['cover', 'intrusion'].find(x => x === np) || true;
34538
- else if (falsy(np))
34545
+ }
34546
+ else if (falsy(np)) {
34539
34547
  this.tampered = false;
34548
+ }
34540
34549
  return;
34541
34550
  }
34542
34551
  if (topic === tCurrent) {
34543
34552
  const mode = payloadToMode(payload);
34544
34553
  const isAlarm = ['alarm', 'triggered'].includes(np);
34545
- const current = this.securitySystemState || { mode: SecuritySystemMode.Disarmed };
34546
- this.securitySystemState = {
34554
+ const current = this.securitySystemState || { mode: sdk_1.SecuritySystemMode.Disarmed };
34555
+ const newState = {
34547
34556
  mode: mode ?? current.mode,
34548
34557
  supportedModes: current.supportedModes ?? [
34549
- SecuritySystemMode.Disarmed,
34550
- SecuritySystemMode.HomeArmed,
34551
- SecuritySystemMode.AwayArmed,
34552
- SecuritySystemMode.NightArmed,
34558
+ sdk_1.SecuritySystemMode.Disarmed,
34559
+ sdk_1.SecuritySystemMode.HomeArmed,
34560
+ sdk_1.SecuritySystemMode.AwayArmed,
34561
+ sdk_1.SecuritySystemMode.NightArmed,
34553
34562
  ],
34554
34563
  triggered: isAlarm || undefined,
34555
34564
  };
34565
+ this.securitySystemState = newState;
34556
34566
  return;
34557
34567
  }
34558
34568
  if (topic === tTarget) {
@@ -34560,11 +34570,10 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34560
34570
  this.console.log('Target state reported:', p, '->', this.pendingTarget);
34561
34571
  return;
34562
34572
  }
34563
- // Dispatch ai sensori + eventuale discover ritardato
34564
- if (this.triedDiscoveryOnce)
34565
- this.safeDiscoverSensors(true);
34566
- for (const dev of this.devices.values())
34573
+ // ---- Sensor dispatch ----
34574
+ for (const dev of this.devices.values()) {
34567
34575
  dev.handleMqtt(topic, payload);
34576
+ }
34568
34577
  }
34569
34578
  catch (e) {
34570
34579
  this.console.error('MQTT message handler error', e);
@@ -34589,21 +34598,21 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
34589
34598
  async armSecuritySystem(mode) {
34590
34599
  const payload = this.getOutgoing(mode);
34591
34600
  this.console.log('armSecuritySystem', mode, '->', payload);
34592
- this.pendingTarget = mode;
34601
+ this.pendingTarget = mode; // memorizza target, ma NON cambiare il current
34593
34602
  this.publishSetTarget(payload);
34594
34603
  }
34595
34604
  async disarmSecuritySystem() {
34596
- const payload = this.getOutgoing(SecuritySystemMode.Disarmed);
34605
+ const payload = this.getOutgoing(sdk_1.SecuritySystemMode.Disarmed);
34597
34606
  this.console.log('disarmSecuritySystem ->', payload);
34598
- this.pendingTarget = SecuritySystemMode.Disarmed;
34607
+ this.pendingTarget = sdk_1.SecuritySystemMode.Disarmed;
34599
34608
  this.publishSetTarget(payload);
34600
34609
  }
34601
34610
  getOutgoing(mode) {
34602
34611
  const map = {
34603
- [SecuritySystemMode.Disarmed]: this.storage.getItem('payloadDisarm') || DEFAULT_OUTGOING[SecuritySystemMode.Disarmed],
34604
- [SecuritySystemMode.HomeArmed]: this.storage.getItem('payloadHome') || DEFAULT_OUTGOING[SecuritySystemMode.HomeArmed],
34605
- [SecuritySystemMode.AwayArmed]: this.storage.getItem('payloadAway') || DEFAULT_OUTGOING[SecuritySystemMode.AwayArmed],
34606
- [SecuritySystemMode.NightArmed]: this.storage.getItem('payloadNight') || DEFAULT_OUTGOING[SecuritySystemMode.NightArmed],
34612
+ [sdk_1.SecuritySystemMode.Disarmed]: this.storage.getItem('payloadDisarm') || DEFAULT_OUTGOING[sdk_1.SecuritySystemMode.Disarmed],
34613
+ [sdk_1.SecuritySystemMode.HomeArmed]: this.storage.getItem('payloadHome') || DEFAULT_OUTGOING[sdk_1.SecuritySystemMode.HomeArmed],
34614
+ [sdk_1.SecuritySystemMode.AwayArmed]: this.storage.getItem('payloadAway') || DEFAULT_OUTGOING[sdk_1.SecuritySystemMode.AwayArmed],
34615
+ [sdk_1.SecuritySystemMode.NightArmed]: this.storage.getItem('payloadNight') || DEFAULT_OUTGOING[sdk_1.SecuritySystemMode.NightArmed],
34607
34616
  };
34608
34617
  return map[mode];
34609
34618
  }
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.40",
3
+ "version": "1.0.42",
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",