@rfranzoi/scrypted-mqtt-securitysystem 1.0.37 → 1.0.39
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 +192 -172
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
package/dist/main.nodejs.js
CHANGED
|
@@ -34029,7 +34029,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
34029
34029
|
};
|
|
34030
34030
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
34031
34031
|
// --- Preload: silenzia SOLO il warning facoltativo di sdk.json ---
|
|
34032
|
-
// Copre console.error e console.warn (alcune versioni usano warn).
|
|
34033
34032
|
(() => {
|
|
34034
34033
|
const swallow = (orig) => (...args) => {
|
|
34035
34034
|
const txt = args.map(a => typeof a === 'string' ? a : (a?.message || '')).join(' ');
|
|
@@ -34040,36 +34039,73 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
|
34040
34039
|
console.error = swallow(console.error.bind(console));
|
|
34041
34040
|
console.warn = swallow(console.warn.bind(console));
|
|
34042
34041
|
})();
|
|
34043
|
-
//
|
|
34042
|
+
// Runtime SDK via require (evita esecuzione anticipata del bundler)
|
|
34044
34043
|
const sdk = __webpack_require__(/*! @scrypted/sdk */ "./node_modules/@scrypted/sdk/dist/src/index.js");
|
|
34045
|
-
|
|
34046
|
-
const { ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, // enum (valori)
|
|
34047
|
-
SecuritySystemMode, // enum (valori)
|
|
34048
|
-
systemManager, } = sdk;
|
|
34044
|
+
const { ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, SecuritySystemMode, systemManager, } = sdk;
|
|
34049
34045
|
const mqtt_1 = __importDefault(__webpack_require__(/*! mqtt */ "./node_modules/mqtt/build/index.js"));
|
|
34050
34046
|
/** utils */
|
|
34047
|
+
function normalize(s) { return (s || '').trim().toLowerCase(); }
|
|
34051
34048
|
function truthy(v) {
|
|
34052
34049
|
if (!v)
|
|
34053
34050
|
return false;
|
|
34054
|
-
const s = v
|
|
34055
|
-
return s === '1' || s === 'true' || s === 'online' || s === 'yes' || s === 'on' || s === 'ok';
|
|
34051
|
+
const s = normalize(v);
|
|
34052
|
+
return s === '1' || s === 'true' || s === 'online' || s === 'yes' || s === 'on' || s === 'ok' || s === 'open';
|
|
34056
34053
|
}
|
|
34057
34054
|
function falsy(v) {
|
|
34058
34055
|
if (!v)
|
|
34059
34056
|
return false;
|
|
34060
|
-
const s = v
|
|
34061
|
-
return s === '0' || s === 'false' || s === 'offline' || s === 'no' || s === 'off';
|
|
34057
|
+
const s = normalize(v);
|
|
34058
|
+
return s === '0' || s === 'false' || s === 'offline' || s === 'no' || s === 'off' || s === 'closed' || s === 'close';
|
|
34062
34059
|
}
|
|
34063
|
-
function normalize(s) { return (s || '').trim().toLowerCase(); }
|
|
34064
34060
|
function clamp(n, min, max) { return Math.max(min, Math.min(max, n)); }
|
|
34065
|
-
|
|
34061
|
+
function deepEqual(a, b) { try {
|
|
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 */
|
|
34066
34103
|
const DEFAULT_OUTGOING = {
|
|
34067
34104
|
[SecuritySystemMode.Disarmed]: 'disarm',
|
|
34068
34105
|
[SecuritySystemMode.HomeArmed]: 'arm_home',
|
|
34069
34106
|
[SecuritySystemMode.AwayArmed]: 'arm_away',
|
|
34070
34107
|
[SecuritySystemMode.NightArmed]: 'arm_night',
|
|
34071
34108
|
};
|
|
34072
|
-
/** Parse incoming payload -> final mode (ignora transitori) */
|
|
34073
34109
|
function payloadToMode(payload) {
|
|
34074
34110
|
if (payload == null)
|
|
34075
34111
|
return;
|
|
@@ -34092,159 +34128,148 @@ class BaseMqttSensor extends ScryptedDeviceBase {
|
|
|
34092
34128
|
this.cfg = cfg;
|
|
34093
34129
|
}
|
|
34094
34130
|
handleMqtt(topic, payload) {
|
|
34095
|
-
const
|
|
34096
|
-
const np = normalize(
|
|
34097
|
-
|
|
34131
|
+
const raw = payload?.toString() ?? '';
|
|
34132
|
+
const np = normalize(raw);
|
|
34133
|
+
// Online
|
|
34134
|
+
if (topicMatches(topic, this.cfg.topics.online)) {
|
|
34098
34135
|
if (truthy(np) || np === 'online')
|
|
34099
|
-
this.online
|
|
34100
|
-
if (falsy(np) || np === 'offline')
|
|
34101
|
-
this.online
|
|
34136
|
+
setAndEmit(this, 'online', true, ScryptedInterface.Online, `[${this.name}] online=true`);
|
|
34137
|
+
else if (falsy(np) || np === 'offline')
|
|
34138
|
+
setAndEmit(this, 'online', false, ScryptedInterface.Online, `[${this.name}] online=false`);
|
|
34102
34139
|
}
|
|
34103
|
-
|
|
34140
|
+
// Tamper
|
|
34141
|
+
if (topicMatches(topic, this.cfg.topics.tamper)) {
|
|
34104
34142
|
if (truthy(np) || ['tamper', 'intrusion', 'cover', 'motion', 'magnetic'].includes(np)) {
|
|
34105
|
-
|
|
34143
|
+
const t = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
|
|
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`);
|
|
34106
34148
|
}
|
|
34107
|
-
else if (falsy(np))
|
|
34108
|
-
this.tampered = false;
|
|
34109
34149
|
}
|
|
34110
|
-
|
|
34111
|
-
|
|
34150
|
+
// Battery
|
|
34151
|
+
if (topicMatches(topic, this.cfg.topics.batteryLevel)) {
|
|
34152
|
+
const n = clamp(parseFloat(raw), 0, 100);
|
|
34112
34153
|
if (isFinite(n))
|
|
34113
|
-
this
|
|
34154
|
+
setAndEmit(this, 'batteryLevel', n, ScryptedInterface.Battery, `[${this.name}] batteryLevel=${n}`);
|
|
34114
34155
|
}
|
|
34115
|
-
else if (topic
|
|
34116
|
-
|
|
34156
|
+
else if (topicMatches(topic, this.cfg.topics.lowBattery) && !this.cfg.topics.batteryLevel) {
|
|
34157
|
+
const n = truthy(np) ? 10 : 100;
|
|
34158
|
+
setAndEmit(this, 'batteryLevel', n, ScryptedInterface.Battery, `[${this.name}] batteryLevel=${n} (lowBattery)`);
|
|
34117
34159
|
}
|
|
34118
|
-
|
|
34160
|
+
// Primario
|
|
34161
|
+
this.handlePrimary(topic, np, raw);
|
|
34119
34162
|
}
|
|
34120
34163
|
}
|
|
34121
|
-
/** === SENSORI
|
|
34164
|
+
/** === SENSORI: parsing robusto + eventi === */
|
|
34122
34165
|
class ContactMqttSensor extends BaseMqttSensor {
|
|
34123
34166
|
handlePrimary(topic, np, raw) {
|
|
34124
|
-
if (topic
|
|
34125
|
-
return;
|
|
34126
|
-
// stringhe comuni
|
|
34127
|
-
if (['open', 'opened', '1', 'true', 'on', 'yes'].includes(np)) {
|
|
34128
|
-
this.entryOpen = true;
|
|
34129
|
-
return;
|
|
34130
|
-
}
|
|
34131
|
-
if (['closed', 'close', '0', 'false', 'off', 'no', 'shut'].includes(np)) {
|
|
34132
|
-
this.entryOpen = false;
|
|
34167
|
+
if (!topicMatches(topic, this.cfg.topics.contact))
|
|
34133
34168
|
return;
|
|
34134
|
-
|
|
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;
|
|
34135
34175
|
// JSON comuni
|
|
34136
|
-
|
|
34137
|
-
|
|
34138
|
-
|
|
34139
|
-
|
|
34140
|
-
|
|
34141
|
-
|
|
34142
|
-
|
|
34143
|
-
|
|
34144
|
-
|
|
34145
|
-
|
|
34146
|
-
|
|
34147
|
-
|
|
34148
|
-
|
|
34149
|
-
|
|
34150
|
-
|
|
34151
|
-
const s = String(j.state).toLowerCase();
|
|
34152
|
-
if (s === 'open') {
|
|
34153
|
-
this.entryOpen = true;
|
|
34154
|
-
return;
|
|
34155
|
-
}
|
|
34156
|
-
if (s === 'closed') {
|
|
34157
|
-
this.entryOpen = false;
|
|
34158
|
-
return;
|
|
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;
|
|
34159
34191
|
}
|
|
34160
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}"`);
|
|
34161
34200
|
}
|
|
34162
|
-
catch { }
|
|
34163
|
-
this.console?.debug?.(`Contact payload non gestito (${this.cfg.id}): "${raw}"`);
|
|
34164
34201
|
}
|
|
34165
34202
|
}
|
|
34166
34203
|
class MotionMqttSensor extends BaseMqttSensor {
|
|
34167
34204
|
handlePrimary(topic, np, raw) {
|
|
34168
|
-
if (topic
|
|
34205
|
+
if (!topicMatches(topic, this.cfg.topics.motion))
|
|
34169
34206
|
return;
|
|
34170
|
-
|
|
34171
|
-
|
|
34172
|
-
|
|
34173
|
-
|
|
34174
|
-
|
|
34175
|
-
|
|
34176
|
-
|
|
34177
|
-
|
|
34178
|
-
|
|
34179
|
-
|
|
34180
|
-
|
|
34181
|
-
|
|
34182
|
-
|
|
34183
|
-
|
|
34184
|
-
|
|
34185
|
-
|
|
34186
|
-
|
|
34187
|
-
|
|
34188
|
-
|
|
34189
|
-
|
|
34190
|
-
return;
|
|
34191
|
-
}
|
|
34192
|
-
if (typeof j?.state === 'string') {
|
|
34193
|
-
const s = String(j.state).toLowerCase();
|
|
34194
|
-
if (['on', 'motion', 'detected', 'active'].includes(s)) {
|
|
34195
|
-
this.motionDetected = true;
|
|
34196
|
-
return;
|
|
34197
|
-
}
|
|
34198
|
-
if (['off', 'clear', 'inactive'].includes(s)) {
|
|
34199
|
-
this.motionDetected = false;
|
|
34200
|
-
return;
|
|
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;
|
|
34201
34227
|
}
|
|
34202
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}"`);
|
|
34203
34236
|
}
|
|
34204
|
-
catch { }
|
|
34205
|
-
this.console?.debug?.(`Motion payload non gestito (${this.cfg.id}): "${raw}"`);
|
|
34206
34237
|
}
|
|
34207
34238
|
}
|
|
34208
34239
|
class OccupancyMqttSensor extends BaseMqttSensor {
|
|
34209
34240
|
handlePrimary(topic, np, raw) {
|
|
34210
|
-
if (topic
|
|
34211
|
-
return;
|
|
34212
|
-
if (['occupied', 'presence', 'present', '1', 'true', 'on', 'yes'].includes(np)) {
|
|
34213
|
-
this.occupied = true;
|
|
34241
|
+
if (!topicMatches(topic, this.cfg.topics.occupancy))
|
|
34214
34242
|
return;
|
|
34215
|
-
|
|
34216
|
-
if (['
|
|
34217
|
-
|
|
34218
|
-
|
|
34219
|
-
|
|
34220
|
-
|
|
34221
|
-
|
|
34222
|
-
|
|
34223
|
-
|
|
34224
|
-
|
|
34225
|
-
|
|
34226
|
-
|
|
34227
|
-
|
|
34228
|
-
|
|
34229
|
-
|
|
34230
|
-
|
|
34231
|
-
|
|
34232
|
-
|
|
34233
|
-
|
|
34234
|
-
|
|
34235
|
-
const s = String(j.state).toLowerCase();
|
|
34236
|
-
if (['occupied', 'presence', 'present', 'on'].includes(s)) {
|
|
34237
|
-
this.occupied = true;
|
|
34238
|
-
return;
|
|
34239
|
-
}
|
|
34240
|
-
if (['vacant', 'absent', 'clear', 'off'].includes(s)) {
|
|
34241
|
-
this.occupied = false;
|
|
34242
|
-
return;
|
|
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;
|
|
34243
34263
|
}
|
|
34244
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}"`);
|
|
34245
34272
|
}
|
|
34246
|
-
catch { }
|
|
34247
|
-
this.console?.debug?.(`Occupancy payload non gestito (${this.cfg.id}): "${raw}"`);
|
|
34248
34273
|
}
|
|
34249
34274
|
}
|
|
34250
34275
|
/** ----------------- Main Plugin ----------------- */
|
|
@@ -34253,7 +34278,6 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34253
34278
|
super();
|
|
34254
34279
|
this.sensorsCfg = [];
|
|
34255
34280
|
this.devices = new Map();
|
|
34256
|
-
// Evita loop di log: tenta una volta finché deviceManager non c’è, poi riprova su eventi utili.
|
|
34257
34281
|
this.discoveryPostponed = false;
|
|
34258
34282
|
// Tipo in UI (best-effort)
|
|
34259
34283
|
setTimeout(() => {
|
|
@@ -34273,12 +34297,9 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34273
34297
|
],
|
|
34274
34298
|
};
|
|
34275
34299
|
this.online = this.online ?? false;
|
|
34276
|
-
// Config sensori e (tentativo) announce
|
|
34277
34300
|
this.loadSensorsFromStorage();
|
|
34278
|
-
this.safeDiscoverSensors();
|
|
34279
|
-
// Connect MQTT
|
|
34301
|
+
this.safeDiscoverSensors();
|
|
34280
34302
|
this.connectMqtt().catch((e) => this.console.error('MQTT connect error:', e));
|
|
34281
|
-
// Shutdown pulito
|
|
34282
34303
|
try {
|
|
34283
34304
|
process.once('SIGTERM', () => { try {
|
|
34284
34305
|
this.client?.end(true);
|
|
@@ -34322,12 +34343,11 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34322
34343
|
];
|
|
34323
34344
|
// Add Sensor
|
|
34324
34345
|
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.' });
|
|
34325
|
-
// Sensors esistenti
|
|
34326
34346
|
for (const cfg of this.sensorsCfg) {
|
|
34327
34347
|
const gid = `Sensor: ${cfg.name} [${cfg.id}]`;
|
|
34328
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'] });
|
|
34329
34349
|
if (cfg.kind === 'contact')
|
|
34330
|
-
out.push({ group: gid, key: `sensor.${cfg.id}.topic.contact`, title: 'Contact State Topic', value: cfg.topics.contact || '' });
|
|
34350
|
+
out.push({ group: gid, key: `sensor.${cfg.id}.topic.contact`, title: 'Contact State Topic', value: cfg.topics.contact || '', placeholder: 'paradox/states/zones/XYZ/open (supporta +/#)' });
|
|
34331
34351
|
else if (cfg.kind === 'motion')
|
|
34332
34352
|
out.push({ group: gid, key: `sensor.${cfg.id}.topic.motion`, title: 'Motion Detected Topic', value: cfg.topics.motion || '' });
|
|
34333
34353
|
else
|
|
@@ -34430,11 +34450,9 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34430
34450
|
this.sensorsCfg = [];
|
|
34431
34451
|
}
|
|
34432
34452
|
}
|
|
34433
|
-
/** Annuncia i sensori SOLO se deviceManager è pronto. Niente loop infinito. */
|
|
34434
34453
|
safeDiscoverSensors(triggeredByChange = false) {
|
|
34435
34454
|
const dmAny = sdk?.deviceManager;
|
|
34436
34455
|
if (!dmAny) {
|
|
34437
|
-
// Posticipa una sola volta; poi riproviamo su connect MQTT e al primo messaggio
|
|
34438
34456
|
if (!this.discoveryPostponed) {
|
|
34439
34457
|
this.console.log('Device discovery postponed: deviceManager not ready yet.');
|
|
34440
34458
|
this.discoveryPostponed = true;
|
|
@@ -34446,9 +34464,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34446
34464
|
if (triggeredByChange)
|
|
34447
34465
|
this.console.log('Sensors discovered/updated.');
|
|
34448
34466
|
}
|
|
34449
|
-
/** discoverSensors con deviceManager garantito */
|
|
34450
34467
|
discoverSensors(dmAny) {
|
|
34451
|
-
// 1) Manifests
|
|
34452
34468
|
const manifests = this.sensorsCfg.map(cfg => {
|
|
34453
34469
|
const nativeId = `sensor:${cfg.id}`;
|
|
34454
34470
|
const t = cfg.topics || {};
|
|
@@ -34465,15 +34481,11 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34465
34481
|
interfaces.push(ScryptedInterface.Battery);
|
|
34466
34482
|
return { nativeId, name: cfg.name, type: ScryptedDeviceType.Sensor, interfaces };
|
|
34467
34483
|
});
|
|
34468
|
-
|
|
34469
|
-
if (typeof dmAny.onDevicesChanged === 'function') {
|
|
34484
|
+
if (typeof dmAny.onDevicesChanged === 'function')
|
|
34470
34485
|
dmAny.onDevicesChanged({ devices: manifests });
|
|
34471
|
-
|
|
34472
|
-
else if (typeof dmAny.onDeviceDiscovered === 'function') {
|
|
34486
|
+
else if (typeof dmAny.onDeviceDiscovered === 'function')
|
|
34473
34487
|
for (const m of manifests)
|
|
34474
34488
|
dmAny.onDeviceDiscovered(m);
|
|
34475
|
-
}
|
|
34476
|
-
// 3) Istanzia/aggiorna
|
|
34477
34489
|
for (const cfg of this.sensorsCfg) {
|
|
34478
34490
|
const nativeId = `sensor:${cfg.id}`;
|
|
34479
34491
|
let dev = this.devices.get(nativeId);
|
|
@@ -34491,9 +34503,8 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34491
34503
|
}
|
|
34492
34504
|
const hasBattery = !!(cfg.topics.batteryLevel || cfg.topics.lowBattery);
|
|
34493
34505
|
if (hasBattery && dev.batteryLevel === undefined)
|
|
34494
|
-
dev.batteryLevel
|
|
34506
|
+
setAndEmit(dev, 'batteryLevel', 100, ScryptedInterface.Battery, `[${cfg.name}] batteryLevel=100 (default)`);
|
|
34495
34507
|
}
|
|
34496
|
-
// 4) Cleanup
|
|
34497
34508
|
const announced = new Set(manifests.map(m => m.nativeId));
|
|
34498
34509
|
for (const [nativeId] of this.devices) {
|
|
34499
34510
|
if (!announced.has(nativeId)) {
|
|
@@ -34556,39 +34567,41 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34556
34567
|
const tOnline = this.storage.getItem('topicOnline') || '';
|
|
34557
34568
|
client.on('connect', () => {
|
|
34558
34569
|
this.console.log('MQTT connected');
|
|
34559
|
-
this.online
|
|
34570
|
+
setAndEmit(this, 'online', true, ScryptedInterface.Online, `[Alarm] online=true`);
|
|
34560
34571
|
if (subs.length)
|
|
34561
34572
|
client.subscribe(subs, { qos: 0 }, (err) => { if (err)
|
|
34562
34573
|
this.console.error('subscribe error', err); });
|
|
34563
|
-
// Al primo connect riprova (silenziosamente) ad annunciare i sensori
|
|
34564
34574
|
this.safeDiscoverSensors(true);
|
|
34565
34575
|
});
|
|
34566
34576
|
client.on('reconnect', () => this.console.log('MQTT reconnecting...'));
|
|
34567
|
-
client.on('close', () => { this.console.log('MQTT closed'); this.online
|
|
34577
|
+
client.on('close', () => { this.console.log('MQTT closed'); setAndEmit(this, 'online', false, ScryptedInterface.Online, `[Alarm] online=false`); });
|
|
34568
34578
|
client.on('error', (e) => { this.console.error('MQTT error', e); });
|
|
34569
34579
|
client.on('message', (topic, payload) => {
|
|
34570
34580
|
try {
|
|
34571
|
-
const
|
|
34572
|
-
const np = normalize(
|
|
34573
|
-
if (topic
|
|
34581
|
+
const raw = payload?.toString() ?? '';
|
|
34582
|
+
const np = normalize(raw);
|
|
34583
|
+
if (topicMatches(topic, tOnline)) {
|
|
34574
34584
|
if (truthy(np) || np === 'online')
|
|
34575
|
-
this.online
|
|
34576
|
-
if (falsy(np) || np === 'offline')
|
|
34577
|
-
this.online
|
|
34585
|
+
setAndEmit(this, 'online', true, ScryptedInterface.Online, `[Alarm] online=true (${topic})`);
|
|
34586
|
+
else if (falsy(np) || np === 'offline')
|
|
34587
|
+
setAndEmit(this, 'online', false, ScryptedInterface.Online, `[Alarm] online=false (${topic})`);
|
|
34578
34588
|
return;
|
|
34579
34589
|
}
|
|
34580
|
-
if (topic
|
|
34581
|
-
if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np))
|
|
34582
|
-
|
|
34583
|
-
|
|
34584
|
-
|
|
34590
|
+
if (topicMatches(topic, tTamper)) {
|
|
34591
|
+
if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np)) {
|
|
34592
|
+
const t = ['cover', 'intrusion'].find(x => x === np) || true;
|
|
34593
|
+
setAndEmit(this, 'tampered', t, ScryptedInterface.TamperSensor, `[Alarm] tampered=${t} (${topic})`);
|
|
34594
|
+
}
|
|
34595
|
+
else if (falsy(np)) {
|
|
34596
|
+
setAndEmit(this, 'tampered', false, ScryptedInterface.TamperSensor, `[Alarm] tampered=false (${topic})`);
|
|
34597
|
+
}
|
|
34585
34598
|
return;
|
|
34586
34599
|
}
|
|
34587
|
-
if (topic
|
|
34600
|
+
if (topicMatches(topic, tCurrent)) {
|
|
34588
34601
|
const mode = payloadToMode(payload);
|
|
34589
34602
|
const isAlarm = ['alarm', 'triggered'].includes(np);
|
|
34590
34603
|
const current = this.securitySystemState || { mode: SecuritySystemMode.Disarmed };
|
|
34591
|
-
|
|
34604
|
+
const newState = {
|
|
34592
34605
|
mode: mode ?? current.mode,
|
|
34593
34606
|
supportedModes: current.supportedModes ?? [
|
|
34594
34607
|
SecuritySystemMode.Disarmed,
|
|
@@ -34598,15 +34611,22 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34598
34611
|
],
|
|
34599
34612
|
triggered: isAlarm || undefined,
|
|
34600
34613
|
};
|
|
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
|
+
}
|
|
34601
34622
|
return;
|
|
34602
34623
|
}
|
|
34603
|
-
if (topic
|
|
34624
|
+
if (topicMatches(topic, tTarget)) {
|
|
34604
34625
|
this.pendingTarget = payloadToMode(payload);
|
|
34605
|
-
this.console.log(
|
|
34626
|
+
this.console.log(`[Alarm] target reported: "${raw}" -> ${this.pendingTarget} (${topic})`);
|
|
34606
34627
|
return;
|
|
34607
34628
|
}
|
|
34608
|
-
//
|
|
34609
|
-
// (E prova ad annunciare se era stato posticipato e ora il manager è pronto)
|
|
34629
|
+
// Sensors: se discovery rimandata, riprova
|
|
34610
34630
|
if (this.discoveryPostponed)
|
|
34611
34631
|
this.safeDiscoverSensors(true);
|
|
34612
34632
|
for (const dev of this.devices.values())
|
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED