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