@rfranzoi/scrypted-mqtt-securitysystem 1.0.98 → 1.1.0

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.
package/README.md CHANGED
@@ -7,7 +7,9 @@ Control and sync alarm panels over MQTT (e.g. Paradox with PAI/PAI-MQTT), expose
7
7
  ## Features
8
8
  - Arm/Disarm: Disarmed, Home, Away, Night.
9
9
  - MQTT topics for current/target state, tamper, and online.
10
+ - Multiple partition support through additional child **SecuritySystem** devices.
10
11
  - Optional per‑sensor MQTT bindings (contact/motion/occupancy) with battery, tamper, and online status.
12
+ - Optional per‑sensor **Bypass** switch via MQTT topics.
11
13
  - HomeKit: usable through the official Scrypted HomeKit plugin (non-standalone accessory).
12
14
 
13
15
  ---
@@ -37,6 +39,22 @@ Then in **Scrypted → Manage Plugins → Install
37
39
  - **Get Status Tampered (subscribe)**: tamper status.
38
40
  - **Get Online (subscribe)**: online/offline indicator of the alarm bridge.
39
41
 
42
+ ### Partitions
43
+ The main plugin device keeps the legacy single-partition alarm topics above. For panels with multiple
44
+ areas/partitions, use **Add Partition** in Settings to create one child **SecuritySystem** per area.
45
+
46
+ For example:
47
+ - **Casa**
48
+ - Set Target: `paradox/control/partitions/Casa`
49
+ - Get Target: `paradox/states/partitions/Casa/target_state`
50
+ - Get Current: `paradox/states/partitions/Casa/current_state`
51
+ - **Cantina**
52
+ - Set Target: `paradox/control/partitions/Cantina`
53
+ - Get Target: `paradox/states/partitions/Cantina/target_state`
54
+ - Get Current: `paradox/states/partitions/Cantina/current_state`
55
+
56
+ Each partition is exposed as a separate Scrypted security system and can be added separately to HomeKit.
57
+
40
58
  ### Publish Options
41
59
  - **QoS** / **Retain**
42
60
 
@@ -60,6 +78,10 @@ Each sensor has:
60
78
  - Motion: `your/zones/hallway/motion`
61
79
  - Occupancy: `your/rooms/office/occupied`
62
80
  - Optional: `batteryLevel` (0..100), `lowBattery` (boolean), `tamper`, `online`
81
+ - **bypass (optional)**:
82
+ - **Bypass Control Topic**: publish a bypass command for that zone.
83
+ - **Bypass State Topic**: subscribe to the actual bypass state.
84
+ - **Bypass Payload ON/OFF**: defaults to `bypass` / `clear_bypass`.
63
85
 
64
86
  ### Creating Sensors from the UI
65
87
  - Fill the fields for **ID**, **Name**, **Kind**, and desired **Topics**.
@@ -69,6 +91,11 @@ Each sensor has:
69
91
 
70
92
  > Only the capabilities for which topics are provided will be exposed (e.g., no low‑battery if no related topic is set).
71
93
 
94
+ ### Bypass Switch (Optional)
95
+ If you configure bypass topics for a sensor, the plugin exposes an extra switch accessory named **`<Zone Name> Bypass`**.
96
+ Turning the switch **On/Off** publishes to the **Bypass Control Topic** with the configured payloads, and the
97
+ **Bypass State Topic** keeps the switch in sync (if provided).
98
+
72
99
  ---
73
100
 
74
101
  ## HomeKit Notes
@@ -83,6 +110,7 @@ Add this plugin’s device to the HomeKit plugin **(not as a Standalone accessor
83
110
  - **Get Current** (subscribe): `alarm/states/partition_1/current_state`
84
111
  - **Tamper** (subscribe): `alarm/states/system/tamper`
85
112
  - **Online** (subscribe): `alarm/interface/availability` (`online`/`offline`)
113
+ - **Zone Bypass** (optional): publish to `alarm/control/zones/front-door`, state from `alarm/states/zones/front-door/bypassed`
86
114
 
87
115
  ---
88
116
 
@@ -34451,12 +34451,99 @@ class BypassMqttSwitch extends sdk_1.ScryptedDeviceBase {
34451
34451
  return this.setPowerState(on);
34452
34452
  }
34453
34453
  }
34454
+ class PartitionMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34455
+ constructor(nativeId, cfg, parent) {
34456
+ super(nativeId);
34457
+ this.cfg = cfg;
34458
+ this.parent = parent;
34459
+ this.securitySystemState = this.securitySystemState || parent.defaultSecuritySystemState();
34460
+ this.online = this.online ?? false;
34461
+ }
34462
+ updateConfig(cfg) {
34463
+ this.cfg = cfg;
34464
+ }
34465
+ handleMqtt(topic, payload) {
34466
+ const raw = payload?.toString() ?? '';
34467
+ const np = normalize(raw);
34468
+ if (topic === this.cfg.topicOnline) {
34469
+ if (truthy(np) || np === 'online') {
34470
+ this.online = true;
34471
+ try {
34472
+ this.onDeviceEvent(sdk_1.ScryptedInterface.Online, true);
34473
+ }
34474
+ catch { }
34475
+ }
34476
+ else if (falsy(np) || np === 'offline') {
34477
+ this.online = false;
34478
+ try {
34479
+ this.onDeviceEvent(sdk_1.ScryptedInterface.Online, false);
34480
+ }
34481
+ catch { }
34482
+ }
34483
+ return;
34484
+ }
34485
+ if (topic === this.cfg.topicTamper) {
34486
+ if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np)) {
34487
+ const val = ['cover', 'intrusion'].find(x => x === np) || true;
34488
+ this.tampered = val;
34489
+ try {
34490
+ this.onDeviceEvent(sdk_1.ScryptedInterface.TamperSensor, val);
34491
+ }
34492
+ catch { }
34493
+ }
34494
+ else if (falsy(np)) {
34495
+ this.tampered = false;
34496
+ try {
34497
+ this.onDeviceEvent(sdk_1.ScryptedInterface.TamperSensor, false);
34498
+ }
34499
+ catch { }
34500
+ }
34501
+ return;
34502
+ }
34503
+ if (topic === this.cfg.topicGetCurrent) {
34504
+ const mode = this.parent.parseIncomingMode(payload);
34505
+ const isAlarm = this.parent.isTriggeredToken(np);
34506
+ const current = this.securitySystemState || this.parent.defaultSecuritySystemState();
34507
+ const newState = {
34508
+ mode: mode ?? current.mode,
34509
+ supportedModes: current.supportedModes ?? this.parent.supportedSecuritySystemModes(),
34510
+ triggered: isAlarm || undefined,
34511
+ };
34512
+ this.securitySystemState = newState;
34513
+ try {
34514
+ this.onDeviceEvent(sdk_1.ScryptedInterface.SecuritySystem, newState);
34515
+ }
34516
+ catch { }
34517
+ if (RUNTIME.logSensors)
34518
+ this.console?.log?.(`[Partition] ${this.cfg.name} current: mode=${newState.mode} triggered=${!!newState.triggered} (${topic}="${raw}")`);
34519
+ return;
34520
+ }
34521
+ if (topic === this.cfg.topicGetTarget) {
34522
+ this.pendingTarget = this.parent.parseIncomingMode(payload);
34523
+ if (RUNTIME.logSensors)
34524
+ this.console?.log?.(`[Partition] ${this.cfg.name} target reported: "${raw}" -> ${this.pendingTarget}`);
34525
+ }
34526
+ }
34527
+ async armSecuritySystem(mode) {
34528
+ const payload = this.parent.getOutgoing(mode);
34529
+ this.console?.log?.('armSecuritySystem', this.cfg.name, mode, '->', payload);
34530
+ this.pendingTarget = mode;
34531
+ this.parent.publishSetTargetTo(this.cfg.topicSetTarget, payload, this.cfg.name);
34532
+ }
34533
+ async disarmSecuritySystem() {
34534
+ const payload = this.parent.getOutgoing(sdk_1.SecuritySystemMode.Disarmed);
34535
+ this.console?.log?.('disarmSecuritySystem', this.cfg.name, '->', payload);
34536
+ this.pendingTarget = sdk_1.SecuritySystemMode.Disarmed;
34537
+ this.parent.publishSetTargetTo(this.cfg.topicSetTarget, payload, this.cfg.name);
34538
+ }
34539
+ }
34454
34540
  /** ----------------- Main Plugin ----------------- */
34455
34541
  class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34456
34542
  constructor() {
34457
34543
  super();
34458
34544
  // device management
34459
34545
  this.sensorsCfg = [];
34546
+ this.partitionsCfg = [];
34460
34547
  this.devices = new Map();
34461
34548
  // load runtime flags
34462
34549
  updateRuntimeFromStorage((k) => this.storage.getItem(k) || '');
@@ -34470,15 +34557,11 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34470
34557
  // Default state
34471
34558
  this.securitySystemState = this.securitySystemState || {
34472
34559
  mode: sdk_1.SecuritySystemMode.Disarmed,
34473
- supportedModes: [
34474
- sdk_1.SecuritySystemMode.Disarmed,
34475
- sdk_1.SecuritySystemMode.HomeArmed,
34476
- sdk_1.SecuritySystemMode.AwayArmed,
34477
- sdk_1.SecuritySystemMode.NightArmed,
34478
- ],
34560
+ supportedModes: this.supportedSecuritySystemModes(),
34479
34561
  };
34480
34562
  this.online = this.online ?? false;
34481
34563
  // Load configs and announce devices
34564
+ this.loadPartitionsFromStorage();
34482
34565
  this.loadSensorsFromStorage();
34483
34566
  this.discoverDevices().catch(e => this.console.error('discoverDevices error', e));
34484
34567
  // Connect on start
@@ -34518,6 +34601,20 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34518
34601
  useStrict() {
34519
34602
  return this.storage.getItem('strictParsing') === 'true';
34520
34603
  }
34604
+ supportedSecuritySystemModes() {
34605
+ return [
34606
+ sdk_1.SecuritySystemMode.Disarmed,
34607
+ sdk_1.SecuritySystemMode.HomeArmed,
34608
+ sdk_1.SecuritySystemMode.AwayArmed,
34609
+ sdk_1.SecuritySystemMode.NightArmed,
34610
+ ];
34611
+ }
34612
+ defaultSecuritySystemState() {
34613
+ return {
34614
+ mode: sdk_1.SecuritySystemMode.Disarmed,
34615
+ supportedModes: this.supportedSecuritySystemModes(),
34616
+ };
34617
+ }
34521
34618
  parseIncomingMode(payload) {
34522
34619
  const np = normalize(payload?.toString?.() ?? String(payload ?? ''));
34523
34620
  if (!this.useStrict())
@@ -34560,6 +34657,14 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34560
34657
  this.console.error('saveSensorsToStorage error', e);
34561
34658
  }
34562
34659
  }
34660
+ savePartitionsToStorage() {
34661
+ try {
34662
+ this.storage.setItem('partitionsJson', JSON.stringify(this.partitionsCfg));
34663
+ }
34664
+ catch (e) {
34665
+ this.console.error('savePartitionsToStorage error', e);
34666
+ }
34667
+ }
34563
34668
  /** ---- Settings UI ---- */
34564
34669
  async getSettings() {
34565
34670
  const out = [
@@ -34592,6 +34697,12 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34592
34697
  { group: 'Logging', key: 'logSensors', title: 'Log sensor state changes', type: 'boolean', value: this.storage.getItem('logSensors') === 'true' },
34593
34698
  { group: 'Logging', key: 'logMqttAll', title: 'Log ALL MQTT messages', type: 'boolean', value: this.storage.getItem('logMqttAll') === 'true', description: 'Warning: this will be very verbose.' },
34594
34699
  ];
34700
+ // ---- UI Add Partition ----
34701
+ out.push({ group: 'Add Partition', key: 'newPartition.id', title: 'New Partition ID', placeholder: 'casa', value: this.storage.getItem('newPartition.id') || '' }, { group: 'Add Partition', key: 'newPartition.name', title: 'Name', placeholder: 'Casa', value: this.storage.getItem('newPartition.name') || '' }, { group: 'Add Partition', key: 'newPartition.topicSetTarget', title: 'Set Target State (publish)', placeholder: 'paradox/control/partitions/Casa', value: this.storage.getItem('newPartition.topicSetTarget') || '' }, { group: 'Add Partition', key: 'newPartition.topicGetTarget', title: 'Get Target State (subscribe)', placeholder: 'paradox/states/partitions/Casa/target_state', value: this.storage.getItem('newPartition.topicGetTarget') || '' }, { group: 'Add Partition', key: 'newPartition.topicGetCurrent', title: 'Get Current State (subscribe)', placeholder: 'paradox/states/partitions/Casa/current_state', value: this.storage.getItem('newPartition.topicGetCurrent') || '' }, { group: 'Add Partition', key: 'newPartition.topicTamper', title: 'Get Status Tampered (subscribe)', placeholder: 'paradox/states/system/troubles/zone_tamper_trouble', value: this.storage.getItem('newPartition.topicTamper') || '' }, { group: 'Add Partition', key: 'newPartition.topicOnline', title: 'Get Online (subscribe)', placeholder: 'paradox/interface/availability', value: this.storage.getItem('newPartition.topicOnline') || '' }, { group: 'Add Partition', key: 'newPartition.create', title: 'Create partition', type: 'boolean', description: 'Fill the fields above and toggle this on to create a separate SecuritySystem device.' });
34702
+ for (const cfg of this.partitionsCfg) {
34703
+ const gid = `Partition: ${cfg.name} [${cfg.id}]`;
34704
+ out.push({ group: gid, key: `partition.${cfg.id}.name`, title: 'Name', value: cfg.name }, { group: gid, key: `partition.${cfg.id}.topicSetTarget`, title: 'Set Target State (publish)', value: cfg.topicSetTarget || '', placeholder: `paradox/control/partitions/${cfg.name}` }, { group: gid, key: `partition.${cfg.id}.topicGetTarget`, title: 'Get Target State (subscribe)', value: cfg.topicGetTarget || '', placeholder: `paradox/states/partitions/${cfg.name}/target_state` }, { group: gid, key: `partition.${cfg.id}.topicGetCurrent`, title: 'Get Current State (subscribe)', value: cfg.topicGetCurrent || '', placeholder: `paradox/states/partitions/${cfg.name}/current_state` }, { group: gid, key: `partition.${cfg.id}.topicTamper`, title: 'Get Status Tampered (subscribe)', value: cfg.topicTamper || '' }, { group: gid, key: `partition.${cfg.id}.topicOnline`, title: 'Get Online (subscribe)', value: cfg.topicOnline || '' }, { group: gid, key: `partition.${cfg.id}.remove`, title: 'Remove partition', type: 'boolean' });
34705
+ }
34595
34706
  // ---- UI Add Sensor ----
34596
34707
  out.push({ group: 'Add Sensor', key: 'new.id', title: 'New Sensor ID', placeholder: 'front-door', value: this.storage.getItem('new.id') || '' }, { group: 'Add Sensor', key: 'new.name', title: 'Name', placeholder: 'Front Door', 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.' });
34597
34708
  // ---- UI for existing sensors ----
@@ -34613,6 +34724,66 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34613
34724
  this.storage.setItem(key, String(value));
34614
34725
  // update runtime flags every time
34615
34726
  updateRuntimeFromStorage((k) => this.storage.getItem(k) || '');
34727
+ // --- Add Partition workflow ---
34728
+ if (key === 'newPartition.create' && String(value) === 'true') {
34729
+ const id = (this.storage.getItem('newPartition.id') || '').trim();
34730
+ const name = (this.storage.getItem('newPartition.name') || '').trim() || id;
34731
+ if (!id) {
34732
+ this.console.warn('Create partition: missing id');
34733
+ return;
34734
+ }
34735
+ if (this.partitionsCfg.find(p => p.id === id)) {
34736
+ this.console.warn('Create partition: id already exists');
34737
+ return;
34738
+ }
34739
+ this.partitionsCfg.push({
34740
+ id,
34741
+ name,
34742
+ topicSetTarget: (this.storage.getItem('newPartition.topicSetTarget') || '').trim(),
34743
+ topicGetTarget: (this.storage.getItem('newPartition.topicGetTarget') || '').trim(),
34744
+ topicGetCurrent: (this.storage.getItem('newPartition.topicGetCurrent') || '').trim(),
34745
+ topicTamper: (this.storage.getItem('newPartition.topicTamper') || '').trim(),
34746
+ topicOnline: (this.storage.getItem('newPartition.topicOnline') || '').trim(),
34747
+ });
34748
+ this.savePartitionsToStorage();
34749
+ for (const k of ['id', 'name', 'topicSetTarget', 'topicGetTarget', 'topicGetCurrent', 'topicTamper', 'topicOnline', 'create'])
34750
+ this.storage.removeItem(`newPartition.${k}`);
34751
+ await this.discoverDevices();
34752
+ await this.connectMqtt(true);
34753
+ return;
34754
+ }
34755
+ // --- Edit/Remove existing partition ---
34756
+ const pm = key.match(/^partition\.([^\.]+)\.(.+)$/);
34757
+ if (pm) {
34758
+ const pid = pm[1];
34759
+ const prop = pm[2];
34760
+ const cfg = this.partitionsCfg.find(p => p.id === pid);
34761
+ if (!cfg) {
34762
+ this.console.warn('putSetting: partition not found', pid);
34763
+ return;
34764
+ }
34765
+ if (prop === 'remove' && String(value) === 'true') {
34766
+ this.partitionsCfg = this.partitionsCfg.filter(p => p.id !== pid);
34767
+ this.savePartitionsToStorage();
34768
+ try {
34769
+ this.devices.delete(`partition:${pid}`);
34770
+ deviceManager.onDeviceRemoved?.(`partition:${pid}`);
34771
+ }
34772
+ catch { }
34773
+ this.storage.removeItem(key);
34774
+ await this.discoverDevices();
34775
+ await this.connectMqtt(true);
34776
+ return;
34777
+ }
34778
+ if (prop === 'name')
34779
+ cfg.name = String(value).trim() || cfg.id;
34780
+ else if (prop !== 'id' && prop !== 'remove')
34781
+ cfg[prop] = String(value).trim();
34782
+ this.savePartitionsToStorage();
34783
+ await this.discoverDevices();
34784
+ await this.connectMqtt(true);
34785
+ return;
34786
+ }
34616
34787
  // --- Add Sensor workflow ---
34617
34788
  if (key === 'new.create' && String(value) === 'true') {
34618
34789
  const id = (this.storage.getItem('new.id') || '').trim();
@@ -34697,6 +34868,15 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34697
34868
  const existing = this.devices.get(nativeId);
34698
34869
  if (existing)
34699
34870
  return existing;
34871
+ if (nativeId.startsWith('partition:')) {
34872
+ const pid = nativeId.substring('partition:'.length);
34873
+ const cfg = this.partitionsCfg.find(p => p.id === pid);
34874
+ if (!cfg)
34875
+ return undefined;
34876
+ const dev = new PartitionMqttSecuritySystem(nativeId, cfg, this);
34877
+ this.devices.set(nativeId, dev);
34878
+ return dev;
34879
+ }
34700
34880
  if (nativeId.startsWith('sensor:')) {
34701
34881
  const sid = nativeId.substring('sensor:'.length);
34702
34882
  const cfg = this.sensorsCfg.find(s => s.id === sid);
@@ -34773,6 +34953,27 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34773
34953
  }
34774
34954
  this.migrateBypassFromStorage();
34775
34955
  }
34956
+ loadPartitionsFromStorage() {
34957
+ try {
34958
+ const raw = this.storage.getItem('partitionsJson') || '[]';
34959
+ const parsed = JSON.parse(raw);
34960
+ this.partitionsCfg = (Array.isArray(parsed) ? parsed : [])
34961
+ .filter(x => x && x.id && x.name)
34962
+ .map(x => ({
34963
+ id: String(x.id),
34964
+ name: String(x.name),
34965
+ topicSetTarget: String(x.topicSetTarget || '').trim(),
34966
+ topicGetTarget: String(x.topicGetTarget || '').trim(),
34967
+ topicGetCurrent: String(x.topicGetCurrent || '').trim(),
34968
+ topicTamper: String(x.topicTamper || '').trim(),
34969
+ topicOnline: String(x.topicOnline || '').trim(),
34970
+ }));
34971
+ }
34972
+ catch (e) {
34973
+ this.console.error('Invalid partitionsJson:', e);
34974
+ this.partitionsCfg = [];
34975
+ }
34976
+ }
34776
34977
  migrateBypassFromStorage() {
34777
34978
  try {
34778
34979
  const raw = this.storage.getItem('bypassJson') || '[]';
@@ -34832,6 +35033,20 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34832
35033
  async discoverDevices() {
34833
35034
  // 1) manifest
34834
35035
  const manifests = [];
35036
+ for (const cfg of this.partitionsCfg) {
35037
+ const interfaces = [
35038
+ sdk_1.ScryptedInterface.SecuritySystem,
35039
+ sdk_1.ScryptedInterface.Online,
35040
+ ];
35041
+ if (cfg.topicTamper)
35042
+ interfaces.push(sdk_1.ScryptedInterface.TamperSensor);
35043
+ manifests.push({
35044
+ nativeId: `partition:${cfg.id}`,
35045
+ name: cfg.name,
35046
+ type: sdk_1.ScryptedDeviceType.SecuritySystem,
35047
+ interfaces,
35048
+ });
35049
+ }
34835
35050
  for (const cfg of this.sensorsCfg) {
34836
35051
  const nativeId = `sensor:${cfg.id}`;
34837
35052
  const t = cfg.topics || {};
@@ -34875,6 +35090,17 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34875
35090
  }
34876
35091
  }
34877
35092
  // 3) instantiate/update
35093
+ for (const cfg of this.partitionsCfg) {
35094
+ const nativeId = `partition:${cfg.id}`;
35095
+ let dev = this.devices.get(nativeId);
35096
+ if (!dev) {
35097
+ dev = new PartitionMqttSecuritySystem(nativeId, cfg, this);
35098
+ this.devices.set(nativeId, dev);
35099
+ }
35100
+ else {
35101
+ dev.updateConfig(cfg);
35102
+ }
35103
+ }
34878
35104
  for (const cfg of this.sensorsCfg) {
34879
35105
  const nativeId = `sensor:${cfg.id}`;
34880
35106
  let dev = this.devices.get(nativeId);
@@ -34957,6 +35183,12 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
34957
35183
  if (v)
34958
35184
  subs.add(v);
34959
35185
  }
35186
+ // partitions
35187
+ for (const p of this.partitionsCfg) {
35188
+ [p.topicGetTarget, p.topicGetCurrent, p.topicTamper, p.topicOnline]
35189
+ .filter((x) => !!x && String(x).trim().length > 0)
35190
+ .forEach(x => subs.add(String(x)));
35191
+ }
34960
35192
  // sensors
34961
35193
  for (const s of this.sensorsCfg) {
34962
35194
  const t = s.topics || {};
@@ -35020,6 +35252,10 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
35020
35252
  const np = normalize(p);
35021
35253
  if (RUNTIME.logMqttAll)
35022
35254
  this.console.log(`[MQTT] ${topic} -> "${p}"`);
35255
+ for (const dev of this.devices.values()) {
35256
+ if (dev instanceof PartitionMqttSecuritySystem)
35257
+ dev.handleMqtt(topic, payload);
35258
+ }
35023
35259
  // ---- Alarm handling ----
35024
35260
  if (topic === tOnline) {
35025
35261
  if (truthy(np) || np === 'online') {
@@ -35086,8 +35322,11 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
35086
35322
  return;
35087
35323
  }
35088
35324
  // ---- Device dispatch ----
35089
- for (const dev of this.devices.values())
35325
+ for (const dev of this.devices.values()) {
35326
+ if (dev instanceof PartitionMqttSecuritySystem)
35327
+ continue;
35090
35328
  dev.handleMqtt(topic, payload);
35329
+ }
35091
35330
  }
35092
35331
  catch (e) {
35093
35332
  this.console.error('MQTT message handler error', e);
@@ -35101,19 +35340,22 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
35101
35340
  const qos = Math.max(0, Math.min(2, isFinite(qosNum) ? qosNum : 0));
35102
35341
  return { qos, retain };
35103
35342
  }
35104
- publishSetTarget(payload) {
35105
- const topic = this.storage.getItem('topicSetTarget');
35343
+ publishSetTargetTo(topic, payload, label = 'Alarm') {
35106
35344
  if (!topic || !this.client) {
35107
- this.console.warn('topicSetTarget or MQTT not configured.');
35108
- return;
35345
+ this.console.warn(`${label}: target topic or MQTT not configured.`);
35346
+ return false;
35109
35347
  }
35110
35348
  const { qos, retain } = this.getPublishOptions();
35111
35349
  this.client.publish(topic, payload, { qos, retain }, (err) => {
35112
35350
  if (err)
35113
35351
  this.console.error('publish error', err);
35114
35352
  else if (RUNTIME.logSensors)
35115
- this.console.log(`[Alarm] published target "${payload}" to ${topic}`);
35353
+ this.console.log(`[${label}] published target "${payload}" to ${topic}`);
35116
35354
  });
35355
+ return true;
35356
+ }
35357
+ publishSetTarget(payload) {
35358
+ this.publishSetTargetTo(this.storage.getItem('topicSetTarget') || '', payload);
35117
35359
  }
35118
35360
  publishBypass(cfg, payload) {
35119
35361
  const topic = cfg.topics?.control?.trim();
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.98",
3
+ "version": "1.1.0",
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",