@rfranzoi/scrypted-mqtt-securitysystem 1.0.39 → 1.0.40
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 +152 -218
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
package/dist/main.nodejs.js
CHANGED
|
@@ -34039,73 +34039,36 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
|
34039
34039
|
console.error = swallow(console.error.bind(console));
|
|
34040
34040
|
console.warn = swallow(console.warn.bind(console));
|
|
34041
34041
|
})();
|
|
34042
|
-
//
|
|
34042
|
+
// Carica lo SDK in runtime (evita ESM per garantire l’ordine col preload)
|
|
34043
34043
|
const sdk = __webpack_require__(/*! @scrypted/sdk */ "./node_modules/@scrypted/sdk/dist/src/index.js");
|
|
34044
|
-
|
|
34044
|
+
// Valori runtime (enum, classi, manager)
|
|
34045
|
+
const { ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, // enum: valori
|
|
34046
|
+
SecuritySystemMode, // enum: valori
|
|
34047
|
+
systemManager, } = sdk;
|
|
34045
34048
|
const mqtt_1 = __importDefault(__webpack_require__(/*! mqtt */ "./node_modules/mqtt/build/index.js"));
|
|
34046
34049
|
/** utils */
|
|
34047
|
-
function normalize(s) { return (s || '').trim().toLowerCase(); }
|
|
34048
34050
|
function truthy(v) {
|
|
34049
34051
|
if (!v)
|
|
34050
34052
|
return false;
|
|
34051
|
-
const s =
|
|
34052
|
-
return s === '1' || s === 'true' || s === 'online' || s === 'yes' || s === 'on' || s === 'ok'
|
|
34053
|
+
const s = v.toString().trim().toLowerCase();
|
|
34054
|
+
return s === '1' || s === 'true' || s === 'online' || s === 'yes' || s === 'on' || s === 'ok';
|
|
34053
34055
|
}
|
|
34054
34056
|
function falsy(v) {
|
|
34055
34057
|
if (!v)
|
|
34056
34058
|
return false;
|
|
34057
|
-
const s =
|
|
34058
|
-
return s === '0' || s === 'false' || s === 'offline' || s === 'no' || s === 'off'
|
|
34059
|
+
const s = v.toString().trim().toLowerCase();
|
|
34060
|
+
return s === '0' || s === 'false' || s === 'offline' || s === 'no' || s === 'off';
|
|
34059
34061
|
}
|
|
34062
|
+
function normalize(s) { return (s || '').trim().toLowerCase(); }
|
|
34060
34063
|
function clamp(n, min, max) { return Math.max(min, Math.min(max, n)); }
|
|
34061
|
-
|
|
34062
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
34063
|
-
}
|
|
34064
|
-
catch {
|
|
34065
|
-
return a === b;
|
|
34066
|
-
} }
|
|
34067
|
-
/** Match topic con wildcard MQTT (+, #) oppure confronto esatto */
|
|
34068
|
-
function topicMatches(topic, pattern) {
|
|
34069
|
-
if (!pattern)
|
|
34070
|
-
return false;
|
|
34071
|
-
if (pattern === topic)
|
|
34072
|
-
return true;
|
|
34073
|
-
if (!pattern.includes('+') && !pattern.includes('#'))
|
|
34074
|
-
return false;
|
|
34075
|
-
// Escapa tutto tranne '/'
|
|
34076
|
-
const esc = pattern.replace(/[-/\\^$*?.()|[\]{}]/g, '\\$&');
|
|
34077
|
-
const rx = '^' + esc
|
|
34078
|
-
.replace(/\\\+/g, '[^/]+') // '+' => un segmento
|
|
34079
|
-
.replace(/\\\#/g, '.+'); // '#" => qualsiasi suffisso
|
|
34080
|
-
try {
|
|
34081
|
-
return new RegExp(rx + '$').test(topic);
|
|
34082
|
-
}
|
|
34083
|
-
catch {
|
|
34084
|
-
return false;
|
|
34085
|
-
}
|
|
34086
|
-
}
|
|
34087
|
-
/** set + emit evento scrypted */
|
|
34088
|
-
function setAndEmit(dev, key, value, iface, log) {
|
|
34089
|
-
if (dev[key] === value)
|
|
34090
|
-
return;
|
|
34091
|
-
dev[key] = value;
|
|
34092
|
-
try {
|
|
34093
|
-
dev.onDeviceEvent?.(iface, value);
|
|
34094
|
-
}
|
|
34095
|
-
catch { }
|
|
34096
|
-
try {
|
|
34097
|
-
if (log)
|
|
34098
|
-
dev.console?.log?.(log);
|
|
34099
|
-
}
|
|
34100
|
-
catch { }
|
|
34101
|
-
}
|
|
34102
|
-
/** Outgoing predefiniti (PAI-like). Chiavi numeriche per compat enum */
|
|
34064
|
+
/** Outgoing predefiniti (PAI-like). Chiavi numeriche per compatibilità enum */
|
|
34103
34065
|
const DEFAULT_OUTGOING = {
|
|
34104
34066
|
[SecuritySystemMode.Disarmed]: 'disarm',
|
|
34105
34067
|
[SecuritySystemMode.HomeArmed]: 'arm_home',
|
|
34106
34068
|
[SecuritySystemMode.AwayArmed]: 'arm_away',
|
|
34107
34069
|
[SecuritySystemMode.NightArmed]: 'arm_night',
|
|
34108
34070
|
};
|
|
34071
|
+
/** Parse incoming payload -> final mode (ignora transitori) */
|
|
34109
34072
|
function payloadToMode(payload) {
|
|
34110
34073
|
if (payload == null)
|
|
34111
34074
|
return;
|
|
@@ -34128,148 +34091,48 @@ class BaseMqttSensor extends ScryptedDeviceBase {
|
|
|
34128
34091
|
this.cfg = cfg;
|
|
34129
34092
|
}
|
|
34130
34093
|
handleMqtt(topic, payload) {
|
|
34131
|
-
const
|
|
34132
|
-
const np = normalize(
|
|
34133
|
-
|
|
34134
|
-
if (topicMatches(topic, this.cfg.topics.online)) {
|
|
34094
|
+
const p = payload?.toString() ?? '';
|
|
34095
|
+
const np = normalize(p);
|
|
34096
|
+
if (topic === this.cfg.topics.online) {
|
|
34135
34097
|
if (truthy(np) || np === 'online')
|
|
34136
|
-
|
|
34137
|
-
|
|
34138
|
-
|
|
34098
|
+
this.online = true;
|
|
34099
|
+
if (falsy(np) || np === 'offline')
|
|
34100
|
+
this.online = false;
|
|
34139
34101
|
}
|
|
34140
|
-
|
|
34141
|
-
if (topicMatches(topic, this.cfg.topics.tamper)) {
|
|
34102
|
+
if (topic === this.cfg.topics.tamper) {
|
|
34142
34103
|
if (truthy(np) || ['tamper', 'intrusion', 'cover', 'motion', 'magnetic'].includes(np)) {
|
|
34143
|
-
|
|
34144
|
-
setAndEmit(this, 'tampered', t, ScryptedInterface.TamperSensor, `[${this.name}] tampered=${t}`);
|
|
34145
|
-
}
|
|
34146
|
-
else if (falsy(np)) {
|
|
34147
|
-
setAndEmit(this, 'tampered', false, ScryptedInterface.TamperSensor, `[${this.name}] tampered=false`);
|
|
34104
|
+
this.tampered = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
|
|
34148
34105
|
}
|
|
34106
|
+
else if (falsy(np))
|
|
34107
|
+
this.tampered = false;
|
|
34149
34108
|
}
|
|
34150
|
-
|
|
34151
|
-
|
|
34152
|
-
const n = clamp(parseFloat(raw), 0, 100);
|
|
34109
|
+
if (topic === this.cfg.topics.batteryLevel) {
|
|
34110
|
+
const n = clamp(parseFloat(p), 0, 100);
|
|
34153
34111
|
if (isFinite(n))
|
|
34154
|
-
|
|
34112
|
+
this.batteryLevel = n;
|
|
34155
34113
|
}
|
|
34156
|
-
else if (
|
|
34157
|
-
|
|
34158
|
-
setAndEmit(this, 'batteryLevel', n, ScryptedInterface.Battery, `[${this.name}] batteryLevel=${n} (lowBattery)`);
|
|
34114
|
+
else if (topic === this.cfg.topics.lowBattery && !this.cfg.topics.batteryLevel) {
|
|
34115
|
+
this.batteryLevel = truthy(np) ? 10 : 100;
|
|
34159
34116
|
}
|
|
34160
|
-
|
|
34161
|
-
this.handlePrimary(topic, np, raw);
|
|
34117
|
+
this.handlePrimary(topic, np, p);
|
|
34162
34118
|
}
|
|
34163
34119
|
}
|
|
34164
|
-
/** === SENSORI: parsing robusto + eventi === */
|
|
34165
34120
|
class ContactMqttSensor extends BaseMqttSensor {
|
|
34166
|
-
handlePrimary(topic, np
|
|
34167
|
-
if (
|
|
34168
|
-
|
|
34169
|
-
let val;
|
|
34170
|
-
// stringhe comuni (True/False compresi via normalize)
|
|
34171
|
-
if (['open', 'opened', '1', 'true', 'on', 'yes'].includes(np))
|
|
34172
|
-
val = true;
|
|
34173
|
-
else if (['closed', 'close', '0', 'false', 'off', 'no', 'shut'].includes(np))
|
|
34174
|
-
val = false;
|
|
34175
|
-
// JSON comuni
|
|
34176
|
-
if (val === undefined) {
|
|
34177
|
-
try {
|
|
34178
|
-
const j = JSON.parse(raw);
|
|
34179
|
-
if (typeof j?.open === 'boolean')
|
|
34180
|
-
val = !!j.open;
|
|
34181
|
-
else if (typeof j?.opened === 'boolean')
|
|
34182
|
-
val = !!j.opened;
|
|
34183
|
-
else if (typeof j?.contact === 'boolean')
|
|
34184
|
-
val = !j.contact; // contact:false => aperto
|
|
34185
|
-
else if (typeof j?.state === 'string') {
|
|
34186
|
-
const s = normalize(j.state);
|
|
34187
|
-
if (s === 'open')
|
|
34188
|
-
val = true;
|
|
34189
|
-
if (s === 'closed')
|
|
34190
|
-
val = false;
|
|
34191
|
-
}
|
|
34192
|
-
}
|
|
34193
|
-
catch { }
|
|
34194
|
-
}
|
|
34195
|
-
if (val !== undefined) {
|
|
34196
|
-
setAndEmit(this, 'entryOpen', val, ScryptedInterface.EntrySensor, `[${this.name}] entryOpen=${val} (${topic})`);
|
|
34197
|
-
}
|
|
34198
|
-
else {
|
|
34199
|
-
this.console?.debug?.(`Contact payload non gestito (${this.cfg.id}) topic=${topic} raw="${raw}"`);
|
|
34200
|
-
}
|
|
34121
|
+
handlePrimary(topic, np) {
|
|
34122
|
+
if (topic === this.cfg.topics.contact)
|
|
34123
|
+
this.entryOpen = truthy(np);
|
|
34201
34124
|
}
|
|
34202
34125
|
}
|
|
34203
34126
|
class MotionMqttSensor extends BaseMqttSensor {
|
|
34204
|
-
handlePrimary(topic, np
|
|
34205
|
-
if (
|
|
34206
|
-
|
|
34207
|
-
let val;
|
|
34208
|
-
if (['motion', 'detected', 'active', '1', 'true', 'on', 'yes'].includes(np))
|
|
34209
|
-
val = true;
|
|
34210
|
-
else if (['clear', 'inactive', 'no_motion', 'none', '0', 'false', 'off', 'no'].includes(np))
|
|
34211
|
-
val = false;
|
|
34212
|
-
if (val === undefined) {
|
|
34213
|
-
try {
|
|
34214
|
-
const j = JSON.parse(raw);
|
|
34215
|
-
if (typeof j?.motion === 'boolean')
|
|
34216
|
-
val = !!j.motion;
|
|
34217
|
-
else if (typeof j?.occupancy === 'boolean')
|
|
34218
|
-
val = !!j.occupancy;
|
|
34219
|
-
else if (typeof j?.presence === 'boolean')
|
|
34220
|
-
val = !!j.presence;
|
|
34221
|
-
else if (typeof j?.state === 'string') {
|
|
34222
|
-
const s = normalize(j.state);
|
|
34223
|
-
if (['on', 'motion', 'detected', 'active'].includes(s))
|
|
34224
|
-
val = true;
|
|
34225
|
-
if (['off', 'clear', 'inactive'].includes(s))
|
|
34226
|
-
val = false;
|
|
34227
|
-
}
|
|
34228
|
-
}
|
|
34229
|
-
catch { }
|
|
34230
|
-
}
|
|
34231
|
-
if (val !== undefined) {
|
|
34232
|
-
setAndEmit(this, 'motionDetected', val, ScryptedInterface.MotionSensor, `[${this.name}] motionDetected=${val} (${topic})`);
|
|
34233
|
-
}
|
|
34234
|
-
else {
|
|
34235
|
-
this.console?.debug?.(`Motion payload non gestito (${this.cfg.id}) topic=${topic} raw="${raw}"`);
|
|
34236
|
-
}
|
|
34127
|
+
handlePrimary(topic, np) {
|
|
34128
|
+
if (topic === this.cfg.topics.motion)
|
|
34129
|
+
this.motionDetected = truthy(np);
|
|
34237
34130
|
}
|
|
34238
34131
|
}
|
|
34239
34132
|
class OccupancyMqttSensor extends BaseMqttSensor {
|
|
34240
|
-
handlePrimary(topic, np
|
|
34241
|
-
if (
|
|
34242
|
-
|
|
34243
|
-
let val;
|
|
34244
|
-
if (['occupied', 'presence', 'present', '1', 'true', 'on', 'yes'].includes(np))
|
|
34245
|
-
val = true;
|
|
34246
|
-
else if (['unoccupied', 'vacant', 'absent', '0', 'false', 'off', 'no', 'clear'].includes(np))
|
|
34247
|
-
val = false;
|
|
34248
|
-
if (val === undefined) {
|
|
34249
|
-
try {
|
|
34250
|
-
const j = JSON.parse(raw);
|
|
34251
|
-
if (typeof j?.occupied === 'boolean')
|
|
34252
|
-
val = !!j.occupied;
|
|
34253
|
-
else if (typeof j?.presence === 'boolean')
|
|
34254
|
-
val = !!j.presence;
|
|
34255
|
-
else if (typeof j?.occupancy === 'boolean')
|
|
34256
|
-
val = !!j.occupancy;
|
|
34257
|
-
else if (typeof j?.state === 'string') {
|
|
34258
|
-
const s = normalize(j.state);
|
|
34259
|
-
if (['occupied', 'presence', 'present', 'on'].includes(s))
|
|
34260
|
-
val = true;
|
|
34261
|
-
if (['vacant', 'absent', 'clear', 'off'].includes(s))
|
|
34262
|
-
val = false;
|
|
34263
|
-
}
|
|
34264
|
-
}
|
|
34265
|
-
catch { }
|
|
34266
|
-
}
|
|
34267
|
-
if (val !== undefined) {
|
|
34268
|
-
setAndEmit(this, 'occupied', val, ScryptedInterface.OccupancySensor, `[${this.name}] occupied=${val} (${topic})`);
|
|
34269
|
-
}
|
|
34270
|
-
else {
|
|
34271
|
-
this.console?.debug?.(`Occupancy payload non gestito (${this.cfg.id}) topic=${topic} raw="${raw}"`);
|
|
34272
|
-
}
|
|
34133
|
+
handlePrimary(topic, np) {
|
|
34134
|
+
if (topic === this.cfg.topics.occupancy)
|
|
34135
|
+
this.occupied = truthy(np);
|
|
34273
34136
|
}
|
|
34274
34137
|
}
|
|
34275
34138
|
/** ----------------- Main Plugin ----------------- */
|
|
@@ -34278,7 +34141,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34278
34141
|
super();
|
|
34279
34142
|
this.sensorsCfg = [];
|
|
34280
34143
|
this.devices = new Map();
|
|
34281
|
-
this.
|
|
34144
|
+
this.triedDiscoveryOnce = false;
|
|
34282
34145
|
// Tipo in UI (best-effort)
|
|
34283
34146
|
setTimeout(() => {
|
|
34284
34147
|
try {
|
|
@@ -34297,9 +34160,12 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34297
34160
|
],
|
|
34298
34161
|
};
|
|
34299
34162
|
this.online = this.online ?? false;
|
|
34163
|
+
// Config sensori e announce
|
|
34300
34164
|
this.loadSensorsFromStorage();
|
|
34301
|
-
this.safeDiscoverSensors();
|
|
34165
|
+
this.safeDiscoverSensors(); // non spamma se deviceManager non c'è
|
|
34166
|
+
// Connect MQTT
|
|
34302
34167
|
this.connectMqtt().catch((e) => this.console.error('MQTT connect error:', e));
|
|
34168
|
+
// Shutdown pulito
|
|
34303
34169
|
try {
|
|
34304
34170
|
process.once('SIGTERM', () => { try {
|
|
34305
34171
|
this.client?.end(true);
|
|
@@ -34340,14 +34206,19 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34340
34206
|
{ group: 'Alarm Topics', key: 'topicOnline', title: 'Get Online (subscribe)', placeholder: 'paradox/interface/availability', value: this.storage.getItem('topicOnline') || '' },
|
|
34341
34207
|
{ group: 'Publish Options', key: 'qos', title: 'QoS', type: 'integer', value: parseInt(this.storage.getItem('qos') || '0') },
|
|
34342
34208
|
{ group: 'Publish Options', key: 'retain', title: 'Retain', type: 'boolean', value: this.storage.getItem('retain') === 'true' },
|
|
34209
|
+
// ---- Danger Zone ----
|
|
34210
|
+
{ group: 'Danger Zone', key: 'danger.resetSensors', title: 'Reset all sensors (wipe)', type: 'boolean', description: 'Cancella tutti i sensori salvati e rimuove i device annunciati.' },
|
|
34211
|
+
{ group: 'Danger Zone', key: 'danger.export', title: 'Export sensors JSON (read-only)', value: JSON.stringify(this.sensorsCfg, null, 2), readonly: true },
|
|
34212
|
+
{ group: 'Danger Zone', key: 'danger.import', title: 'Import sensors JSON (paste & Save)', type: 'string', placeholder: '[]', description: 'Incolla JSON valido per sovrascrivere l’elenco sensori.' },
|
|
34343
34213
|
];
|
|
34344
|
-
// Add Sensor
|
|
34345
|
-
out.push({ group: 'Add Sensor', key: 'new.id', title: 'New Sensor ID', placeholder: 'porta-
|
|
34214
|
+
// ---- Add Sensor
|
|
34215
|
+
out.push({ group: 'Add Sensor', key: 'new.id', title: 'New Sensor ID', placeholder: 'porta-sogg', value: this.storage.getItem('new.id') || '' }, { group: 'Add Sensor', key: 'new.name', title: 'Name', placeholder: 'Porta Soggiorno', 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 e Save per creare.' });
|
|
34216
|
+
// ---- Sensors esistenti
|
|
34346
34217
|
for (const cfg of this.sensorsCfg) {
|
|
34347
34218
|
const gid = `Sensor: ${cfg.name} [${cfg.id}]`;
|
|
34348
|
-
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'] });
|
|
34219
|
+
out.push({ group: gid, key: `sensor.${cfg.id}.id`, title: 'ID (read-only)', value: cfg.id, readonly: true, description: 'L’ID identifica il device. Evita spazi/puntini.' }, { 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'] }, { group: gid, key: `sensor.${cfg.id}.renameTo`, title: 'Clone/Rename → New ID', placeholder: 'es. porta-sogg', description: 'Compila e Save per clonare con nuovo ID e rimuovere il vecchio.' });
|
|
34349
34220
|
if (cfg.kind === 'contact')
|
|
34350
|
-
out.push({ group: gid, key: `sensor.${cfg.id}.topic.contact`, title: 'Contact State Topic', value: cfg.topics.contact || ''
|
|
34221
|
+
out.push({ group: gid, key: `sensor.${cfg.id}.topic.contact`, title: 'Contact State Topic', value: cfg.topics.contact || '' });
|
|
34351
34222
|
else if (cfg.kind === 'motion')
|
|
34352
34223
|
out.push({ group: gid, key: `sensor.${cfg.id}.topic.motion`, title: 'Motion Detected Topic', value: cfg.topics.motion || '' });
|
|
34353
34224
|
else
|
|
@@ -34358,6 +34229,46 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34358
34229
|
}
|
|
34359
34230
|
async putSetting(key, value) {
|
|
34360
34231
|
this.storage.setItem(key, String(value));
|
|
34232
|
+
// ---- Danger Zone: reset all ----
|
|
34233
|
+
if (key === 'danger.resetSensors' && String(value) === 'true') {
|
|
34234
|
+
this.storage.removeItem('danger.resetSensors');
|
|
34235
|
+
// rimuovi device annunciati
|
|
34236
|
+
try {
|
|
34237
|
+
const dm = sdk?.deviceManager;
|
|
34238
|
+
for (const nativeId of Array.from(this.devices.keys())) {
|
|
34239
|
+
try {
|
|
34240
|
+
dm?.onDeviceRemoved?.(nativeId);
|
|
34241
|
+
}
|
|
34242
|
+
catch { }
|
|
34243
|
+
}
|
|
34244
|
+
}
|
|
34245
|
+
catch { }
|
|
34246
|
+
this.devices.clear();
|
|
34247
|
+
this.sensorsCfg = [];
|
|
34248
|
+
this.saveSensorsToStorage();
|
|
34249
|
+
this.safeDiscoverSensors(true);
|
|
34250
|
+
await this.connectMqtt(true);
|
|
34251
|
+
return;
|
|
34252
|
+
}
|
|
34253
|
+
// ---- Danger Zone: import JSON ----
|
|
34254
|
+
if (key === 'danger.import') {
|
|
34255
|
+
try {
|
|
34256
|
+
const parsed = JSON.parse(String(value) || '[]');
|
|
34257
|
+
if (!Array.isArray(parsed))
|
|
34258
|
+
throw new Error('JSON non è un array.');
|
|
34259
|
+
// sanifica
|
|
34260
|
+
this.sensorsCfg = (parsed || []).filter(x => x && x.id && x.name && x.kind && x.topics);
|
|
34261
|
+
this.saveSensorsToStorage();
|
|
34262
|
+
this.storage.removeItem('danger.import');
|
|
34263
|
+
this.safeDiscoverSensors(true);
|
|
34264
|
+
await this.connectMqtt(true);
|
|
34265
|
+
}
|
|
34266
|
+
catch (e) {
|
|
34267
|
+
this.console.error('Import JSON error:', e);
|
|
34268
|
+
}
|
|
34269
|
+
return;
|
|
34270
|
+
}
|
|
34271
|
+
// ---- Add Sensor ----
|
|
34361
34272
|
if (key === 'new.create' && String(value) === 'true') {
|
|
34362
34273
|
const id = (this.storage.getItem('new.id') || '').trim();
|
|
34363
34274
|
const name = (this.storage.getItem('new.name') || '').trim() || id;
|
|
@@ -34380,6 +34291,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34380
34291
|
await this.connectMqtt(true);
|
|
34381
34292
|
return;
|
|
34382
34293
|
}
|
|
34294
|
+
// ---- Edit/Remove/Rename sensore ----
|
|
34383
34295
|
const m = key.match(/^sensor\.([^\.]+)\.(.+)$/);
|
|
34384
34296
|
if (m) {
|
|
34385
34297
|
const sid = m[1];
|
|
@@ -34389,6 +34301,30 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34389
34301
|
this.console.warn('putSetting: sensor non trovato', sid);
|
|
34390
34302
|
return;
|
|
34391
34303
|
}
|
|
34304
|
+
// rename/clone
|
|
34305
|
+
if (prop === 'renameTo') {
|
|
34306
|
+
const newId = String(value).trim();
|
|
34307
|
+
this.storage.removeItem(key);
|
|
34308
|
+
if (!newId)
|
|
34309
|
+
return;
|
|
34310
|
+
if (this.sensorsCfg.find(s => s.id === newId)) {
|
|
34311
|
+
this.console.warn('renameTo: ID già esistente:', newId);
|
|
34312
|
+
return;
|
|
34313
|
+
}
|
|
34314
|
+
const cloned = { ...cfg, id: newId, name: cfg.name };
|
|
34315
|
+
this.sensorsCfg.push(cloned);
|
|
34316
|
+
// rimuovi vecchio
|
|
34317
|
+
this.sensorsCfg = this.sensorsCfg.filter(s => s !== cfg);
|
|
34318
|
+
try {
|
|
34319
|
+
sdk?.deviceManager?.onDeviceRemoved?.(`sensor:${sid}`);
|
|
34320
|
+
}
|
|
34321
|
+
catch { }
|
|
34322
|
+
this.saveSensorsToStorage();
|
|
34323
|
+
this.safeDiscoverSensors(true);
|
|
34324
|
+
await this.connectMqtt(true);
|
|
34325
|
+
return;
|
|
34326
|
+
}
|
|
34327
|
+
// remove
|
|
34392
34328
|
if (prop === 'remove' && String(value) === 'true') {
|
|
34393
34329
|
this.sensorsCfg = this.sensorsCfg.filter(s => s.id !== sid);
|
|
34394
34330
|
this.saveSensorsToStorage();
|
|
@@ -34401,6 +34337,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34401
34337
|
await this.connectMqtt(true);
|
|
34402
34338
|
return;
|
|
34403
34339
|
}
|
|
34340
|
+
// edit
|
|
34404
34341
|
if (prop === 'name')
|
|
34405
34342
|
cfg.name = String(value);
|
|
34406
34343
|
else if (prop === 'kind')
|
|
@@ -34414,6 +34351,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34414
34351
|
await this.connectMqtt(true);
|
|
34415
34352
|
return;
|
|
34416
34353
|
}
|
|
34354
|
+
// Altre impostazioni: riconnetti
|
|
34417
34355
|
if (key === 'sensorsJson') {
|
|
34418
34356
|
this.loadSensorsFromStorage();
|
|
34419
34357
|
this.safeDiscoverSensors(true);
|
|
@@ -34450,21 +34388,22 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34450
34388
|
this.sensorsCfg = [];
|
|
34451
34389
|
}
|
|
34452
34390
|
}
|
|
34391
|
+
/** Annuncia i sensori SOLO se deviceManager è pronto. */
|
|
34453
34392
|
safeDiscoverSensors(triggeredByChange = false) {
|
|
34454
34393
|
const dmAny = sdk?.deviceManager;
|
|
34455
34394
|
if (!dmAny) {
|
|
34456
|
-
if (!this.
|
|
34395
|
+
if (!this.triedDiscoveryOnce) {
|
|
34457
34396
|
this.console.log('Device discovery postponed: deviceManager not ready yet.');
|
|
34458
|
-
this.
|
|
34397
|
+
this.triedDiscoveryOnce = true;
|
|
34459
34398
|
}
|
|
34460
34399
|
return;
|
|
34461
34400
|
}
|
|
34462
|
-
this.
|
|
34401
|
+
this.triedDiscoveryOnce = false;
|
|
34463
34402
|
this.discoverSensors(dmAny);
|
|
34464
|
-
if (triggeredByChange)
|
|
34465
|
-
this.console.log('Sensors discovered/updated.');
|
|
34466
34403
|
}
|
|
34404
|
+
/** discoverSensors con deviceManager garantito */
|
|
34467
34405
|
discoverSensors(dmAny) {
|
|
34406
|
+
// 1) Manifests
|
|
34468
34407
|
const manifests = this.sensorsCfg.map(cfg => {
|
|
34469
34408
|
const nativeId = `sensor:${cfg.id}`;
|
|
34470
34409
|
const t = cfg.topics || {};
|
|
@@ -34481,11 +34420,15 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34481
34420
|
interfaces.push(ScryptedInterface.Battery);
|
|
34482
34421
|
return { nativeId, name: cfg.name, type: ScryptedDeviceType.Sensor, interfaces };
|
|
34483
34422
|
});
|
|
34484
|
-
|
|
34423
|
+
// 2) Annuncio
|
|
34424
|
+
if (typeof dmAny.onDevicesChanged === 'function') {
|
|
34485
34425
|
dmAny.onDevicesChanged({ devices: manifests });
|
|
34486
|
-
|
|
34426
|
+
}
|
|
34427
|
+
else if (typeof dmAny.onDeviceDiscovered === 'function') {
|
|
34487
34428
|
for (const m of manifests)
|
|
34488
34429
|
dmAny.onDeviceDiscovered(m);
|
|
34430
|
+
}
|
|
34431
|
+
// 3) Istanzia/aggiorna
|
|
34489
34432
|
for (const cfg of this.sensorsCfg) {
|
|
34490
34433
|
const nativeId = `sensor:${cfg.id}`;
|
|
34491
34434
|
let dev = this.devices.get(nativeId);
|
|
@@ -34503,8 +34446,9 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34503
34446
|
}
|
|
34504
34447
|
const hasBattery = !!(cfg.topics.batteryLevel || cfg.topics.lowBattery);
|
|
34505
34448
|
if (hasBattery && dev.batteryLevel === undefined)
|
|
34506
|
-
|
|
34449
|
+
dev.batteryLevel = 100;
|
|
34507
34450
|
}
|
|
34451
|
+
// 4) Cleanup
|
|
34508
34452
|
const announced = new Set(manifests.map(m => m.nativeId));
|
|
34509
34453
|
for (const [nativeId] of this.devices) {
|
|
34510
34454
|
if (!announced.has(nativeId)) {
|
|
@@ -34567,41 +34511,39 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34567
34511
|
const tOnline = this.storage.getItem('topicOnline') || '';
|
|
34568
34512
|
client.on('connect', () => {
|
|
34569
34513
|
this.console.log('MQTT connected');
|
|
34570
|
-
|
|
34514
|
+
this.online = true;
|
|
34571
34515
|
if (subs.length)
|
|
34572
34516
|
client.subscribe(subs, { qos: 0 }, (err) => { if (err)
|
|
34573
34517
|
this.console.error('subscribe error', err); });
|
|
34518
|
+
// Al primo connect riprova ad annunciare i sensori
|
|
34574
34519
|
this.safeDiscoverSensors(true);
|
|
34575
34520
|
});
|
|
34576
34521
|
client.on('reconnect', () => this.console.log('MQTT reconnecting...'));
|
|
34577
|
-
client.on('close', () => { this.console.log('MQTT closed');
|
|
34522
|
+
client.on('close', () => { this.console.log('MQTT closed'); this.online = false; });
|
|
34578
34523
|
client.on('error', (e) => { this.console.error('MQTT error', e); });
|
|
34579
34524
|
client.on('message', (topic, payload) => {
|
|
34580
34525
|
try {
|
|
34581
|
-
const
|
|
34582
|
-
const np = normalize(
|
|
34583
|
-
if (
|
|
34526
|
+
const p = payload?.toString() ?? '';
|
|
34527
|
+
const np = normalize(p);
|
|
34528
|
+
if (topic === tOnline) {
|
|
34584
34529
|
if (truthy(np) || np === 'online')
|
|
34585
|
-
|
|
34586
|
-
|
|
34587
|
-
|
|
34530
|
+
this.online = true;
|
|
34531
|
+
if (falsy(np) || np === 'offline')
|
|
34532
|
+
this.online = false;
|
|
34588
34533
|
return;
|
|
34589
34534
|
}
|
|
34590
|
-
if (
|
|
34591
|
-
if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np))
|
|
34592
|
-
|
|
34593
|
-
|
|
34594
|
-
|
|
34595
|
-
else if (falsy(np)) {
|
|
34596
|
-
setAndEmit(this, 'tampered', false, ScryptedInterface.TamperSensor, `[Alarm] tampered=false (${topic})`);
|
|
34597
|
-
}
|
|
34535
|
+
if (topic === tTamper) {
|
|
34536
|
+
if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np))
|
|
34537
|
+
this.tampered = ['cover', 'intrusion'].find(x => x === np) || true;
|
|
34538
|
+
else if (falsy(np))
|
|
34539
|
+
this.tampered = false;
|
|
34598
34540
|
return;
|
|
34599
34541
|
}
|
|
34600
|
-
if (
|
|
34542
|
+
if (topic === tCurrent) {
|
|
34601
34543
|
const mode = payloadToMode(payload);
|
|
34602
34544
|
const isAlarm = ['alarm', 'triggered'].includes(np);
|
|
34603
34545
|
const current = this.securitySystemState || { mode: SecuritySystemMode.Disarmed };
|
|
34604
|
-
|
|
34546
|
+
this.securitySystemState = {
|
|
34605
34547
|
mode: mode ?? current.mode,
|
|
34606
34548
|
supportedModes: current.supportedModes ?? [
|
|
34607
34549
|
SecuritySystemMode.Disarmed,
|
|
@@ -34611,23 +34553,15 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34611
34553
|
],
|
|
34612
34554
|
triggered: isAlarm || undefined,
|
|
34613
34555
|
};
|
|
34614
|
-
if (!deepEqual(this.securitySystemState, newState)) {
|
|
34615
|
-
this.securitySystemState = newState;
|
|
34616
|
-
try {
|
|
34617
|
-
this.onDeviceEvent?.(ScryptedInterface.SecuritySystem, newState);
|
|
34618
|
-
}
|
|
34619
|
-
catch { }
|
|
34620
|
-
this.console.log(`[Alarm] currentState=${JSON.stringify(newState)} (${topic})`);
|
|
34621
|
-
}
|
|
34622
34556
|
return;
|
|
34623
34557
|
}
|
|
34624
|
-
if (
|
|
34558
|
+
if (topic === tTarget) {
|
|
34625
34559
|
this.pendingTarget = payloadToMode(payload);
|
|
34626
|
-
this.console.log(
|
|
34560
|
+
this.console.log('Target state reported:', p, '->', this.pendingTarget);
|
|
34627
34561
|
return;
|
|
34628
34562
|
}
|
|
34629
|
-
//
|
|
34630
|
-
if (this.
|
|
34563
|
+
// Dispatch ai sensori + eventuale discover ritardato
|
|
34564
|
+
if (this.triedDiscoveryOnce)
|
|
34631
34565
|
this.safeDiscoverSensors(true);
|
|
34632
34566
|
for (const dev of this.devices.values())
|
|
34633
34567
|
dev.handleMqtt(topic, payload);
|
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED