@rfranzoi/scrypted-mqtt-securitysystem 1.0.14 → 1.0.17

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.
@@ -34063,7 +34063,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
34063
34063
  Object.defineProperty(exports, "__esModule", ({ value: true }));
34064
34064
  const sdk_1 = __importStar(__webpack_require__(/*! @scrypted/sdk */ "./node_modules/@scrypted/sdk/dist/src/index.js"));
34065
34065
  const mqtt_1 = __importDefault(__webpack_require__(/*! mqtt */ "./node_modules/mqtt/build/index.js"));
34066
- const { systemManager } = sdk_1.default;
34066
+ const { systemManager, deviceManager } = sdk_1.default;
34067
+ /** utils */
34067
34068
  function truthy(v) {
34068
34069
  if (!v)
34069
34070
  return false;
@@ -34079,20 +34080,21 @@ function falsy(v) {
34079
34080
  function normalize(s) {
34080
34081
  return (s || '').trim().toLowerCase();
34081
34082
  }
34082
- /** Default payloads for PAI/PAI-MQTT-like setups */
34083
+ function clamp(n, min, max) {
34084
+ return Math.max(min, Math.min(max, n));
34085
+ }
34086
+ /** SecuritySystem outgoing defaults (PAI-like) */
34083
34087
  const DEFAULT_OUTGOING = {
34084
34088
  [sdk_1.SecuritySystemMode.Disarmed]: 'disarm',
34085
34089
  [sdk_1.SecuritySystemMode.HomeArmed]: 'arm_home',
34086
34090
  [sdk_1.SecuritySystemMode.AwayArmed]: 'arm_away',
34087
34091
  [sdk_1.SecuritySystemMode.NightArmed]: 'arm_night',
34088
34092
  };
34089
- /** Common incoming synonyms SecuritySystemMode
34090
- * (FIX: gli stati transitori non alterano la modalità) */
34093
+ /** Parse incoming payload -> final mode (ignore transition states) */
34091
34094
  function payloadToMode(payload) {
34092
34095
  if (payload == null)
34093
34096
  return;
34094
34097
  const p = normalize(payload.toString());
34095
- // final modes
34096
34098
  if (['disarm', 'disarmed', 'off', '0', 'idle', 'ready'].includes(p))
34097
34099
  return sdk_1.SecuritySystemMode.Disarmed;
34098
34100
  if (['arm_home', 'home', 'stay', 'armed_home'].includes(p))
@@ -34101,14 +34103,88 @@ function payloadToMode(payload) {
34101
34103
  return sdk_1.SecuritySystemMode.AwayArmed;
34102
34104
  if (['arm_night', 'night', 'armed_night', 'sleep', 'arm_sleep', 'armed_sleep'].includes(p))
34103
34105
  return sdk_1.SecuritySystemMode.NightArmed;
34104
- // transitional / ignore for mode changes
34106
+ // transitori: non cambiano il mode
34105
34107
  if (['entry_delay', 'exit_delay', 'pending', 'arming', 'disarming'].includes(p))
34106
34108
  return undefined;
34107
34109
  return undefined;
34108
34110
  }
34111
+ class BaseMqttSensor extends sdk_1.ScryptedDeviceBase {
34112
+ constructor(nativeId, cfg) {
34113
+ super(nativeId);
34114
+ this.cfg = cfg;
34115
+ this.online = this.online ?? true;
34116
+ }
34117
+ /** Called by parent on each MQTT message */
34118
+ handleMqtt(topic, payload) {
34119
+ const p = payload?.toString() ?? '';
34120
+ const np = normalize(p);
34121
+ // online
34122
+ if (topic === this.cfg.topics.online) {
34123
+ if (truthy(np) || np === 'online')
34124
+ this.online = true;
34125
+ if (falsy(np) || np === 'offline')
34126
+ this.online = false;
34127
+ }
34128
+ // tamper
34129
+ if (topic === this.cfg.topics.tamper) {
34130
+ if (truthy(np) || ['tamper', 'intrusion', 'cover', 'motion', 'magnetic'].includes(np)) {
34131
+ this.tampered = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
34132
+ }
34133
+ else if (falsy(np)) {
34134
+ this.tampered = false;
34135
+ }
34136
+ }
34137
+ // battery
34138
+ if (topic === this.cfg.topics.batteryLevel) {
34139
+ const n = clamp(parseFloat(p), 0, 100);
34140
+ if (isFinite(n))
34141
+ this.batteryLevel = n;
34142
+ }
34143
+ else if (topic === this.cfg.topics.lowBattery && !this.cfg.topics.batteryLevel) {
34144
+ // sintetizza se non c'è batteryLevel
34145
+ this.batteryLevel = truthy(np) ? 10 : 100;
34146
+ }
34147
+ // primary handled by subclasses
34148
+ this.handlePrimary(topic, np, p);
34149
+ }
34150
+ }
34151
+ class ContactMqttSensor extends BaseMqttSensor {
34152
+ constructor(nativeId, cfg) {
34153
+ super(nativeId, cfg);
34154
+ }
34155
+ handlePrimary(topic, np, _raw) {
34156
+ if (topic === this.cfg.topics.contact) {
34157
+ this.entryOpen = truthy(np);
34158
+ }
34159
+ }
34160
+ }
34161
+ class MotionMqttSensor extends BaseMqttSensor {
34162
+ constructor(nativeId, cfg) {
34163
+ super(nativeId, cfg);
34164
+ }
34165
+ handlePrimary(topic, np, _raw) {
34166
+ if (topic === this.cfg.topics.motion) {
34167
+ this.motionDetected = truthy(np);
34168
+ }
34169
+ }
34170
+ }
34171
+ class OccupancyMqttSensor extends BaseMqttSensor {
34172
+ constructor(nativeId, cfg) {
34173
+ super(nativeId, cfg);
34174
+ }
34175
+ handlePrimary(topic, np, _raw) {
34176
+ if (topic === this.cfg.topics.occupancy) {
34177
+ this.occupied = truthy(np);
34178
+ }
34179
+ }
34180
+ }
34181
+ /** ----------------- Main Plugin ----------------- */
34109
34182
  class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34110
34183
  constructor() {
34111
34184
  super();
34185
+ // sensor management
34186
+ this.sensorsCfg = [];
34187
+ this.devices = new Map();
34112
34188
  // (facoltativo) Imposta il device type in UI
34113
34189
  setTimeout(() => {
34114
34190
  try {
@@ -34127,49 +34203,156 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34127
34203
  ],
34128
34204
  };
34129
34205
  this.online = this.online ?? false;
34206
+ // Load sensors config and announce devices
34207
+ this.loadSensorsFromStorage();
34208
+ this.discoverSensors();
34130
34209
  // Connect on start
34131
34210
  this.connectMqtt().catch(e => this.console.error('MQTT connect error:', e));
34132
34211
  // chiusura pulita del client MQTT ai reload/stop del plugin
34133
- process.once('SIGTERM', () => { try {
34134
- this.client?.end(true);
34135
- }
34136
- catch { } });
34137
- process.once('SIGINT', () => { try {
34138
- this.client?.end(true);
34139
- }
34140
- catch { } });
34141
- process.on('exit', () => { try {
34142
- this.client?.end(true);
34212
+ try {
34213
+ process.once('SIGTERM', () => { try {
34214
+ this.client?.end(true);
34215
+ }
34216
+ catch { } });
34217
+ process.once('SIGINT', () => { try {
34218
+ this.client?.end(true);
34219
+ }
34220
+ catch { } });
34221
+ process.on('exit', () => { try {
34222
+ this.client?.end(true);
34223
+ }
34224
+ catch { } });
34143
34225
  }
34144
- catch { } });
34226
+ catch { }
34145
34227
  }
34146
- // --- Settings UI ---
34228
+ /** ---- Settings UI ---- */
34147
34229
  async getSettings() {
34148
34230
  return [
34231
+ // MQTT Core
34149
34232
  { 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' },
34150
34233
  { group: 'MQTT', key: 'username', title: 'Username', type: 'string', value: this.storage.getItem('username') || '' },
34151
34234
  { group: 'MQTT', key: 'password', title: 'Password', type: 'password', value: this.storage.getItem('password') || '' },
34152
34235
  { group: 'MQTT', key: 'clientId', title: 'Client ID', placeholder: 'scrypted-paradox', value: this.storage.getItem('clientId') || 'scrypted-paradox' },
34153
34236
  { group: 'MQTT', key: 'tls', title: 'Use TLS', type: 'boolean', value: this.storage.getItem('tls') === 'true' },
34154
34237
  { group: 'MQTT', key: 'rejectUnauthorized', title: 'Reject Unauthorized (TLS)', type: 'boolean', value: this.storage.getItem('rejectUnauthorized') !== 'false', description: 'Disattiva solo con broker self-signed.' },
34155
- { group: 'Topics', key: 'topicSetTarget', title: 'Set Target State (publish)', placeholder: 'paradox/control/partitions/Area_1', value: this.storage.getItem('topicSetTarget') || '' },
34156
- { group: 'Topics', key: 'topicGetTarget', title: 'Get Target State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/target_state', value: this.storage.getItem('topicGetTarget') || '' },
34157
- { group: 'Topics', key: 'topicGetCurrent', title: 'Get Current State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/current_state', value: this.storage.getItem('topicGetCurrent') || '' },
34158
- { group: 'Topics', key: 'topicTamper', title: 'Get Status Tampered (subscribe)', placeholder: 'paradox/states/system/troubles/zone_tamper_trouble', value: this.storage.getItem('topicTamper') || '' },
34159
- { group: 'Topics', key: 'topicOnline', title: 'Get Online (subscribe)', placeholder: 'paradox/interface/availability', value: this.storage.getItem('topicOnline') || '' },
34238
+ // Alarm Topics
34239
+ { group: 'Alarm Topics', key: 'topicSetTarget', title: 'Set Target State (publish)', placeholder: 'paradox/control/partitions/Area_1', value: this.storage.getItem('topicSetTarget') || '' },
34240
+ { group: 'Alarm Topics', key: 'topicGetTarget', title: 'Get Target State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/target_state', value: this.storage.getItem('topicGetTarget') || '' },
34241
+ { group: 'Alarm Topics', key: 'topicGetCurrent', title: 'Get Current State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/current_state', value: this.storage.getItem('topicGetCurrent') || '' },
34242
+ { group: 'Alarm Topics', key: 'topicTamper', title: 'Get Status Tampered (subscribe)', placeholder: 'paradox/states/system/troubles/zone_tamper_trouble', value: this.storage.getItem('topicTamper') || '' },
34243
+ { group: 'Alarm Topics', key: 'topicOnline', title: 'Get Online (subscribe)', placeholder: 'paradox/interface/availability', value: this.storage.getItem('topicOnline') || '' },
34160
34244
  { group: 'Publish Options', key: 'qos', title: 'QoS', type: 'integer', value: parseInt(this.storage.getItem('qos') || '0') },
34161
34245
  { group: 'Publish Options', key: 'retain', title: 'Retain', type: 'boolean', value: this.storage.getItem('retain') === 'true' },
34162
34246
  { group: 'Outgoing Payloads', key: 'payloadDisarm', title: 'Payload Disarm', value: this.storage.getItem('payloadDisarm') || DEFAULT_OUTGOING[sdk_1.SecuritySystemMode.Disarmed] },
34163
34247
  { group: 'Outgoing Payloads', key: 'payloadHome', title: 'Payload HomeArmed', value: this.storage.getItem('payloadHome') || DEFAULT_OUTGOING[sdk_1.SecuritySystemMode.HomeArmed] },
34164
34248
  { group: 'Outgoing Payloads', key: 'payloadAway', title: 'Payload AwayArmed', value: this.storage.getItem('payloadAway') || DEFAULT_OUTGOING[sdk_1.SecuritySystemMode.AwayArmed] },
34165
34249
  { group: 'Outgoing Payloads', key: 'payloadNight', title: 'Payload NightArmed', value: this.storage.getItem('payloadNight') || DEFAULT_OUTGOING[sdk_1.SecuritySystemMode.NightArmed] },
34250
+ // Sensors config (JSON)
34251
+ {
34252
+ group: 'Sensors',
34253
+ key: 'sensorsJson',
34254
+ title: 'Sensors JSON (contact/motion/occupancy)',
34255
+ description: 'Definisci i sensori e i topic MQTT (vedi README). Incolla JSON; le interruzioni di riga sono accettate.',
34256
+ type: 'string',
34257
+ value: this.storage.getItem('sensorsJson') || '[\n {\n "id": "front-door",\n "name": "Front Door",\n "kind": "contact",\n "topics": { "contact": "SYSTEM/zones/front/contact" }\n }\n]'
34258
+ },
34166
34259
  ];
34167
34260
  }
34168
34261
  async putSetting(key, value) {
34169
34262
  this.storage.setItem(key, String(value));
34170
- await this.connectMqtt(true);
34263
+ if (key === 'sensorsJson') {
34264
+ this.loadSensorsFromStorage();
34265
+ await this.discoverSensors();
34266
+ await this.connectMqtt(true);
34267
+ }
34268
+ else {
34269
+ await this.connectMqtt(true);
34270
+ }
34271
+ }
34272
+ /** ---- DeviceProvider ---- */
34273
+ async getDevice(nativeId) {
34274
+ return this.devices.get(nativeId);
34171
34275
  }
34172
- // --- MQTT ---
34276
+ async releaseDevice(id, nativeId) {
34277
+ try {
34278
+ // chiudi e rimuovi l’istanza locale se esiste
34279
+ const dev = this.devices.get(nativeId);
34280
+ if (dev) {
34281
+ this.devices.delete(nativeId);
34282
+ }
34283
+ // notifica (best effort) la rimozione al device manager
34284
+ try {
34285
+ deviceManager.onDeviceRemoved?.(nativeId);
34286
+ }
34287
+ catch { }
34288
+ }
34289
+ catch (e) {
34290
+ this.console.warn('releaseDevice error', e);
34291
+ }
34292
+ }
34293
+ loadSensorsFromStorage() {
34294
+ try {
34295
+ const raw = this.storage.getItem('sensorsJson') || '[]';
34296
+ const parsed = JSON.parse(raw);
34297
+ // sanitize
34298
+ this.sensorsCfg = (parsed || []).filter(x => x && x.id && x.name && x.kind && x.topics);
34299
+ }
34300
+ catch (e) {
34301
+ this.console.error('Invalid sensorsJson:', e);
34302
+ this.sensorsCfg = [];
34303
+ }
34304
+ }
34305
+ async discoverSensors() {
34306
+ const announced = new Set();
34307
+ for (const cfg of this.sensorsCfg) {
34308
+ const nativeId = `sensor:${cfg.id}`;
34309
+ announced.add(nativeId);
34310
+ let interfaces = [sdk_1.ScryptedInterface.Online, sdk_1.ScryptedInterface.TamperSensor, sdk_1.ScryptedInterface.Battery];
34311
+ switch (cfg.kind) {
34312
+ case 'contact':
34313
+ interfaces = [sdk_1.ScryptedInterface.EntrySensor, ...interfaces];
34314
+ break;
34315
+ case 'motion':
34316
+ interfaces = [sdk_1.ScryptedInterface.MotionSensor, ...interfaces];
34317
+ break;
34318
+ case 'occupancy':
34319
+ interfaces = [sdk_1.ScryptedInterface.OccupancySensor, ...interfaces];
34320
+ break;
34321
+ }
34322
+ deviceManager.onDeviceDiscovered({
34323
+ nativeId,
34324
+ name: cfg.name,
34325
+ type: sdk_1.ScryptedDeviceType.Sensor,
34326
+ interfaces,
34327
+ });
34328
+ // create/update instance
34329
+ let dev = this.devices.get(nativeId);
34330
+ if (!dev) {
34331
+ if (cfg.kind === 'contact')
34332
+ dev = new ContactMqttSensor(nativeId, cfg);
34333
+ else if (cfg.kind === 'motion')
34334
+ dev = new MotionMqttSensor(nativeId, cfg);
34335
+ else
34336
+ dev = new OccupancyMqttSensor(nativeId, cfg);
34337
+ this.devices.set(nativeId, dev);
34338
+ }
34339
+ else {
34340
+ // update config reference
34341
+ dev.cfg = cfg;
34342
+ }
34343
+ }
34344
+ // drop removed sensors
34345
+ for (const [nativeId] of this.devices) {
34346
+ if (!announced.has(nativeId)) {
34347
+ try {
34348
+ this.devices.delete(nativeId);
34349
+ deviceManager.onDeviceRemoved?.(nativeId);
34350
+ }
34351
+ catch { }
34352
+ }
34353
+ }
34354
+ }
34355
+ /** ---- MQTT ---- */
34173
34356
  getMqttOptions() {
34174
34357
  const url = this.storage.getItem('brokerUrl') || 'mqtt://127.0.0.1:1883';
34175
34358
  const username = this.storage.getItem('username') || undefined;
@@ -34190,13 +34373,25 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34190
34373
  }
34191
34374
  return { url, opts };
34192
34375
  }
34376
+ collectAllSubscriptions() {
34377
+ const subs = new Set();
34378
+ // alarm
34379
+ for (const k of ['topicGetTarget', 'topicGetCurrent', 'topicTamper', 'topicOnline']) {
34380
+ const v = this.storage.getItem(k);
34381
+ if (v)
34382
+ subs.add(v);
34383
+ }
34384
+ // sensors
34385
+ for (const s of this.sensorsCfg) {
34386
+ const t = s.topics;
34387
+ [t.contact, t.motion, t.occupancy, t.batteryLevel, t.lowBattery, t.tamper, t.online]
34388
+ .filter(Boolean)
34389
+ .forEach(x => subs.add(String(x)));
34390
+ }
34391
+ return Array.from(subs);
34392
+ }
34193
34393
  async connectMqtt(reconnect = false) {
34194
- const subs = [
34195
- this.storage.getItem('topicGetTarget'),
34196
- this.storage.getItem('topicGetCurrent'),
34197
- this.storage.getItem('topicTamper'),
34198
- this.storage.getItem('topicOnline'),
34199
- ].filter(Boolean);
34394
+ const subs = this.collectAllSubscriptions();
34200
34395
  if (!subs.length && !this.storage.getItem('topicSetTarget')) {
34201
34396
  this.console.warn('Configura almeno un topic nelle impostazioni.');
34202
34397
  }
@@ -34211,6 +34406,11 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34211
34406
  this.console.log(`Connecting MQTT ${url} ...`);
34212
34407
  const client = mqtt_1.default.connect(url, opts);
34213
34408
  this.client = client;
34409
+ // cache alarm topics for fast compare
34410
+ const tTarget = this.storage.getItem('topicGetTarget') || '';
34411
+ const tCurrent = this.storage.getItem('topicGetCurrent') || '';
34412
+ const tTamper = this.storage.getItem('topicTamper') || '';
34413
+ const tOnline = this.storage.getItem('topicOnline') || '';
34214
34414
  client.on('connect', () => {
34215
34415
  this.console.log('MQTT connected');
34216
34416
  this.online = true;
@@ -34227,29 +34427,27 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34227
34427
  client.on('message', (topic, payload) => {
34228
34428
  try {
34229
34429
  const p = payload?.toString() ?? '';
34230
- // Online
34231
- if (topic === this.storage.getItem('topicOnline')) {
34232
- if (truthy(p) || p.toLowerCase() === 'online')
34430
+ const np = normalize(p);
34431
+ // ---- Alarm handling ----
34432
+ if (topic === tOnline) {
34433
+ if (truthy(np) || np === 'online')
34233
34434
  this.online = true;
34234
- if (falsy(p) || p.toLowerCase() === 'offline')
34435
+ if (falsy(np) || np === 'offline')
34235
34436
  this.online = false;
34236
34437
  return;
34237
34438
  }
34238
- // Tamper
34239
- if (topic === this.storage.getItem('topicTamper')) {
34240
- const np = normalize(p);
34241
- if (truthy(np) || ['tamper', 'intrusion', 'cover', 'motion', 'magnetic'].includes(np)) {
34242
- this.tampered = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
34439
+ if (topic === tTamper) {
34440
+ if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np)) {
34441
+ this.tampered = ['cover', 'intrusion'].find(x => x === np) || true;
34243
34442
  }
34244
34443
  else if (falsy(np)) {
34245
34444
  this.tampered = false;
34246
34445
  }
34247
34446
  return;
34248
34447
  }
34249
- // Target/Current mode/triggered
34250
- if ([this.storage.getItem('topicGetTarget'), this.storage.getItem('topicGetCurrent')].includes(topic)) {
34448
+ if (topic === tCurrent) {
34251
34449
  const mode = payloadToMode(payload);
34252
- const isAlarm = ['alarm', 'triggered'].includes(normalize(p));
34450
+ const isAlarm = ['alarm', 'triggered'].includes(np);
34253
34451
  const current = this.securitySystemState || { mode: sdk_1.SecuritySystemMode.Disarmed };
34254
34452
  const newState = {
34255
34453
  mode: mode ?? current.mode,
@@ -34264,13 +34462,22 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34264
34462
  this.securitySystemState = newState;
34265
34463
  return;
34266
34464
  }
34465
+ if (topic === tTarget) {
34466
+ this.pendingTarget = payloadToMode(payload);
34467
+ this.console.log('Target state reported:', p, '->', this.pendingTarget);
34468
+ return;
34469
+ }
34470
+ // ---- Sensor dispatch ----
34471
+ for (const dev of this.devices.values()) {
34472
+ dev.handleMqtt(topic, payload);
34473
+ }
34267
34474
  }
34268
34475
  catch (e) {
34269
34476
  this.console.error('MQTT message handler error', e);
34270
34477
  }
34271
34478
  });
34272
34479
  }
34273
- // --- SecuritySystem commands ---
34480
+ /** ---- SecuritySystem commands ---- */
34274
34481
  publishSetTarget(payload) {
34275
34482
  const topic = this.storage.getItem('topicSetTarget');
34276
34483
  if (!topic || !this.client) {
@@ -34288,17 +34495,14 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34288
34495
  async armSecuritySystem(mode) {
34289
34496
  const payload = this.getOutgoing(mode);
34290
34497
  this.console.log('armSecuritySystem', mode, '->', payload);
34498
+ this.pendingTarget = mode; // memorizza target, ma NON cambiare il current
34291
34499
  this.publishSetTarget(payload);
34292
- // Optimistic UI update until broker echoes target/current state
34293
- const st = this.securitySystemState || { mode: sdk_1.SecuritySystemMode.Disarmed };
34294
- this.securitySystemState = { ...st, mode, triggered: false };
34295
34500
  }
34296
34501
  async disarmSecuritySystem() {
34297
34502
  const payload = this.getOutgoing(sdk_1.SecuritySystemMode.Disarmed);
34298
34503
  this.console.log('disarmSecuritySystem ->', payload);
34504
+ this.pendingTarget = sdk_1.SecuritySystemMode.Disarmed;
34299
34505
  this.publishSetTarget(payload);
34300
- const st = this.securitySystemState || { mode: sdk_1.SecuritySystemMode.Disarmed };
34301
- this.securitySystemState = { ...st, mode: sdk_1.SecuritySystemMode.Disarmed, triggered: false };
34302
34506
  }
34303
34507
  getOutgoing(mode) {
34304
34508
  const map = {
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.14",
3
+ "version": "1.0.17",
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",