@rfranzoi/scrypted-mqtt-securitysystem 1.0.35 → 1.0.37
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 +202 -143
- 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,178 @@ 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
|
}
|
|
34121
|
+
/** === SENSORI CON PARSING ROBUSTO PER HOMEKIT === */
|
|
34135
34122
|
class ContactMqttSensor extends BaseMqttSensor {
|
|
34136
|
-
handlePrimary(topic, np) {
|
|
34137
|
-
if (topic
|
|
34138
|
-
|
|
34123
|
+
handlePrimary(topic, np, raw) {
|
|
34124
|
+
if (topic !== this.cfg.topics.contact)
|
|
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;
|
|
34133
|
+
return;
|
|
34134
|
+
}
|
|
34135
|
+
// JSON comuni
|
|
34136
|
+
try {
|
|
34137
|
+
const j = JSON.parse(raw);
|
|
34138
|
+
if (typeof j?.open === 'boolean') {
|
|
34139
|
+
this.entryOpen = !!j.open;
|
|
34140
|
+
return;
|
|
34141
|
+
}
|
|
34142
|
+
if (typeof j?.opened === 'boolean') {
|
|
34143
|
+
this.entryOpen = !!j.opened;
|
|
34144
|
+
return;
|
|
34145
|
+
}
|
|
34146
|
+
if (typeof j?.contact === 'boolean') {
|
|
34147
|
+
this.entryOpen = !j.contact;
|
|
34148
|
+
return;
|
|
34149
|
+
} // contact:false => OPEN
|
|
34150
|
+
if (typeof j?.state === 'string') {
|
|
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;
|
|
34159
|
+
}
|
|
34160
|
+
}
|
|
34139
34161
|
}
|
|
34162
|
+
catch { }
|
|
34163
|
+
this.console?.debug?.(`Contact payload non gestito (${this.cfg.id}): "${raw}"`);
|
|
34140
34164
|
}
|
|
34141
34165
|
}
|
|
34142
34166
|
class MotionMqttSensor extends BaseMqttSensor {
|
|
34143
|
-
handlePrimary(topic, np) {
|
|
34144
|
-
if (topic
|
|
34145
|
-
|
|
34167
|
+
handlePrimary(topic, np, raw) {
|
|
34168
|
+
if (topic !== this.cfg.topics.motion)
|
|
34169
|
+
return;
|
|
34170
|
+
if (['motion', 'detected', 'active', '1', 'true', 'on', 'yes'].includes(np)) {
|
|
34171
|
+
this.motionDetected = true;
|
|
34172
|
+
return;
|
|
34173
|
+
}
|
|
34174
|
+
if (['clear', 'inactive', 'no_motion', 'none', '0', 'false', 'off', 'no'].includes(np)) {
|
|
34175
|
+
this.motionDetected = false;
|
|
34176
|
+
return;
|
|
34177
|
+
}
|
|
34178
|
+
try {
|
|
34179
|
+
const j = JSON.parse(raw);
|
|
34180
|
+
if (typeof j?.motion === 'boolean') {
|
|
34181
|
+
this.motionDetected = !!j.motion;
|
|
34182
|
+
return;
|
|
34183
|
+
}
|
|
34184
|
+
if (typeof j?.occupancy === 'boolean') {
|
|
34185
|
+
this.motionDetected = !!j.occupancy;
|
|
34186
|
+
return;
|
|
34187
|
+
}
|
|
34188
|
+
if (typeof j?.presence === 'boolean') {
|
|
34189
|
+
this.motionDetected = !!j.presence;
|
|
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;
|
|
34201
|
+
}
|
|
34202
|
+
}
|
|
34146
34203
|
}
|
|
34204
|
+
catch { }
|
|
34205
|
+
this.console?.debug?.(`Motion payload non gestito (${this.cfg.id}): "${raw}"`);
|
|
34147
34206
|
}
|
|
34148
34207
|
}
|
|
34149
34208
|
class OccupancyMqttSensor extends BaseMqttSensor {
|
|
34150
|
-
handlePrimary(topic, np) {
|
|
34151
|
-
if (topic
|
|
34152
|
-
|
|
34209
|
+
handlePrimary(topic, np, raw) {
|
|
34210
|
+
if (topic !== this.cfg.topics.occupancy)
|
|
34211
|
+
return;
|
|
34212
|
+
if (['occupied', 'presence', 'present', '1', 'true', 'on', 'yes'].includes(np)) {
|
|
34213
|
+
this.occupied = true;
|
|
34214
|
+
return;
|
|
34215
|
+
}
|
|
34216
|
+
if (['unoccupied', 'vacant', 'absent', '0', 'false', 'off', 'no', 'clear'].includes(np)) {
|
|
34217
|
+
this.occupied = false;
|
|
34218
|
+
return;
|
|
34219
|
+
}
|
|
34220
|
+
try {
|
|
34221
|
+
const j = JSON.parse(raw);
|
|
34222
|
+
if (typeof j?.occupied === 'boolean') {
|
|
34223
|
+
this.occupied = !!j.occupied;
|
|
34224
|
+
return;
|
|
34225
|
+
}
|
|
34226
|
+
if (typeof j?.presence === 'boolean') {
|
|
34227
|
+
this.occupied = !!j.presence;
|
|
34228
|
+
return;
|
|
34229
|
+
}
|
|
34230
|
+
if (typeof j?.occupancy === 'boolean') {
|
|
34231
|
+
this.occupied = !!j.occupancy;
|
|
34232
|
+
return;
|
|
34233
|
+
}
|
|
34234
|
+
if (typeof j?.state === 'string') {
|
|
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
|
+
}
|
|
34244
|
+
}
|
|
34153
34245
|
}
|
|
34246
|
+
catch { }
|
|
34247
|
+
this.console?.debug?.(`Occupancy payload non gestito (${this.cfg.id}): "${raw}"`);
|
|
34154
34248
|
}
|
|
34155
34249
|
}
|
|
34156
34250
|
/** ----------------- Main Plugin ----------------- */
|
|
34157
34251
|
class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
34158
34252
|
constructor() {
|
|
34159
34253
|
super();
|
|
34160
|
-
// sensor management
|
|
34161
34254
|
this.sensorsCfg = [];
|
|
34162
34255
|
this.devices = new Map();
|
|
34163
|
-
//
|
|
34256
|
+
// Evita loop di log: tenta una volta finché deviceManager non c’è, poi riprova su eventi utili.
|
|
34257
|
+
this.discoveryPostponed = false;
|
|
34258
|
+
// Tipo in UI (best-effort)
|
|
34164
34259
|
setTimeout(() => {
|
|
34165
34260
|
try {
|
|
34166
34261
|
systemManager.getDeviceById(this.id)?.setType?.(ScryptedDeviceType.SecuritySystem);
|
|
34167
34262
|
}
|
|
34168
34263
|
catch { }
|
|
34169
34264
|
});
|
|
34170
|
-
//
|
|
34265
|
+
// Stato di default
|
|
34171
34266
|
this.securitySystemState = this.securitySystemState || {
|
|
34172
34267
|
mode: SecuritySystemMode.Disarmed,
|
|
34173
34268
|
supportedModes: [
|
|
@@ -34178,12 +34273,12 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34178
34273
|
],
|
|
34179
34274
|
};
|
|
34180
34275
|
this.online = this.online ?? false;
|
|
34181
|
-
//
|
|
34276
|
+
// Config sensori e (tentativo) announce
|
|
34182
34277
|
this.loadSensorsFromStorage();
|
|
34183
|
-
this.
|
|
34184
|
-
// Connect
|
|
34278
|
+
this.safeDiscoverSensors(); // non spamma se deviceManager non c'è
|
|
34279
|
+
// Connect MQTT
|
|
34185
34280
|
this.connectMqtt().catch((e) => this.console.error('MQTT connect error:', e));
|
|
34186
|
-
//
|
|
34281
|
+
// Shutdown pulito
|
|
34187
34282
|
try {
|
|
34188
34283
|
process.once('SIGTERM', () => { try {
|
|
34189
34284
|
this.client?.end(true);
|
|
@@ -34200,7 +34295,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34200
34295
|
}
|
|
34201
34296
|
catch { }
|
|
34202
34297
|
}
|
|
34203
|
-
|
|
34298
|
+
/** ---- Settings ---- */
|
|
34204
34299
|
saveSensorsToStorage() {
|
|
34205
34300
|
try {
|
|
34206
34301
|
this.storage.setItem('sensorsJson', JSON.stringify(this.sensorsCfg));
|
|
@@ -34209,17 +34304,14 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34209
34304
|
this.console.error('saveSensorsToStorage error', e);
|
|
34210
34305
|
}
|
|
34211
34306
|
}
|
|
34212
|
-
/** ---- Settings UI ---- */
|
|
34213
34307
|
async getSettings() {
|
|
34214
34308
|
const out = [
|
|
34215
|
-
// MQTT Core
|
|
34216
34309
|
{ 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
34310
|
{ group: 'MQTT', key: 'username', title: 'Username', type: 'string', value: this.storage.getItem('username') || '' },
|
|
34218
34311
|
{ group: 'MQTT', key: 'password', title: 'Password', type: 'password', value: this.storage.getItem('password') || '' },
|
|
34219
34312
|
{ group: 'MQTT', key: 'clientId', title: 'Client ID', placeholder: 'scrypted-paradox', value: this.storage.getItem('clientId') || 'scrypted-paradox' },
|
|
34220
34313
|
{ group: 'MQTT', key: 'tls', title: 'Use TLS', type: 'boolean', value: this.storage.getItem('tls') === 'true' },
|
|
34221
34314
|
{ 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
34315
|
{ group: 'Alarm Topics', key: 'topicSetTarget', title: 'Set Target State (publish)', placeholder: 'paradox/control/partitions/Area_1', value: this.storage.getItem('topicSetTarget') || '' },
|
|
34224
34316
|
{ group: 'Alarm Topics', key: 'topicGetTarget', title: 'Get Target State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/target_state', value: this.storage.getItem('topicGetTarget') || '' },
|
|
34225
34317
|
{ 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 +34320,24 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34228
34320
|
{ group: 'Publish Options', key: 'qos', title: 'QoS', type: 'integer', value: parseInt(this.storage.getItem('qos') || '0') },
|
|
34229
34321
|
{ group: 'Publish Options', key: 'retain', title: 'Retain', type: 'boolean', value: this.storage.getItem('retain') === 'true' },
|
|
34230
34322
|
];
|
|
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
|
-
//
|
|
34323
|
+
// Add Sensor
|
|
34324
|
+
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
|
|
34234
34326
|
for (const cfg of this.sensorsCfg) {
|
|
34235
34327
|
const gid = `Sensor: ${cfg.name} [${cfg.id}]`;
|
|
34236
34328
|
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
|
|
34329
|
+
if (cfg.kind === 'contact')
|
|
34330
|
+
out.push({ group: gid, key: `sensor.${cfg.id}.topic.contact`, title: 'Contact State Topic', value: cfg.topics.contact || '' });
|
|
34331
|
+
else if (cfg.kind === 'motion')
|
|
34332
|
+
out.push({ group: gid, key: `sensor.${cfg.id}.topic.motion`, title: 'Motion Detected Topic', value: cfg.topics.motion || '' });
|
|
34333
|
+
else
|
|
34334
|
+
out.push({ group: gid, key: `sensor.${cfg.id}.topic.occupancy`, title: 'Occupancy Detected Topic', value: cfg.topics.occupancy || '' });
|
|
34248
34335
|
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
34336
|
}
|
|
34250
34337
|
return out;
|
|
34251
34338
|
}
|
|
34252
34339
|
async putSetting(key, value) {
|
|
34253
|
-
// salva sempre nella storage la value del campo (così resta in UI)
|
|
34254
34340
|
this.storage.setItem(key, String(value));
|
|
34255
|
-
// --- Add Sensor workflow ---
|
|
34256
34341
|
if (key === 'new.create' && String(value) === 'true') {
|
|
34257
34342
|
const id = (this.storage.getItem('new.id') || '').trim();
|
|
34258
34343
|
const name = (this.storage.getItem('new.name') || '').trim() || id;
|
|
@@ -34267,16 +34352,14 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34267
34352
|
}
|
|
34268
34353
|
this.sensorsCfg.push({ id, name, kind, topics: {} });
|
|
34269
34354
|
this.saveSensorsToStorage();
|
|
34270
|
-
// pulisci i campi "new.*"
|
|
34271
34355
|
this.storage.removeItem('new.id');
|
|
34272
34356
|
this.storage.removeItem('new.name');
|
|
34273
34357
|
this.storage.removeItem('new.kind');
|
|
34274
34358
|
this.storage.removeItem('new.create');
|
|
34275
|
-
|
|
34359
|
+
this.safeDiscoverSensors(true);
|
|
34276
34360
|
await this.connectMqtt(true);
|
|
34277
34361
|
return;
|
|
34278
34362
|
}
|
|
34279
|
-
// --- Edit/Remove sensore esistente ---
|
|
34280
34363
|
const m = key.match(/^sensor\.([^\.]+)\.(.+)$/);
|
|
34281
34364
|
if (m) {
|
|
34282
34365
|
const sid = m[1];
|
|
@@ -34287,40 +34370,33 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34287
34370
|
return;
|
|
34288
34371
|
}
|
|
34289
34372
|
if (prop === 'remove' && String(value) === 'true') {
|
|
34290
|
-
// elimina
|
|
34291
34373
|
this.sensorsCfg = this.sensorsCfg.filter(s => s.id !== sid);
|
|
34292
34374
|
this.saveSensorsToStorage();
|
|
34293
34375
|
try {
|
|
34294
|
-
this.devices.delete(`sensor:${sid}`);
|
|
34295
34376
|
sdk?.deviceManager?.onDeviceRemoved?.(`sensor:${sid}`);
|
|
34296
34377
|
}
|
|
34297
34378
|
catch { }
|
|
34298
|
-
// pulisci flag
|
|
34299
34379
|
this.storage.removeItem(key);
|
|
34300
|
-
|
|
34380
|
+
this.safeDiscoverSensors(true);
|
|
34301
34381
|
await this.connectMqtt(true);
|
|
34302
34382
|
return;
|
|
34303
34383
|
}
|
|
34304
|
-
if (prop === 'name')
|
|
34384
|
+
if (prop === 'name')
|
|
34305
34385
|
cfg.name = String(value);
|
|
34306
|
-
|
|
34307
|
-
else if (prop === 'kind') {
|
|
34386
|
+
else if (prop === 'kind')
|
|
34308
34387
|
cfg.kind = String(value);
|
|
34309
|
-
}
|
|
34310
34388
|
else if (prop.startsWith('topic.')) {
|
|
34311
34389
|
const tk = prop.substring('topic.'.length);
|
|
34312
34390
|
cfg.topics[tk] = String(value).trim();
|
|
34313
34391
|
}
|
|
34314
34392
|
this.saveSensorsToStorage();
|
|
34315
|
-
|
|
34393
|
+
this.safeDiscoverSensors(true);
|
|
34316
34394
|
await this.connectMqtt(true);
|
|
34317
34395
|
return;
|
|
34318
34396
|
}
|
|
34319
|
-
// --- Altro (MQTT / Alarm settings) ---
|
|
34320
34397
|
if (key === 'sensorsJson') {
|
|
34321
|
-
// non più mostrato, ma se presente da vecchie versioni
|
|
34322
34398
|
this.loadSensorsFromStorage();
|
|
34323
|
-
|
|
34399
|
+
this.safeDiscoverSensors(true);
|
|
34324
34400
|
await this.connectMqtt(true);
|
|
34325
34401
|
}
|
|
34326
34402
|
else {
|
|
@@ -34328,15 +34404,12 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34328
34404
|
}
|
|
34329
34405
|
}
|
|
34330
34406
|
/** ---- DeviceProvider ---- */
|
|
34331
|
-
async getDevice(nativeId) {
|
|
34332
|
-
return this.devices.get(nativeId);
|
|
34333
|
-
}
|
|
34407
|
+
async getDevice(nativeId) { return this.devices.get(nativeId); }
|
|
34334
34408
|
async releaseDevice(_id, nativeId) {
|
|
34335
34409
|
try {
|
|
34336
34410
|
const dev = this.devices.get(nativeId);
|
|
34337
|
-
if (dev)
|
|
34411
|
+
if (dev)
|
|
34338
34412
|
this.devices.delete(nativeId);
|
|
34339
|
-
}
|
|
34340
34413
|
try {
|
|
34341
34414
|
sdk?.deviceManager?.onDeviceRemoved?.(nativeId);
|
|
34342
34415
|
}
|
|
@@ -34350,7 +34423,6 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34350
34423
|
try {
|
|
34351
34424
|
const raw = this.storage.getItem('sensorsJson') || '[]';
|
|
34352
34425
|
const parsed = JSON.parse(raw);
|
|
34353
|
-
// sanitize
|
|
34354
34426
|
this.sensorsCfg = (parsed || []).filter(x => x && x.id && x.name && x.kind && x.topics);
|
|
34355
34427
|
}
|
|
34356
34428
|
catch (e) {
|
|
@@ -34358,15 +34430,25 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34358
34430
|
this.sensorsCfg = [];
|
|
34359
34431
|
}
|
|
34360
34432
|
}
|
|
34361
|
-
/**
|
|
34362
|
-
|
|
34433
|
+
/** Annuncia i sensori SOLO se deviceManager è pronto. Niente loop infinito. */
|
|
34434
|
+
safeDiscoverSensors(triggeredByChange = false) {
|
|
34363
34435
|
const dmAny = sdk?.deviceManager;
|
|
34364
34436
|
if (!dmAny) {
|
|
34365
|
-
|
|
34366
|
-
|
|
34437
|
+
// Posticipa una sola volta; poi riproviamo su connect MQTT e al primo messaggio
|
|
34438
|
+
if (!this.discoveryPostponed) {
|
|
34439
|
+
this.console.log('Device discovery postponed: deviceManager not ready yet.');
|
|
34440
|
+
this.discoveryPostponed = true;
|
|
34441
|
+
}
|
|
34367
34442
|
return;
|
|
34368
34443
|
}
|
|
34369
|
-
|
|
34444
|
+
this.discoveryPostponed = false;
|
|
34445
|
+
this.discoverSensors(dmAny);
|
|
34446
|
+
if (triggeredByChange)
|
|
34447
|
+
this.console.log('Sensors discovered/updated.');
|
|
34448
|
+
}
|
|
34449
|
+
/** discoverSensors con deviceManager garantito */
|
|
34450
|
+
discoverSensors(dmAny) {
|
|
34451
|
+
// 1) Manifests
|
|
34370
34452
|
const manifests = this.sensorsCfg.map(cfg => {
|
|
34371
34453
|
const nativeId = `sensor:${cfg.id}`;
|
|
34372
34454
|
const t = cfg.topics || {};
|
|
@@ -34386,18 +34468,10 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34386
34468
|
// 2) Annuncio
|
|
34387
34469
|
if (typeof dmAny.onDevicesChanged === 'function') {
|
|
34388
34470
|
dmAny.onDevicesChanged({ devices: manifests });
|
|
34389
|
-
this.console.log('Annunciati (batch):', manifests.map(m => m.nativeId).join(', '));
|
|
34390
34471
|
}
|
|
34391
34472
|
else if (typeof dmAny.onDeviceDiscovered === 'function') {
|
|
34392
|
-
for (const m of manifests)
|
|
34473
|
+
for (const m of manifests)
|
|
34393
34474
|
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
34475
|
}
|
|
34402
34476
|
// 3) Istanzia/aggiorna
|
|
34403
34477
|
for (const cfg of this.sensorsCfg) {
|
|
@@ -34416,18 +34490,16 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34416
34490
|
dev.cfg = cfg;
|
|
34417
34491
|
}
|
|
34418
34492
|
const hasBattery = !!(cfg.topics.batteryLevel || cfg.topics.lowBattery);
|
|
34419
|
-
if (hasBattery && dev.batteryLevel === undefined)
|
|
34493
|
+
if (hasBattery && dev.batteryLevel === undefined)
|
|
34420
34494
|
dev.batteryLevel = 100;
|
|
34421
|
-
}
|
|
34422
34495
|
}
|
|
34423
|
-
// 4)
|
|
34496
|
+
// 4) Cleanup
|
|
34424
34497
|
const announced = new Set(manifests.map(m => m.nativeId));
|
|
34425
34498
|
for (const [nativeId] of this.devices) {
|
|
34426
34499
|
if (!announced.has(nativeId)) {
|
|
34427
34500
|
try {
|
|
34428
34501
|
this.devices.delete(nativeId);
|
|
34429
|
-
|
|
34430
|
-
this.console.log('Rimosso:', nativeId);
|
|
34502
|
+
dmAny.onDeviceRemoved?.(nativeId);
|
|
34431
34503
|
}
|
|
34432
34504
|
catch { }
|
|
34433
34505
|
}
|
|
@@ -34441,13 +34513,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34441
34513
|
const clientId = this.storage.getItem('clientId') || 'scrypted-paradox';
|
|
34442
34514
|
const tls = this.storage.getItem('tls') === 'true';
|
|
34443
34515
|
const rejectUnauthorized = this.storage.getItem('rejectUnauthorized') !== 'false';
|
|
34444
|
-
const opts = {
|
|
34445
|
-
clientId,
|
|
34446
|
-
username,
|
|
34447
|
-
password,
|
|
34448
|
-
clean: true,
|
|
34449
|
-
reconnectPeriod: 3000,
|
|
34450
|
-
};
|
|
34516
|
+
const opts = { clientId, username, password, clean: true, reconnectPeriod: 3000 };
|
|
34451
34517
|
if (tls) {
|
|
34452
34518
|
opts.protocol = 'mqtts';
|
|
34453
34519
|
opts.rejectUnauthorized = rejectUnauthorized;
|
|
@@ -34456,38 +34522,34 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34456
34522
|
}
|
|
34457
34523
|
collectAllSubscriptions() {
|
|
34458
34524
|
const subs = new Set();
|
|
34459
|
-
// alarm
|
|
34460
34525
|
for (const k of ['topicGetTarget', 'topicGetCurrent', 'topicTamper', 'topicOnline']) {
|
|
34461
34526
|
const v = this.storage.getItem(k);
|
|
34462
34527
|
if (v)
|
|
34463
34528
|
subs.add(v);
|
|
34464
34529
|
}
|
|
34465
|
-
// sensors
|
|
34466
34530
|
for (const s of this.sensorsCfg) {
|
|
34467
34531
|
const t = s.topics || {};
|
|
34468
34532
|
[t.contact, t.motion, t.occupancy, t.batteryLevel, t.lowBattery, t.tamper, t.online]
|
|
34469
|
-
.filter(Boolean)
|
|
34470
|
-
.forEach(x => subs.add(String(x)));
|
|
34533
|
+
.filter(Boolean).forEach(x => subs.add(String(x)));
|
|
34471
34534
|
}
|
|
34472
34535
|
return Array.from(subs);
|
|
34473
34536
|
}
|
|
34474
34537
|
async connectMqtt(_reconnect = false) {
|
|
34475
34538
|
const subs = this.collectAllSubscriptions();
|
|
34476
|
-
if (!subs.length && !this.storage.getItem('topicSetTarget'))
|
|
34539
|
+
if (!subs.length && !this.storage.getItem('topicSetTarget'))
|
|
34477
34540
|
this.console.warn('Configura almeno un topic nelle impostazioni.');
|
|
34478
|
-
}
|
|
34479
34541
|
if (this.client) {
|
|
34480
34542
|
try {
|
|
34481
34543
|
this.client.end(true);
|
|
34482
34544
|
}
|
|
34483
34545
|
catch { }
|
|
34546
|
+
;
|
|
34484
34547
|
this.client = undefined;
|
|
34485
34548
|
}
|
|
34486
34549
|
const { url, opts } = this.getMqttOptions();
|
|
34487
34550
|
this.console.log(`Connecting MQTT ${url} ...`);
|
|
34488
34551
|
const client = mqtt_1.default.connect(url, opts);
|
|
34489
34552
|
this.client = client;
|
|
34490
|
-
// cache alarm topics for fast compare
|
|
34491
34553
|
const tTarget = this.storage.getItem('topicGetTarget') || '';
|
|
34492
34554
|
const tCurrent = this.storage.getItem('topicGetCurrent') || '';
|
|
34493
34555
|
const tTamper = this.storage.getItem('topicTamper') || '';
|
|
@@ -34495,12 +34557,11 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34495
34557
|
client.on('connect', () => {
|
|
34496
34558
|
this.console.log('MQTT connected');
|
|
34497
34559
|
this.online = true;
|
|
34498
|
-
if (subs.length)
|
|
34499
|
-
client.subscribe(subs, { qos: 0 }, (err) => {
|
|
34500
|
-
|
|
34501
|
-
|
|
34502
|
-
|
|
34503
|
-
}
|
|
34560
|
+
if (subs.length)
|
|
34561
|
+
client.subscribe(subs, { qos: 0 }, (err) => { if (err)
|
|
34562
|
+
this.console.error('subscribe error', err); });
|
|
34563
|
+
// Al primo connect riprova (silenziosamente) ad annunciare i sensori
|
|
34564
|
+
this.safeDiscoverSensors(true);
|
|
34504
34565
|
});
|
|
34505
34566
|
client.on('reconnect', () => this.console.log('MQTT reconnecting...'));
|
|
34506
34567
|
client.on('close', () => { this.console.log('MQTT closed'); this.online = false; });
|
|
@@ -34509,7 +34570,6 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34509
34570
|
try {
|
|
34510
34571
|
const p = payload?.toString() ?? '';
|
|
34511
34572
|
const np = normalize(p);
|
|
34512
|
-
// ---- Alarm handling ----
|
|
34513
34573
|
if (topic === tOnline) {
|
|
34514
34574
|
if (truthy(np) || np === 'online')
|
|
34515
34575
|
this.online = true;
|
|
@@ -34518,19 +34578,17 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34518
34578
|
return;
|
|
34519
34579
|
}
|
|
34520
34580
|
if (topic === tTamper) {
|
|
34521
|
-
if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np))
|
|
34581
|
+
if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np))
|
|
34522
34582
|
this.tampered = ['cover', 'intrusion'].find(x => x === np) || true;
|
|
34523
|
-
|
|
34524
|
-
else if (falsy(np)) {
|
|
34583
|
+
else if (falsy(np))
|
|
34525
34584
|
this.tampered = false;
|
|
34526
|
-
}
|
|
34527
34585
|
return;
|
|
34528
34586
|
}
|
|
34529
34587
|
if (topic === tCurrent) {
|
|
34530
34588
|
const mode = payloadToMode(payload);
|
|
34531
34589
|
const isAlarm = ['alarm', 'triggered'].includes(np);
|
|
34532
34590
|
const current = this.securitySystemState || { mode: SecuritySystemMode.Disarmed };
|
|
34533
|
-
|
|
34591
|
+
this.securitySystemState = {
|
|
34534
34592
|
mode: mode ?? current.mode,
|
|
34535
34593
|
supportedModes: current.supportedModes ?? [
|
|
34536
34594
|
SecuritySystemMode.Disarmed,
|
|
@@ -34540,7 +34598,6 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34540
34598
|
],
|
|
34541
34599
|
triggered: isAlarm || undefined,
|
|
34542
34600
|
};
|
|
34543
|
-
this.securitySystemState = newState;
|
|
34544
34601
|
return;
|
|
34545
34602
|
}
|
|
34546
34603
|
if (topic === tTarget) {
|
|
@@ -34548,10 +34605,12 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34548
34605
|
this.console.log('Target state reported:', p, '->', this.pendingTarget);
|
|
34549
34606
|
return;
|
|
34550
34607
|
}
|
|
34551
|
-
//
|
|
34552
|
-
|
|
34608
|
+
// Dispatch ai sensori
|
|
34609
|
+
// (E prova ad annunciare se era stato posticipato e ora il manager è pronto)
|
|
34610
|
+
if (this.discoveryPostponed)
|
|
34611
|
+
this.safeDiscoverSensors(true);
|
|
34612
|
+
for (const dev of this.devices.values())
|
|
34553
34613
|
dev.handleMqtt(topic, payload);
|
|
34554
|
-
}
|
|
34555
34614
|
}
|
|
34556
34615
|
catch (e) {
|
|
34557
34616
|
this.console.error('MQTT message handler error', e);
|
|
@@ -34576,7 +34635,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34576
34635
|
async armSecuritySystem(mode) {
|
|
34577
34636
|
const payload = this.getOutgoing(mode);
|
|
34578
34637
|
this.console.log('armSecuritySystem', mode, '->', payload);
|
|
34579
|
-
this.pendingTarget = mode;
|
|
34638
|
+
this.pendingTarget = mode;
|
|
34580
34639
|
this.publishSetTarget(payload);
|
|
34581
34640
|
}
|
|
34582
34641
|
async disarmSecuritySystem() {
|
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED