@rfranzoi/scrypted-mqtt-securitysystem 1.0.99 → 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 +17 -0
- package/dist/main.nodejs.js +254 -12
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ 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.
|
|
11
12
|
- Optional per‑sensor **Bypass** switch via MQTT topics.
|
|
12
13
|
- HomeKit: usable through the official Scrypted HomeKit plugin (non-standalone accessory).
|
|
@@ -38,6 +39,22 @@ Then in **Scrypted → Manage Plugins → Install
|
|
|
38
39
|
- **Get Status Tampered (subscribe)**: tamper status.
|
|
39
40
|
- **Get Online (subscribe)**: online/offline indicator of the alarm bridge.
|
|
40
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
|
+
|
|
41
58
|
### Publish Options
|
|
42
59
|
- **QoS** / **Retain**
|
|
43
60
|
|
package/dist/main.nodejs.js
CHANGED
|
@@ -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
|
-
|
|
35105
|
-
const topic = this.storage.getItem('topicSetTarget');
|
|
35343
|
+
publishSetTargetTo(topic, payload, label = 'Alarm') {
|
|
35106
35344
|
if (!topic || !this.client) {
|
|
35107
|
-
this.console.warn(
|
|
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(`[
|
|
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