@rfranzoi/scrypted-mqtt-securitysystem 1.0.34 → 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 +98 -143
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
package/dist/main.nodejs.js
CHANGED
|
@@ -34028,23 +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,
|
|
34045
|
+
// Valori runtime dal modulo SDK
|
|
34046
|
+
const { ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, // enum (valori)
|
|
34047
|
+
SecuritySystemMode, // enum (valori)
|
|
34048
|
+
systemManager, } = sdk;
|
|
34048
34049
|
const mqtt_1 = __importDefault(__webpack_require__(/*! mqtt */ "./node_modules/mqtt/build/index.js"));
|
|
34049
34050
|
/** utils */
|
|
34050
34051
|
function truthy(v) {
|
|
@@ -34059,21 +34060,16 @@ function falsy(v) {
|
|
|
34059
34060
|
const s = v.toString().trim().toLowerCase();
|
|
34060
34061
|
return s === '0' || s === 'false' || s === 'offline' || s === 'no' || s === 'off';
|
|
34061
34062
|
}
|
|
34062
|
-
function normalize(s) {
|
|
34063
|
-
|
|
34064
|
-
|
|
34065
|
-
function clamp(n, min, max) {
|
|
34066
|
-
return Math.max(min, Math.min(max, n));
|
|
34067
|
-
}
|
|
34068
|
-
/** SecuritySystem outgoing defaults (PAI-like)
|
|
34069
|
-
* 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 */
|
|
34070
34066
|
const DEFAULT_OUTGOING = {
|
|
34071
34067
|
[SecuritySystemMode.Disarmed]: 'disarm',
|
|
34072
34068
|
[SecuritySystemMode.HomeArmed]: 'arm_home',
|
|
34073
34069
|
[SecuritySystemMode.AwayArmed]: 'arm_away',
|
|
34074
34070
|
[SecuritySystemMode.NightArmed]: 'arm_night',
|
|
34075
34071
|
};
|
|
34076
|
-
/** Parse incoming payload -> final mode (
|
|
34072
|
+
/** Parse incoming payload -> final mode (ignora transitori) */
|
|
34077
34073
|
function payloadToMode(payload) {
|
|
34078
34074
|
if (payload == null)
|
|
34079
34075
|
return;
|
|
@@ -34086,7 +34082,6 @@ function payloadToMode(payload) {
|
|
|
34086
34082
|
return SecuritySystemMode.AwayArmed;
|
|
34087
34083
|
if (['arm_night', 'night', 'armed_night', 'sleep', 'arm_sleep', 'armed_sleep'].includes(p))
|
|
34088
34084
|
return SecuritySystemMode.NightArmed;
|
|
34089
|
-
// transitori: non cambiano il mode
|
|
34090
34085
|
if (['entry_delay', 'exit_delay', 'pending', 'arming', 'disarming'].includes(p))
|
|
34091
34086
|
return undefined;
|
|
34092
34087
|
return undefined;
|
|
@@ -34096,76 +34091,67 @@ class BaseMqttSensor extends ScryptedDeviceBase {
|
|
|
34096
34091
|
super(nativeId);
|
|
34097
34092
|
this.cfg = cfg;
|
|
34098
34093
|
}
|
|
34099
|
-
/** Called by parent on each MQTT message */
|
|
34100
34094
|
handleMqtt(topic, payload) {
|
|
34101
34095
|
const p = payload?.toString() ?? '';
|
|
34102
34096
|
const np = normalize(p);
|
|
34103
|
-
// online
|
|
34104
34097
|
if (topic === this.cfg.topics.online) {
|
|
34105
34098
|
if (truthy(np) || np === 'online')
|
|
34106
34099
|
this.online = true;
|
|
34107
34100
|
if (falsy(np) || np === 'offline')
|
|
34108
34101
|
this.online = false;
|
|
34109
34102
|
}
|
|
34110
|
-
// tamper
|
|
34111
34103
|
if (topic === this.cfg.topics.tamper) {
|
|
34112
34104
|
if (truthy(np) || ['tamper', 'intrusion', 'cover', 'motion', 'magnetic'].includes(np)) {
|
|
34113
34105
|
this.tampered = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
|
|
34114
34106
|
}
|
|
34115
|
-
else if (falsy(np))
|
|
34107
|
+
else if (falsy(np))
|
|
34116
34108
|
this.tampered = false;
|
|
34117
|
-
}
|
|
34118
34109
|
}
|
|
34119
|
-
// battery
|
|
34120
34110
|
if (topic === this.cfg.topics.batteryLevel) {
|
|
34121
34111
|
const n = clamp(parseFloat(p), 0, 100);
|
|
34122
34112
|
if (isFinite(n))
|
|
34123
34113
|
this.batteryLevel = n;
|
|
34124
34114
|
}
|
|
34125
34115
|
else if (topic === this.cfg.topics.lowBattery && !this.cfg.topics.batteryLevel) {
|
|
34126
|
-
// se abbiamo SOLO lowBattery (bool):
|
|
34127
34116
|
this.batteryLevel = truthy(np) ? 10 : 100;
|
|
34128
34117
|
}
|
|
34129
|
-
// primary handled by subclasses
|
|
34130
34118
|
this.handlePrimary(topic, np, p);
|
|
34131
34119
|
}
|
|
34132
34120
|
}
|
|
34133
34121
|
class ContactMqttSensor extends BaseMqttSensor {
|
|
34134
34122
|
handlePrimary(topic, np) {
|
|
34135
|
-
if (topic === this.cfg.topics.contact)
|
|
34123
|
+
if (topic === this.cfg.topics.contact)
|
|
34136
34124
|
this.entryOpen = truthy(np);
|
|
34137
|
-
}
|
|
34138
34125
|
}
|
|
34139
34126
|
}
|
|
34140
34127
|
class MotionMqttSensor extends BaseMqttSensor {
|
|
34141
34128
|
handlePrimary(topic, np) {
|
|
34142
|
-
if (topic === this.cfg.topics.motion)
|
|
34129
|
+
if (topic === this.cfg.topics.motion)
|
|
34143
34130
|
this.motionDetected = truthy(np);
|
|
34144
|
-
}
|
|
34145
34131
|
}
|
|
34146
34132
|
}
|
|
34147
34133
|
class OccupancyMqttSensor extends BaseMqttSensor {
|
|
34148
34134
|
handlePrimary(topic, np) {
|
|
34149
|
-
if (topic === this.cfg.topics.occupancy)
|
|
34135
|
+
if (topic === this.cfg.topics.occupancy)
|
|
34150
34136
|
this.occupied = truthy(np);
|
|
34151
|
-
}
|
|
34152
34137
|
}
|
|
34153
34138
|
}
|
|
34154
34139
|
/** ----------------- Main Plugin ----------------- */
|
|
34155
34140
|
class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
34156
34141
|
constructor() {
|
|
34157
34142
|
super();
|
|
34158
|
-
// sensor management
|
|
34159
34143
|
this.sensorsCfg = [];
|
|
34160
34144
|
this.devices = new Map();
|
|
34161
|
-
//
|
|
34145
|
+
// evitiamo spam: ricordiamo se abbiamo già provato ad annunciare
|
|
34146
|
+
this.triedDiscoveryOnce = false;
|
|
34147
|
+
// Tipo in UI (best-effort)
|
|
34162
34148
|
setTimeout(() => {
|
|
34163
34149
|
try {
|
|
34164
34150
|
systemManager.getDeviceById(this.id)?.setType?.(ScryptedDeviceType.SecuritySystem);
|
|
34165
34151
|
}
|
|
34166
34152
|
catch { }
|
|
34167
34153
|
});
|
|
34168
|
-
//
|
|
34154
|
+
// Stato di default
|
|
34169
34155
|
this.securitySystemState = this.securitySystemState || {
|
|
34170
34156
|
mode: SecuritySystemMode.Disarmed,
|
|
34171
34157
|
supportedModes: [
|
|
@@ -34176,12 +34162,12 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34176
34162
|
],
|
|
34177
34163
|
};
|
|
34178
34164
|
this.online = this.online ?? false;
|
|
34179
|
-
//
|
|
34165
|
+
// Config sensori e (tentativo) announce
|
|
34180
34166
|
this.loadSensorsFromStorage();
|
|
34181
|
-
this.
|
|
34182
|
-
// Connect
|
|
34167
|
+
this.safeDiscoverSensors(); // non spamma se deviceManager non c'è
|
|
34168
|
+
// Connect MQTT
|
|
34183
34169
|
this.connectMqtt().catch((e) => this.console.error('MQTT connect error:', e));
|
|
34184
|
-
//
|
|
34170
|
+
// Shutdown pulito
|
|
34185
34171
|
try {
|
|
34186
34172
|
process.once('SIGTERM', () => { try {
|
|
34187
34173
|
this.client?.end(true);
|
|
@@ -34198,7 +34184,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34198
34184
|
}
|
|
34199
34185
|
catch { }
|
|
34200
34186
|
}
|
|
34201
|
-
|
|
34187
|
+
/** ---- Settings ---- */
|
|
34202
34188
|
saveSensorsToStorage() {
|
|
34203
34189
|
try {
|
|
34204
34190
|
this.storage.setItem('sensorsJson', JSON.stringify(this.sensorsCfg));
|
|
@@ -34207,17 +34193,14 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34207
34193
|
this.console.error('saveSensorsToStorage error', e);
|
|
34208
34194
|
}
|
|
34209
34195
|
}
|
|
34210
|
-
/** ---- Settings UI ---- */
|
|
34211
34196
|
async getSettings() {
|
|
34212
34197
|
const out = [
|
|
34213
|
-
// MQTT Core
|
|
34214
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' },
|
|
34215
34199
|
{ group: 'MQTT', key: 'username', title: 'Username', type: 'string', value: this.storage.getItem('username') || '' },
|
|
34216
34200
|
{ group: 'MQTT', key: 'password', title: 'Password', type: 'password', value: this.storage.getItem('password') || '' },
|
|
34217
34201
|
{ group: 'MQTT', key: 'clientId', title: 'Client ID', placeholder: 'scrypted-paradox', value: this.storage.getItem('clientId') || 'scrypted-paradox' },
|
|
34218
34202
|
{ group: 'MQTT', key: 'tls', title: 'Use TLS', type: 'boolean', value: this.storage.getItem('tls') === 'true' },
|
|
34219
34203
|
{ group: 'MQTT', key: 'rejectUnauthorized', title: 'Reject Unauthorized (TLS)', type: 'boolean', value: this.storage.getItem('rejectUnauthorized') !== 'false', description: 'Disattiva solo con broker self-signed.' },
|
|
34220
|
-
// Alarm Topics
|
|
34221
34204
|
{ group: 'Alarm Topics', key: 'topicSetTarget', title: 'Set Target State (publish)', placeholder: 'paradox/control/partitions/Area_1', value: this.storage.getItem('topicSetTarget') || '' },
|
|
34222
34205
|
{ group: 'Alarm Topics', key: 'topicGetTarget', title: 'Get Target State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/target_state', value: this.storage.getItem('topicGetTarget') || '' },
|
|
34223
34206
|
{ group: 'Alarm Topics', key: 'topicGetCurrent', title: 'Get Current State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/current_state', value: this.storage.getItem('topicGetCurrent') || '' },
|
|
@@ -34226,31 +34209,24 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34226
34209
|
{ group: 'Publish Options', key: 'qos', title: 'QoS', type: 'integer', value: parseInt(this.storage.getItem('qos') || '0') },
|
|
34227
34210
|
{ group: 'Publish Options', key: 'retain', title: 'Retain', type: 'boolean', value: this.storage.getItem('retain') === 'true' },
|
|
34228
34211
|
];
|
|
34229
|
-
//
|
|
34230
|
-
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: '
|
|
34231
|
-
//
|
|
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
|
|
34232
34215
|
for (const cfg of this.sensorsCfg) {
|
|
34233
34216
|
const gid = `Sensor: ${cfg.name} [${cfg.id}]`;
|
|
34234
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'] });
|
|
34235
|
-
|
|
34236
|
-
|
|
34237
|
-
|
|
34238
|
-
|
|
34239
|
-
else
|
|
34240
|
-
out.push({ group: gid, key: `sensor.${cfg.id}.topic.
|
|
34241
|
-
}
|
|
34242
|
-
else {
|
|
34243
|
-
out.push({ group: gid, key: `sensor.${cfg.id}.topic.occupancy`, title: 'Occupancy Detected Topic', value: cfg.topics.occupancy || '', placeholder: 'paradox/states/zones/XYZ/open' });
|
|
34244
|
-
}
|
|
34245
|
-
// 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 || '' });
|
|
34246
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' });
|
|
34247
34225
|
}
|
|
34248
34226
|
return out;
|
|
34249
34227
|
}
|
|
34250
34228
|
async putSetting(key, value) {
|
|
34251
|
-
// salva sempre nella storage la value del campo (così resta in UI)
|
|
34252
34229
|
this.storage.setItem(key, String(value));
|
|
34253
|
-
// --- Add Sensor workflow ---
|
|
34254
34230
|
if (key === 'new.create' && String(value) === 'true') {
|
|
34255
34231
|
const id = (this.storage.getItem('new.id') || '').trim();
|
|
34256
34232
|
const name = (this.storage.getItem('new.name') || '').trim() || id;
|
|
@@ -34265,16 +34241,14 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34265
34241
|
}
|
|
34266
34242
|
this.sensorsCfg.push({ id, name, kind, topics: {} });
|
|
34267
34243
|
this.saveSensorsToStorage();
|
|
34268
|
-
// pulisci i campi "new.*"
|
|
34269
34244
|
this.storage.removeItem('new.id');
|
|
34270
34245
|
this.storage.removeItem('new.name');
|
|
34271
34246
|
this.storage.removeItem('new.kind');
|
|
34272
34247
|
this.storage.removeItem('new.create');
|
|
34273
|
-
|
|
34248
|
+
this.safeDiscoverSensors(true);
|
|
34274
34249
|
await this.connectMqtt(true);
|
|
34275
34250
|
return;
|
|
34276
34251
|
}
|
|
34277
|
-
// --- Edit/Remove sensore esistente ---
|
|
34278
34252
|
const m = key.match(/^sensor\.([^\.]+)\.(.+)$/);
|
|
34279
34253
|
if (m) {
|
|
34280
34254
|
const sid = m[1];
|
|
@@ -34285,40 +34259,33 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34285
34259
|
return;
|
|
34286
34260
|
}
|
|
34287
34261
|
if (prop === 'remove' && String(value) === 'true') {
|
|
34288
|
-
// elimina
|
|
34289
34262
|
this.sensorsCfg = this.sensorsCfg.filter(s => s.id !== sid);
|
|
34290
34263
|
this.saveSensorsToStorage();
|
|
34291
34264
|
try {
|
|
34292
|
-
|
|
34293
|
-
deviceManager.onDeviceRemoved?.(`sensor:${sid}`);
|
|
34265
|
+
sdk?.deviceManager?.onDeviceRemoved?.(`sensor:${sid}`);
|
|
34294
34266
|
}
|
|
34295
34267
|
catch { }
|
|
34296
|
-
// pulisci flag
|
|
34297
34268
|
this.storage.removeItem(key);
|
|
34298
|
-
|
|
34269
|
+
this.safeDiscoverSensors(true);
|
|
34299
34270
|
await this.connectMqtt(true);
|
|
34300
34271
|
return;
|
|
34301
34272
|
}
|
|
34302
|
-
if (prop === 'name')
|
|
34273
|
+
if (prop === 'name')
|
|
34303
34274
|
cfg.name = String(value);
|
|
34304
|
-
|
|
34305
|
-
else if (prop === 'kind') {
|
|
34275
|
+
else if (prop === 'kind')
|
|
34306
34276
|
cfg.kind = String(value);
|
|
34307
|
-
}
|
|
34308
34277
|
else if (prop.startsWith('topic.')) {
|
|
34309
34278
|
const tk = prop.substring('topic.'.length);
|
|
34310
34279
|
cfg.topics[tk] = String(value).trim();
|
|
34311
34280
|
}
|
|
34312
34281
|
this.saveSensorsToStorage();
|
|
34313
|
-
|
|
34282
|
+
this.safeDiscoverSensors(true);
|
|
34314
34283
|
await this.connectMqtt(true);
|
|
34315
34284
|
return;
|
|
34316
34285
|
}
|
|
34317
|
-
// --- Altro (MQTT / Alarm settings) ---
|
|
34318
34286
|
if (key === 'sensorsJson') {
|
|
34319
|
-
// non più mostrato, ma se presente da vecchie versioni
|
|
34320
34287
|
this.loadSensorsFromStorage();
|
|
34321
|
-
|
|
34288
|
+
this.safeDiscoverSensors(true);
|
|
34322
34289
|
await this.connectMqtt(true);
|
|
34323
34290
|
}
|
|
34324
34291
|
else {
|
|
@@ -34326,17 +34293,14 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34326
34293
|
}
|
|
34327
34294
|
}
|
|
34328
34295
|
/** ---- DeviceProvider ---- */
|
|
34329
|
-
async getDevice(nativeId) {
|
|
34330
|
-
return this.devices.get(nativeId);
|
|
34331
|
-
}
|
|
34296
|
+
async getDevice(nativeId) { return this.devices.get(nativeId); }
|
|
34332
34297
|
async releaseDevice(_id, nativeId) {
|
|
34333
34298
|
try {
|
|
34334
34299
|
const dev = this.devices.get(nativeId);
|
|
34335
|
-
if (dev)
|
|
34300
|
+
if (dev)
|
|
34336
34301
|
this.devices.delete(nativeId);
|
|
34337
|
-
}
|
|
34338
34302
|
try {
|
|
34339
|
-
deviceManager
|
|
34303
|
+
sdk?.deviceManager?.onDeviceRemoved?.(nativeId);
|
|
34340
34304
|
}
|
|
34341
34305
|
catch { }
|
|
34342
34306
|
}
|
|
@@ -34348,7 +34312,6 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34348
34312
|
try {
|
|
34349
34313
|
const raw = this.storage.getItem('sensorsJson') || '[]';
|
|
34350
34314
|
const parsed = JSON.parse(raw);
|
|
34351
|
-
// sanitize
|
|
34352
34315
|
this.sensorsCfg = (parsed || []).filter(x => x && x.id && x.name && x.kind && x.topics);
|
|
34353
34316
|
}
|
|
34354
34317
|
catch (e) {
|
|
@@ -34356,42 +34319,50 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34356
34319
|
this.sensorsCfg = [];
|
|
34357
34320
|
}
|
|
34358
34321
|
}
|
|
34359
|
-
/**
|
|
34360
|
-
|
|
34361
|
-
|
|
34322
|
+
/** Annuncia i sensori SOLO se deviceManager è pronto. Niente loop infinito. */
|
|
34323
|
+
safeDiscoverSensors(triggeredByChange = false) {
|
|
34324
|
+
const dmAny = sdk?.deviceManager;
|
|
34325
|
+
if (!dmAny) {
|
|
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).
|
|
34332
|
+
return;
|
|
34333
|
+
}
|
|
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
|
|
34362
34341
|
const manifests = this.sensorsCfg.map(cfg => {
|
|
34363
34342
|
const nativeId = `sensor:${cfg.id}`;
|
|
34364
34343
|
const t = cfg.topics || {};
|
|
34365
34344
|
const interfaces = [ScryptedInterface.Online];
|
|
34366
|
-
// Tamper solo se c'è un topic tamper
|
|
34367
34345
|
if (t.tamper)
|
|
34368
34346
|
interfaces.push(ScryptedInterface.TamperSensor);
|
|
34369
|
-
// Interfaccia primaria
|
|
34370
34347
|
if (cfg.kind === 'contact')
|
|
34371
34348
|
interfaces.unshift(ScryptedInterface.EntrySensor);
|
|
34372
34349
|
else if (cfg.kind === 'motion')
|
|
34373
34350
|
interfaces.unshift(ScryptedInterface.MotionSensor);
|
|
34374
34351
|
else
|
|
34375
34352
|
interfaces.unshift(ScryptedInterface.OccupancySensor);
|
|
34376
|
-
|
|
34377
|
-
if (t.batteryLevel || t.lowBattery) {
|
|
34353
|
+
if (t.batteryLevel || t.lowBattery)
|
|
34378
34354
|
interfaces.push(ScryptedInterface.Battery);
|
|
34379
|
-
}
|
|
34380
34355
|
return { nativeId, name: cfg.name, type: ScryptedDeviceType.Sensor, interfaces };
|
|
34381
34356
|
});
|
|
34382
34357
|
// 2) Annuncio
|
|
34383
|
-
const dmAny = deviceManager;
|
|
34384
34358
|
if (typeof dmAny.onDevicesChanged === 'function') {
|
|
34385
34359
|
dmAny.onDevicesChanged({ devices: manifests });
|
|
34386
|
-
this.console.log('Annunciati (batch):', manifests.map(m => m.nativeId).join(', '));
|
|
34387
34360
|
}
|
|
34388
|
-
else {
|
|
34389
|
-
for (const m of manifests)
|
|
34390
|
-
|
|
34391
|
-
this.console.log('Annunciato:', m.nativeId);
|
|
34392
|
-
}
|
|
34361
|
+
else if (typeof dmAny.onDeviceDiscovered === 'function') {
|
|
34362
|
+
for (const m of manifests)
|
|
34363
|
+
dmAny.onDeviceDiscovered(m);
|
|
34393
34364
|
}
|
|
34394
|
-
// 3) Istanzia/aggiorna
|
|
34365
|
+
// 3) Istanzia/aggiorna
|
|
34395
34366
|
for (const cfg of this.sensorsCfg) {
|
|
34396
34367
|
const nativeId = `sensor:${cfg.id}`;
|
|
34397
34368
|
let dev = this.devices.get(nativeId);
|
|
@@ -34407,20 +34378,17 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34407
34378
|
else {
|
|
34408
34379
|
dev.cfg = cfg;
|
|
34409
34380
|
}
|
|
34410
|
-
// Default “OK” se abbiamo Battery ma nessun valore ancora ricevuto
|
|
34411
34381
|
const hasBattery = !!(cfg.topics.batteryLevel || cfg.topics.lowBattery);
|
|
34412
|
-
if (hasBattery && dev.batteryLevel === undefined)
|
|
34382
|
+
if (hasBattery && dev.batteryLevel === undefined)
|
|
34413
34383
|
dev.batteryLevel = 100;
|
|
34414
|
-
}
|
|
34415
34384
|
}
|
|
34416
|
-
// 4)
|
|
34385
|
+
// 4) Cleanup
|
|
34417
34386
|
const announced = new Set(manifests.map(m => m.nativeId));
|
|
34418
34387
|
for (const [nativeId] of this.devices) {
|
|
34419
34388
|
if (!announced.has(nativeId)) {
|
|
34420
34389
|
try {
|
|
34421
34390
|
this.devices.delete(nativeId);
|
|
34422
|
-
|
|
34423
|
-
this.console.log('Rimosso:', nativeId);
|
|
34391
|
+
dmAny.onDeviceRemoved?.(nativeId);
|
|
34424
34392
|
}
|
|
34425
34393
|
catch { }
|
|
34426
34394
|
}
|
|
@@ -34434,13 +34402,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34434
34402
|
const clientId = this.storage.getItem('clientId') || 'scrypted-paradox';
|
|
34435
34403
|
const tls = this.storage.getItem('tls') === 'true';
|
|
34436
34404
|
const rejectUnauthorized = this.storage.getItem('rejectUnauthorized') !== 'false';
|
|
34437
|
-
const opts = {
|
|
34438
|
-
clientId,
|
|
34439
|
-
username,
|
|
34440
|
-
password,
|
|
34441
|
-
clean: true,
|
|
34442
|
-
reconnectPeriod: 3000,
|
|
34443
|
-
};
|
|
34405
|
+
const opts = { clientId, username, password, clean: true, reconnectPeriod: 3000 };
|
|
34444
34406
|
if (tls) {
|
|
34445
34407
|
opts.protocol = 'mqtts';
|
|
34446
34408
|
opts.rejectUnauthorized = rejectUnauthorized;
|
|
@@ -34449,38 +34411,34 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34449
34411
|
}
|
|
34450
34412
|
collectAllSubscriptions() {
|
|
34451
34413
|
const subs = new Set();
|
|
34452
|
-
// alarm
|
|
34453
34414
|
for (const k of ['topicGetTarget', 'topicGetCurrent', 'topicTamper', 'topicOnline']) {
|
|
34454
34415
|
const v = this.storage.getItem(k);
|
|
34455
34416
|
if (v)
|
|
34456
34417
|
subs.add(v);
|
|
34457
34418
|
}
|
|
34458
|
-
// sensors
|
|
34459
34419
|
for (const s of this.sensorsCfg) {
|
|
34460
34420
|
const t = s.topics || {};
|
|
34461
34421
|
[t.contact, t.motion, t.occupancy, t.batteryLevel, t.lowBattery, t.tamper, t.online]
|
|
34462
|
-
.filter(Boolean)
|
|
34463
|
-
.forEach(x => subs.add(String(x)));
|
|
34422
|
+
.filter(Boolean).forEach(x => subs.add(String(x)));
|
|
34464
34423
|
}
|
|
34465
34424
|
return Array.from(subs);
|
|
34466
34425
|
}
|
|
34467
34426
|
async connectMqtt(_reconnect = false) {
|
|
34468
34427
|
const subs = this.collectAllSubscriptions();
|
|
34469
|
-
if (!subs.length && !this.storage.getItem('topicSetTarget'))
|
|
34428
|
+
if (!subs.length && !this.storage.getItem('topicSetTarget'))
|
|
34470
34429
|
this.console.warn('Configura almeno un topic nelle impostazioni.');
|
|
34471
|
-
}
|
|
34472
34430
|
if (this.client) {
|
|
34473
34431
|
try {
|
|
34474
34432
|
this.client.end(true);
|
|
34475
34433
|
}
|
|
34476
34434
|
catch { }
|
|
34435
|
+
;
|
|
34477
34436
|
this.client = undefined;
|
|
34478
34437
|
}
|
|
34479
34438
|
const { url, opts } = this.getMqttOptions();
|
|
34480
34439
|
this.console.log(`Connecting MQTT ${url} ...`);
|
|
34481
34440
|
const client = mqtt_1.default.connect(url, opts);
|
|
34482
34441
|
this.client = client;
|
|
34483
|
-
// cache alarm topics for fast compare
|
|
34484
34442
|
const tTarget = this.storage.getItem('topicGetTarget') || '';
|
|
34485
34443
|
const tCurrent = this.storage.getItem('topicGetCurrent') || '';
|
|
34486
34444
|
const tTamper = this.storage.getItem('topicTamper') || '';
|
|
@@ -34488,12 +34446,11 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34488
34446
|
client.on('connect', () => {
|
|
34489
34447
|
this.console.log('MQTT connected');
|
|
34490
34448
|
this.online = true;
|
|
34491
|
-
if (subs.length)
|
|
34492
|
-
client.subscribe(subs, { qos: 0 }, (err) => {
|
|
34493
|
-
|
|
34494
|
-
|
|
34495
|
-
|
|
34496
|
-
}
|
|
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);
|
|
34497
34454
|
});
|
|
34498
34455
|
client.on('reconnect', () => this.console.log('MQTT reconnecting...'));
|
|
34499
34456
|
client.on('close', () => { this.console.log('MQTT closed'); this.online = false; });
|
|
@@ -34502,7 +34459,6 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34502
34459
|
try {
|
|
34503
34460
|
const p = payload?.toString() ?? '';
|
|
34504
34461
|
const np = normalize(p);
|
|
34505
|
-
// ---- Alarm handling ----
|
|
34506
34462
|
if (topic === tOnline) {
|
|
34507
34463
|
if (truthy(np) || np === 'online')
|
|
34508
34464
|
this.online = true;
|
|
@@ -34511,19 +34467,17 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34511
34467
|
return;
|
|
34512
34468
|
}
|
|
34513
34469
|
if (topic === tTamper) {
|
|
34514
|
-
if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np))
|
|
34470
|
+
if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np))
|
|
34515
34471
|
this.tampered = ['cover', 'intrusion'].find(x => x === np) || true;
|
|
34516
|
-
|
|
34517
|
-
else if (falsy(np)) {
|
|
34472
|
+
else if (falsy(np))
|
|
34518
34473
|
this.tampered = false;
|
|
34519
|
-
}
|
|
34520
34474
|
return;
|
|
34521
34475
|
}
|
|
34522
34476
|
if (topic === tCurrent) {
|
|
34523
34477
|
const mode = payloadToMode(payload);
|
|
34524
34478
|
const isAlarm = ['alarm', 'triggered'].includes(np);
|
|
34525
34479
|
const current = this.securitySystemState || { mode: SecuritySystemMode.Disarmed };
|
|
34526
|
-
|
|
34480
|
+
this.securitySystemState = {
|
|
34527
34481
|
mode: mode ?? current.mode,
|
|
34528
34482
|
supportedModes: current.supportedModes ?? [
|
|
34529
34483
|
SecuritySystemMode.Disarmed,
|
|
@@ -34533,7 +34487,6 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34533
34487
|
],
|
|
34534
34488
|
triggered: isAlarm || undefined,
|
|
34535
34489
|
};
|
|
34536
|
-
this.securitySystemState = newState;
|
|
34537
34490
|
return;
|
|
34538
34491
|
}
|
|
34539
34492
|
if (topic === tTarget) {
|
|
@@ -34541,10 +34494,12 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34541
34494
|
this.console.log('Target state reported:', p, '->', this.pendingTarget);
|
|
34542
34495
|
return;
|
|
34543
34496
|
}
|
|
34544
|
-
//
|
|
34545
|
-
|
|
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())
|
|
34546
34502
|
dev.handleMqtt(topic, payload);
|
|
34547
|
-
}
|
|
34548
34503
|
}
|
|
34549
34504
|
catch (e) {
|
|
34550
34505
|
this.console.error('MQTT message handler error', e);
|
|
@@ -34569,7 +34524,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34569
34524
|
async armSecuritySystem(mode) {
|
|
34570
34525
|
const payload = this.getOutgoing(mode);
|
|
34571
34526
|
this.console.log('armSecuritySystem', mode, '->', payload);
|
|
34572
|
-
this.pendingTarget = mode;
|
|
34527
|
+
this.pendingTarget = mode;
|
|
34573
34528
|
this.publishSetTarget(payload);
|
|
34574
34529
|
}
|
|
34575
34530
|
async disarmSecuritySystem() {
|
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED