@rfranzoi/scrypted-mqtt-securitysystem 1.0.48 → 1.0.50
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 +129 -83
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
package/dist/main.nodejs.js
CHANGED
|
@@ -34064,6 +34064,15 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
|
34064
34064
|
const sdk_1 = __importStar(__webpack_require__(/*! @scrypted/sdk */ "./node_modules/@scrypted/sdk/dist/src/index.js"));
|
|
34065
34065
|
const mqtt_1 = __importDefault(__webpack_require__(/*! mqtt */ "./node_modules/mqtt/build/index.js"));
|
|
34066
34066
|
const { systemManager, deviceManager } = sdk_1.default;
|
|
34067
|
+
/** ---------- RUNTIME FLAGS (aggiornati da Settings) ---------- */
|
|
34068
|
+
const RUNTIME = {
|
|
34069
|
+
logSensors: false,
|
|
34070
|
+
logMqttAll: false,
|
|
34071
|
+
};
|
|
34072
|
+
function updateRuntimeFromStorage(get) {
|
|
34073
|
+
RUNTIME.logSensors = (get('logSensors') || '') === 'true';
|
|
34074
|
+
RUNTIME.logMqttAll = (get('logMqttAll') || '') === 'true';
|
|
34075
|
+
}
|
|
34067
34076
|
/** utils */
|
|
34068
34077
|
function truthy(v) {
|
|
34069
34078
|
if (!v)
|
|
@@ -34086,7 +34095,7 @@ const DEFAULT_OUTGOING = {
|
|
|
34086
34095
|
[sdk_1.SecuritySystemMode.AwayArmed]: 'arm_away',
|
|
34087
34096
|
[sdk_1.SecuritySystemMode.NightArmed]: 'arm_night',
|
|
34088
34097
|
};
|
|
34089
|
-
/**
|
|
34098
|
+
/** Parser loose con sinonimi (usato se strict=OFF) */
|
|
34090
34099
|
function payloadToModeLoose(payload) {
|
|
34091
34100
|
if (payload == null)
|
|
34092
34101
|
return;
|
|
@@ -34108,8 +34117,8 @@ class BaseMqttSensor extends sdk_1.ScryptedDeviceBase {
|
|
|
34108
34117
|
super(nativeId);
|
|
34109
34118
|
this.cfg = cfg;
|
|
34110
34119
|
}
|
|
34111
|
-
/** setter centralizzato + evento */
|
|
34112
|
-
setAndEmit(prop, val, iface) {
|
|
34120
|
+
/** setter centralizzato + evento + (log opzionale) */
|
|
34121
|
+
setAndEmit(prop, val, iface, logContext) {
|
|
34113
34122
|
const prev = this[prop];
|
|
34114
34123
|
if (prev === val)
|
|
34115
34124
|
return;
|
|
@@ -34120,6 +34129,11 @@ class BaseMqttSensor extends sdk_1.ScryptedDeviceBase {
|
|
|
34120
34129
|
catch (e) {
|
|
34121
34130
|
this.console?.warn?.('onDeviceEvent error', iface, e);
|
|
34122
34131
|
}
|
|
34132
|
+
if (RUNTIME.logSensors) {
|
|
34133
|
+
const label = logContext?.propLabel || prop;
|
|
34134
|
+
const extra = logContext?.topic ? ` (${logContext.topic}="${logContext.raw ?? ''}")` : '';
|
|
34135
|
+
this.console?.log?.(`[Sensor] ${this.cfg.name} [${this.cfg.id}] ${label} -> ${JSON.stringify(val)}${extra}`);
|
|
34136
|
+
}
|
|
34123
34137
|
}
|
|
34124
34138
|
/** Called by parent on each MQTT message */
|
|
34125
34139
|
handleMqtt(topic, payload) {
|
|
@@ -34128,75 +34142,58 @@ class BaseMqttSensor extends sdk_1.ScryptedDeviceBase {
|
|
|
34128
34142
|
// online
|
|
34129
34143
|
if (topic === this.cfg.topics.online) {
|
|
34130
34144
|
if (truthy(np) || np === 'online')
|
|
34131
|
-
this.setAndEmit('online', true, sdk_1.ScryptedInterface.Online);
|
|
34145
|
+
this.setAndEmit('online', true, sdk_1.ScryptedInterface.Online, { topic, raw, propLabel: 'online' });
|
|
34132
34146
|
if (falsy(np) || np === 'offline')
|
|
34133
|
-
this.setAndEmit('online', false, sdk_1.ScryptedInterface.Online);
|
|
34147
|
+
this.setAndEmit('online', false, sdk_1.ScryptedInterface.Online, { topic, raw, propLabel: 'online' });
|
|
34134
34148
|
}
|
|
34135
34149
|
// tamper
|
|
34136
34150
|
if (topic === this.cfg.topics.tamper) {
|
|
34137
34151
|
if (truthy(np) || ['tamper', 'intrusion', 'cover', 'motion', 'magnetic'].includes(np)) {
|
|
34138
34152
|
const value = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
|
|
34139
|
-
this.setAndEmit('tampered', value, sdk_1.ScryptedInterface.TamperSensor);
|
|
34153
|
+
this.setAndEmit('tampered', value, sdk_1.ScryptedInterface.TamperSensor, { topic, raw, propLabel: 'tampered' });
|
|
34140
34154
|
}
|
|
34141
34155
|
else if (falsy(np)) {
|
|
34142
|
-
this.setAndEmit('tampered', false, sdk_1.ScryptedInterface.TamperSensor);
|
|
34156
|
+
this.setAndEmit('tampered', false, sdk_1.ScryptedInterface.TamperSensor, { topic, raw, propLabel: 'tampered' });
|
|
34143
34157
|
}
|
|
34144
34158
|
}
|
|
34145
34159
|
// battery
|
|
34146
34160
|
if (topic === this.cfg.topics.batteryLevel) {
|
|
34147
34161
|
const n = clamp(parseFloat(raw), 0, 100);
|
|
34148
34162
|
if (Number.isFinite(n))
|
|
34149
|
-
this.setAndEmit('batteryLevel', n, sdk_1.ScryptedInterface.Battery);
|
|
34163
|
+
this.setAndEmit('batteryLevel', n, sdk_1.ScryptedInterface.Battery, { topic, raw, propLabel: 'batteryLevel' });
|
|
34150
34164
|
}
|
|
34151
34165
|
else if (topic === this.cfg.topics.lowBattery && !this.cfg.topics.batteryLevel) {
|
|
34166
|
+
// solo se abbiamo lowBattery (bool) ma NON batteryLevel
|
|
34152
34167
|
if (truthy(np))
|
|
34153
|
-
this.setAndEmit('batteryLevel', 10, sdk_1.ScryptedInterface.Battery);
|
|
34168
|
+
this.setAndEmit('batteryLevel', 10, sdk_1.ScryptedInterface.Battery, { topic, raw, propLabel: 'batteryLevel (low)' });
|
|
34154
34169
|
else if (falsy(np) && this.batteryLevel === undefined)
|
|
34155
|
-
this.setAndEmit('batteryLevel', 100, sdk_1.ScryptedInterface.Battery);
|
|
34170
|
+
this.setAndEmit('batteryLevel', 100, sdk_1.ScryptedInterface.Battery, { topic, raw, propLabel: 'batteryLevel (ok)' });
|
|
34156
34171
|
}
|
|
34157
34172
|
// primary handled by subclasses
|
|
34158
34173
|
this.handlePrimary(topic, np, raw);
|
|
34159
34174
|
}
|
|
34160
34175
|
}
|
|
34161
34176
|
class ContactMqttSensor extends BaseMqttSensor {
|
|
34162
|
-
handlePrimary(topic, np) {
|
|
34177
|
+
handlePrimary(topic, np, raw) {
|
|
34163
34178
|
if (topic === this.cfg.topics.contact) {
|
|
34164
34179
|
const v = truthy(np);
|
|
34165
|
-
this.setAndEmit
|
|
34166
|
-
if (this.setAndEmit === undefined) {
|
|
34167
|
-
if (this.entryOpen !== v) {
|
|
34168
|
-
this.entryOpen = v;
|
|
34169
|
-
this.onDeviceEvent(sdk_1.ScryptedInterface.EntrySensor, v);
|
|
34170
|
-
}
|
|
34171
|
-
}
|
|
34180
|
+
this.setAndEmit('entryOpen', v, sdk_1.ScryptedInterface.EntrySensor, { topic, raw, propLabel: 'entryOpen' });
|
|
34172
34181
|
}
|
|
34173
34182
|
}
|
|
34174
34183
|
}
|
|
34175
34184
|
class MotionMqttSensor extends BaseMqttSensor {
|
|
34176
|
-
handlePrimary(topic, np) {
|
|
34185
|
+
handlePrimary(topic, np, raw) {
|
|
34177
34186
|
if (topic === this.cfg.topics.motion) {
|
|
34178
34187
|
const v = truthy(np);
|
|
34179
|
-
this.setAndEmit
|
|
34180
|
-
if (this.setAndEmit === undefined) {
|
|
34181
|
-
if (this.motionDetected !== v) {
|
|
34182
|
-
this.motionDetected = v;
|
|
34183
|
-
this.onDeviceEvent(sdk_1.ScryptedInterface.MotionSensor, v);
|
|
34184
|
-
}
|
|
34185
|
-
}
|
|
34188
|
+
this.setAndEmit('motionDetected', v, sdk_1.ScryptedInterface.MotionSensor, { topic, raw, propLabel: 'motionDetected' });
|
|
34186
34189
|
}
|
|
34187
34190
|
}
|
|
34188
34191
|
}
|
|
34189
34192
|
class OccupancyMqttSensor extends BaseMqttSensor {
|
|
34190
|
-
handlePrimary(topic, np) {
|
|
34193
|
+
handlePrimary(topic, np, raw) {
|
|
34191
34194
|
if (topic === this.cfg.topics.occupancy) {
|
|
34192
34195
|
const v = truthy(np);
|
|
34193
|
-
this.setAndEmit
|
|
34194
|
-
if (this.setAndEmit === undefined) {
|
|
34195
|
-
if (this.occupied !== v) {
|
|
34196
|
-
this.occupied = v;
|
|
34197
|
-
this.onDeviceEvent(sdk_1.ScryptedInterface.OccupancySensor, v);
|
|
34198
|
-
}
|
|
34199
|
-
}
|
|
34196
|
+
this.setAndEmit('occupied', v, sdk_1.ScryptedInterface.OccupancySensor, { topic, raw, propLabel: 'occupied' });
|
|
34200
34197
|
}
|
|
34201
34198
|
}
|
|
34202
34199
|
}
|
|
@@ -34204,14 +34201,19 @@ class OccupancyMqttSensor extends BaseMqttSensor {
|
|
|
34204
34201
|
class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
34205
34202
|
constructor() {
|
|
34206
34203
|
super();
|
|
34204
|
+
// sensor management
|
|
34207
34205
|
this.sensorsCfg = [];
|
|
34208
34206
|
this.devices = new Map();
|
|
34207
|
+
// carica flag runtime
|
|
34208
|
+
updateRuntimeFromStorage((k) => this.storage.getItem(k) || '');
|
|
34209
|
+
// (facoltativo) Imposta il device type in UI
|
|
34209
34210
|
setTimeout(() => {
|
|
34210
34211
|
try {
|
|
34211
34212
|
systemManager.getDeviceById(this.id)?.setType?.(sdk_1.ScryptedDeviceType.SecuritySystem);
|
|
34212
34213
|
}
|
|
34213
34214
|
catch { }
|
|
34214
34215
|
});
|
|
34216
|
+
// Default state
|
|
34215
34217
|
this.securitySystemState = this.securitySystemState || {
|
|
34216
34218
|
mode: sdk_1.SecuritySystemMode.Disarmed,
|
|
34217
34219
|
supportedModes: [
|
|
@@ -34222,9 +34224,12 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34222
34224
|
],
|
|
34223
34225
|
};
|
|
34224
34226
|
this.online = this.online ?? false;
|
|
34227
|
+
// Load sensors config and announce devices
|
|
34225
34228
|
this.loadSensorsFromStorage();
|
|
34226
34229
|
this.discoverSensors().catch(e => this.console.error('discoverSensors error', e));
|
|
34230
|
+
// Connect on start
|
|
34227
34231
|
this.connectMqtt().catch(e => this.console.error('MQTT connect error:', e));
|
|
34232
|
+
// chiusura pulita del client MQTT ai reload/stop del plugin
|
|
34228
34233
|
try {
|
|
34229
34234
|
process.once('SIGTERM', () => { try {
|
|
34230
34235
|
this.client?.end(true);
|
|
@@ -34241,7 +34246,7 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34241
34246
|
}
|
|
34242
34247
|
catch { }
|
|
34243
34248
|
}
|
|
34244
|
-
|
|
34249
|
+
// ====== Strict parsing helpers ======
|
|
34245
34250
|
parseJsonArray(key, fallback) {
|
|
34246
34251
|
try {
|
|
34247
34252
|
const raw = (this.storage.getItem(key) || '').trim();
|
|
@@ -34256,45 +34261,42 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34256
34261
|
return fallback;
|
|
34257
34262
|
}
|
|
34258
34263
|
}
|
|
34259
|
-
getStrictTokens() {
|
|
34260
|
-
const target = this.parseJsonArray('targetStateValues', ['armed_home', 'armed_away', 'armed_night', 'disarmed']);
|
|
34261
|
-
const current = this.parseJsonArray('currentStateValues', ['armed_home', 'armed_away', 'armed_night', 'disarmed', 'triggered']);
|
|
34262
|
-
const triggered = this.parseJsonArray('triggeredValues', ['triggered', 'alarm']);
|
|
34263
|
-
const union = new Set([...target, ...current]);
|
|
34264
|
-
return {
|
|
34265
|
-
union,
|
|
34266
|
-
triggered: new Set(triggered),
|
|
34267
|
-
};
|
|
34268
|
-
}
|
|
34269
34264
|
useStrict() {
|
|
34270
34265
|
return this.storage.getItem('strictParsing') === 'true';
|
|
34271
34266
|
}
|
|
34272
34267
|
parseIncomingMode(payload) {
|
|
34273
|
-
const
|
|
34274
|
-
const np = normalize(p);
|
|
34268
|
+
const np = normalize(payload?.toString?.() ?? String(payload ?? ''));
|
|
34275
34269
|
if (!this.useStrict())
|
|
34276
34270
|
return payloadToModeLoose(np);
|
|
34277
|
-
|
|
34278
|
-
|
|
34279
|
-
if (union.has('disarmed') && np === 'disarmed')
|
|
34271
|
+
const currentVals = new Set(this.parseJsonArray('currentStateValues', ['armed_home', 'armed_away', 'armed_night', 'disarmed', 'triggered']));
|
|
34272
|
+
if (currentVals.has('disarmed') && np === 'disarmed')
|
|
34280
34273
|
return sdk_1.SecuritySystemMode.Disarmed;
|
|
34281
|
-
if (
|
|
34274
|
+
if (currentVals.has('armed_home') && np === 'armed_home')
|
|
34282
34275
|
return sdk_1.SecuritySystemMode.HomeArmed;
|
|
34283
|
-
if (
|
|
34276
|
+
if (currentVals.has('armed_away') && np === 'armed_away')
|
|
34284
34277
|
return sdk_1.SecuritySystemMode.AwayArmed;
|
|
34285
|
-
if (
|
|
34278
|
+
if (currentVals.has('armed_night') && np === 'armed_night')
|
|
34286
34279
|
return sdk_1.SecuritySystemMode.NightArmed;
|
|
34287
|
-
// transitori/altro: ignora
|
|
34288
34280
|
return undefined;
|
|
34289
34281
|
}
|
|
34290
34282
|
isTriggeredToken(np) {
|
|
34291
34283
|
if (this.useStrict()) {
|
|
34292
|
-
const
|
|
34284
|
+
const triggered = new Set(this.parseJsonArray('triggeredValues', ['triggered', 'alarm']));
|
|
34293
34285
|
return triggered.has(np);
|
|
34294
34286
|
}
|
|
34295
|
-
// loose
|
|
34296
34287
|
return np === 'triggered' || np === 'alarm';
|
|
34297
34288
|
}
|
|
34289
|
+
/** Token di publish preferito (strict=ON usa targetStateValues) */
|
|
34290
|
+
preferredTokenForMode(mode) {
|
|
34291
|
+
const t = this.parseJsonArray('targetStateValues', ['armed_home', 'armed_away', 'armed_night', 'disarmed']);
|
|
34292
|
+
const pick = (...cands) => cands.find(c => t.includes(c));
|
|
34293
|
+
switch (mode) {
|
|
34294
|
+
case sdk_1.SecuritySystemMode.Disarmed: return pick('disarmed', 'disarm') || 'disarmed';
|
|
34295
|
+
case sdk_1.SecuritySystemMode.HomeArmed: return pick('armed_home', 'arm_home', 'home', 'stay') || 'armed_home';
|
|
34296
|
+
case sdk_1.SecuritySystemMode.AwayArmed: return pick('armed_away', 'arm_away', 'away') || 'armed_away';
|
|
34297
|
+
case sdk_1.SecuritySystemMode.NightArmed: return pick('armed_night', 'arm_night', 'night', 'sleep') || 'armed_night';
|
|
34298
|
+
}
|
|
34299
|
+
}
|
|
34298
34300
|
// helpers persistenza
|
|
34299
34301
|
saveSensorsToStorage() {
|
|
34300
34302
|
try {
|
|
@@ -34327,6 +34329,14 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34327
34329
|
{ group: 'Parsing / State tokens', key: 'targetStateValues', title: 'Accepted Target State Values (JSON array)', placeholder: '["armed_home","armed_away","armed_night","disarmed"]', value: this.storage.getItem('targetStateValues') || '["armed_home","armed_away","armed_night","disarmed"]' },
|
|
34328
34330
|
{ group: 'Parsing / State tokens', key: 'currentStateValues', title: 'Accepted Current State Values (JSON array)', placeholder: '["armed_home","armed_away","armed_night","disarmed","triggered"]', value: this.storage.getItem('currentStateValues') || '["armed_home","armed_away","armed_night","disarmed","triggered"]' },
|
|
34329
34331
|
{ group: 'Parsing / State tokens', key: 'triggeredValues', title: 'Triggered tokens (JSON array)', placeholder: '["triggered","alarm"]', value: this.storage.getItem('triggeredValues') || '["triggered","alarm"]' },
|
|
34332
|
+
// --- Publish Payloads (override) ---
|
|
34333
|
+
{ group: 'Publish Payloads (override)', key: 'payloadDisarm', title: 'Payload for Disarm', placeholder: 'disarmed', value: this.storage.getItem('payloadDisarm') || '', description: 'Se vuoto: usa targetStateValues (strict ON) o i default arm_*/disarm (strict OFF).' },
|
|
34334
|
+
{ group: 'Publish Payloads (override)', key: 'payloadHome', title: 'Payload for Home Armed', placeholder: 'armed_home', value: this.storage.getItem('payloadHome') || '' },
|
|
34335
|
+
{ group: 'Publish Payloads (override)', key: 'payloadAway', title: 'Payload for Away Armed', placeholder: 'armed_away', value: this.storage.getItem('payloadAway') || '' },
|
|
34336
|
+
{ group: 'Publish Payloads (override)', key: 'payloadNight', title: 'Payload for Night Armed', placeholder: 'armed_night', value: this.storage.getItem('payloadNight') || '' },
|
|
34337
|
+
// --- Logging ---
|
|
34338
|
+
{ group: 'Logging', key: 'logSensors', title: 'Log sensor state changes', type: 'boolean', value: this.storage.getItem('logSensors') === 'true' },
|
|
34339
|
+
{ group: 'Logging', key: 'logMqttAll', title: 'Log ALL MQTT messages', type: 'boolean', value: this.storage.getItem('logMqttAll') === 'true', description: 'Attenzione: molto verboso.' },
|
|
34330
34340
|
];
|
|
34331
34341
|
// ---- UI Add Sensor ----
|
|
34332
34342
|
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: 'Fill the fields above and toggle this on to create the sensor. After creation, restart this plugin to see the accessory listed below. To show it in HomeKit, restart the HomeKit plugin as well.' });
|
|
@@ -34334,22 +34344,22 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34334
34344
|
for (const cfg of this.sensorsCfg) {
|
|
34335
34345
|
const gid = `Sensor: ${cfg.name} [${cfg.id}]`;
|
|
34336
34346
|
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'] });
|
|
34337
|
-
if (cfg.kind === 'contact')
|
|
34347
|
+
if (cfg.kind === 'contact')
|
|
34338
34348
|
out.push({ group: gid, key: `sensor.${cfg.id}.topic.contact`, title: 'Contact State Topic', value: cfg.topics.contact || '', placeholder: 'paradox/states/zones/XYZ/open' });
|
|
34339
|
-
|
|
34340
|
-
else if (cfg.kind === 'motion') {
|
|
34349
|
+
else if (cfg.kind === 'motion')
|
|
34341
34350
|
out.push({ group: gid, key: `sensor.${cfg.id}.topic.motion`, title: 'Motion Detected Topic', value: cfg.topics.motion || '', placeholder: 'paradox/states/zones/XYZ/open' });
|
|
34342
|
-
|
|
34343
|
-
else {
|
|
34351
|
+
else
|
|
34344
34352
|
out.push({ group: gid, key: `sensor.${cfg.id}.topic.occupancy`, title: 'Occupancy Detected Topic', value: cfg.topics.occupancy || '', placeholder: 'paradox/states/zones/XYZ/open' });
|
|
34345
|
-
}
|
|
34346
34353
|
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' });
|
|
34347
34354
|
}
|
|
34348
34355
|
return out;
|
|
34349
34356
|
}
|
|
34350
34357
|
async putSetting(key, value) {
|
|
34358
|
+
// salva sempre nella storage la value del campo (così resta in UI)
|
|
34351
34359
|
this.storage.setItem(key, String(value));
|
|
34352
|
-
//
|
|
34360
|
+
// aggiorna i flag runtime ogni volta
|
|
34361
|
+
updateRuntimeFromStorage((k) => this.storage.getItem(k) || '');
|
|
34362
|
+
// --- Add Sensor workflow ---
|
|
34353
34363
|
if (key === 'new.create' && String(value) === 'true') {
|
|
34354
34364
|
const id = (this.storage.getItem('new.id') || '').trim();
|
|
34355
34365
|
const name = (this.storage.getItem('new.name') || '').trim() || id;
|
|
@@ -34364,6 +34374,7 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34364
34374
|
}
|
|
34365
34375
|
this.sensorsCfg.push({ id, name, kind, topics: {} });
|
|
34366
34376
|
this.saveSensorsToStorage();
|
|
34377
|
+
// pulizia campi "new.*"
|
|
34367
34378
|
this.storage.removeItem('new.id');
|
|
34368
34379
|
this.storage.removeItem('new.name');
|
|
34369
34380
|
this.storage.removeItem('new.kind');
|
|
@@ -34372,7 +34383,7 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34372
34383
|
await this.connectMqtt(true);
|
|
34373
34384
|
return;
|
|
34374
34385
|
}
|
|
34375
|
-
// Edit/Remove sensore
|
|
34386
|
+
// --- Edit/Remove sensore esistente ---
|
|
34376
34387
|
const m = key.match(/^sensor\.([^\.]+)\.(.+)$/);
|
|
34377
34388
|
if (m) {
|
|
34378
34389
|
const sid = m[1];
|
|
@@ -34383,6 +34394,7 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34383
34394
|
return;
|
|
34384
34395
|
}
|
|
34385
34396
|
if (prop === 'remove' && String(value) === 'true') {
|
|
34397
|
+
// elimina
|
|
34386
34398
|
this.sensorsCfg = this.sensorsCfg.filter(s => s.id !== sid);
|
|
34387
34399
|
this.saveSensorsToStorage();
|
|
34388
34400
|
try {
|
|
@@ -34390,6 +34402,7 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34390
34402
|
deviceManager.onDeviceRemoved?.(`sensor:${sid}`);
|
|
34391
34403
|
}
|
|
34392
34404
|
catch { }
|
|
34405
|
+
// pulisci flag
|
|
34393
34406
|
this.storage.removeItem(key);
|
|
34394
34407
|
await this.discoverSensors();
|
|
34395
34408
|
await this.connectMqtt(true);
|
|
@@ -34408,7 +34421,7 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34408
34421
|
await this.connectMqtt(true);
|
|
34409
34422
|
return;
|
|
34410
34423
|
}
|
|
34411
|
-
//
|
|
34424
|
+
// --- Altro (MQTT / Alarm settings / parsing / payloads / logging) ---
|
|
34412
34425
|
if (key === 'sensorsJson') {
|
|
34413
34426
|
this.loadSensorsFromStorage();
|
|
34414
34427
|
await this.discoverSensors();
|
|
@@ -34438,6 +34451,7 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34438
34451
|
try {
|
|
34439
34452
|
const raw = this.storage.getItem('sensorsJson') || '[]';
|
|
34440
34453
|
const parsed = JSON.parse(raw);
|
|
34454
|
+
// sanitize
|
|
34441
34455
|
this.sensorsCfg = (parsed || []).filter(x => x && x.id && x.name && x.kind && x.topics);
|
|
34442
34456
|
}
|
|
34443
34457
|
catch (e) {
|
|
@@ -34447,24 +34461,25 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34447
34461
|
}
|
|
34448
34462
|
/** ===== discoverSensors: annuncia PRIMA, istanzia DOPO ===== */
|
|
34449
34463
|
async discoverSensors() {
|
|
34450
|
-
// 1)
|
|
34464
|
+
// 1) manifest
|
|
34451
34465
|
const manifests = this.sensorsCfg.map(cfg => {
|
|
34452
34466
|
const nativeId = `sensor:${cfg.id}`;
|
|
34453
34467
|
const t = cfg.topics || {};
|
|
34454
34468
|
const interfaces = [sdk_1.ScryptedInterface.Online];
|
|
34455
|
-
if (t.tamper)
|
|
34456
|
-
interfaces.push(sdk_1.ScryptedInterface.TamperSensor);
|
|
34457
34469
|
if (cfg.kind === 'contact')
|
|
34458
34470
|
interfaces.unshift(sdk_1.ScryptedInterface.EntrySensor);
|
|
34459
34471
|
else if (cfg.kind === 'motion')
|
|
34460
34472
|
interfaces.unshift(sdk_1.ScryptedInterface.MotionSensor);
|
|
34461
34473
|
else
|
|
34462
34474
|
interfaces.unshift(sdk_1.ScryptedInterface.OccupancySensor);
|
|
34475
|
+
if (t.tamper)
|
|
34476
|
+
interfaces.push(sdk_1.ScryptedInterface.TamperSensor);
|
|
34477
|
+
// Battery solo se definita davvero
|
|
34463
34478
|
if ((t.batteryLevel && t.batteryLevel.trim()) || (t.lowBattery && t.lowBattery.trim()))
|
|
34464
34479
|
interfaces.push(sdk_1.ScryptedInterface.Battery);
|
|
34465
34480
|
return { nativeId, name: cfg.name, type: sdk_1.ScryptedDeviceType.Sensor, interfaces };
|
|
34466
34481
|
});
|
|
34467
|
-
// 2)
|
|
34482
|
+
// 2) annuncio
|
|
34468
34483
|
const dmAny = deviceManager;
|
|
34469
34484
|
if (typeof dmAny.onDevicesChanged === 'function') {
|
|
34470
34485
|
dmAny.onDevicesChanged({ devices: manifests });
|
|
@@ -34476,7 +34491,7 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34476
34491
|
this.console.log('Annunciato:', m.nativeId);
|
|
34477
34492
|
}
|
|
34478
34493
|
}
|
|
34479
|
-
// 3)
|
|
34494
|
+
// 3) istanzia/aggiorna
|
|
34480
34495
|
for (const cfg of this.sensorsCfg) {
|
|
34481
34496
|
const nativeId = `sensor:${cfg.id}`;
|
|
34482
34497
|
let dev = this.devices.get(nativeId);
|
|
@@ -34492,7 +34507,16 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34492
34507
|
else {
|
|
34493
34508
|
dev.cfg = cfg;
|
|
34494
34509
|
}
|
|
34495
|
-
//
|
|
34510
|
+
// default OK per battery se non abbiamo ancora un valore
|
|
34511
|
+
const hasBattery = !!(cfg.topics.batteryLevel && cfg.topics.batteryLevel.trim())
|
|
34512
|
+
|| !!(cfg.topics.lowBattery && cfg.topics.lowBattery.trim());
|
|
34513
|
+
if (hasBattery && dev.batteryLevel === undefined) {
|
|
34514
|
+
dev.batteryLevel = 100;
|
|
34515
|
+
try {
|
|
34516
|
+
dev.onDeviceEvent(sdk_1.ScryptedInterface.Battery, 100);
|
|
34517
|
+
}
|
|
34518
|
+
catch { }
|
|
34519
|
+
}
|
|
34496
34520
|
}
|
|
34497
34521
|
// 4) cleanup
|
|
34498
34522
|
const announced = new Set(manifests.map(m => m.nativeId));
|
|
@@ -34515,7 +34539,13 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34515
34539
|
const clientId = this.storage.getItem('clientId') || 'scrypted-paradox';
|
|
34516
34540
|
const tls = this.storage.getItem('tls') === 'true';
|
|
34517
34541
|
const rejectUnauthorized = this.storage.getItem('rejectUnauthorized') !== 'false';
|
|
34518
|
-
const opts = {
|
|
34542
|
+
const opts = {
|
|
34543
|
+
clientId,
|
|
34544
|
+
username,
|
|
34545
|
+
password,
|
|
34546
|
+
clean: true,
|
|
34547
|
+
reconnectPeriod: 3000,
|
|
34548
|
+
};
|
|
34519
34549
|
if (tls) {
|
|
34520
34550
|
opts.protocol = 'mqtts';
|
|
34521
34551
|
opts.rejectUnauthorized = rejectUnauthorized;
|
|
@@ -34524,11 +34554,13 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34524
34554
|
}
|
|
34525
34555
|
collectAllSubscriptions() {
|
|
34526
34556
|
const subs = new Set();
|
|
34557
|
+
// alarm
|
|
34527
34558
|
for (const k of ['topicGetTarget', 'topicGetCurrent', 'topicTamper', 'topicOnline']) {
|
|
34528
34559
|
const v = this.storage.getItem(k);
|
|
34529
34560
|
if (v)
|
|
34530
34561
|
subs.add(v);
|
|
34531
34562
|
}
|
|
34563
|
+
// sensors
|
|
34532
34564
|
for (const s of this.sensorsCfg) {
|
|
34533
34565
|
const t = s.topics || {};
|
|
34534
34566
|
[t.contact, t.motion, t.occupancy, t.batteryLevel, t.lowBattery, t.tamper, t.online]
|
|
@@ -34547,13 +34579,13 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34547
34579
|
this.client.end(true);
|
|
34548
34580
|
}
|
|
34549
34581
|
catch { }
|
|
34550
|
-
;
|
|
34551
34582
|
this.client = undefined;
|
|
34552
34583
|
}
|
|
34553
34584
|
const { url, opts } = this.getMqttOptions();
|
|
34554
34585
|
this.console.log(`Connecting MQTT ${url} ...`);
|
|
34555
34586
|
const client = mqtt_1.default.connect(url, opts);
|
|
34556
34587
|
this.client = client;
|
|
34588
|
+
// cache alarm topics
|
|
34557
34589
|
const tTarget = this.storage.getItem('topicGetTarget') || '';
|
|
34558
34590
|
const tCurrent = this.storage.getItem('topicGetCurrent') || '';
|
|
34559
34591
|
const tTamper = this.storage.getItem('topicTamper') || '';
|
|
@@ -34583,6 +34615,9 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34583
34615
|
try {
|
|
34584
34616
|
const p = payload?.toString() ?? '';
|
|
34585
34617
|
const np = normalize(p);
|
|
34618
|
+
if (RUNTIME.logMqttAll)
|
|
34619
|
+
this.console.log(`[MQTT] ${topic} -> "${p}"`);
|
|
34620
|
+
// ---- Alarm handling ----
|
|
34586
34621
|
if (topic === tOnline) {
|
|
34587
34622
|
if (truthy(np) || np === 'online') {
|
|
34588
34623
|
this.online = true;
|
|
@@ -34637,11 +34672,14 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34637
34672
|
this.onDeviceEvent(sdk_1.ScryptedInterface.SecuritySystem, newState);
|
|
34638
34673
|
}
|
|
34639
34674
|
catch { }
|
|
34675
|
+
if (RUNTIME.logSensors)
|
|
34676
|
+
this.console.log(`[Alarm] current: mode=${newState.mode} triggered=${!!newState.triggered} (${topic}="${p}")`);
|
|
34640
34677
|
return;
|
|
34641
34678
|
}
|
|
34642
34679
|
if (topic === tTarget) {
|
|
34643
34680
|
this.pendingTarget = this.parseIncomingMode(payload);
|
|
34644
|
-
|
|
34681
|
+
if (RUNTIME.logSensors)
|
|
34682
|
+
this.console.log(`[Alarm] target reported: "${p}" -> ${this.pendingTarget}`);
|
|
34645
34683
|
return;
|
|
34646
34684
|
}
|
|
34647
34685
|
// ---- Sensor dispatch ----
|
|
@@ -34666,8 +34704,25 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34666
34704
|
this.client.publish(topic, payload, { qos, retain }, (err) => {
|
|
34667
34705
|
if (err)
|
|
34668
34706
|
this.console.error('publish error', err);
|
|
34707
|
+
else if (RUNTIME.logSensors)
|
|
34708
|
+
this.console.log(`[Alarm] published target "${payload}" to ${topic}`);
|
|
34669
34709
|
});
|
|
34670
34710
|
}
|
|
34711
|
+
/** Payload publish: override → strict tokens → default arm_* */
|
|
34712
|
+
getOutgoing(mode) {
|
|
34713
|
+
const overrides = {
|
|
34714
|
+
[sdk_1.SecuritySystemMode.Disarmed]: this.storage.getItem('payloadDisarm') || null,
|
|
34715
|
+
[sdk_1.SecuritySystemMode.HomeArmed]: this.storage.getItem('payloadHome') || null,
|
|
34716
|
+
[sdk_1.SecuritySystemMode.AwayArmed]: this.storage.getItem('payloadAway') || null,
|
|
34717
|
+
[sdk_1.SecuritySystemMode.NightArmed]: this.storage.getItem('payloadNight') || null,
|
|
34718
|
+
};
|
|
34719
|
+
const override = overrides[mode];
|
|
34720
|
+
if (override && override.trim().length)
|
|
34721
|
+
return override.trim();
|
|
34722
|
+
if (this.useStrict())
|
|
34723
|
+
return this.preferredTokenForMode(mode);
|
|
34724
|
+
return DEFAULT_OUTGOING[mode];
|
|
34725
|
+
}
|
|
34671
34726
|
async armSecuritySystem(mode) {
|
|
34672
34727
|
const payload = this.getOutgoing(mode);
|
|
34673
34728
|
this.console.log('armSecuritySystem', mode, '->', payload);
|
|
@@ -34680,15 +34735,6 @@ class ParadoxMqttSecuritySystem extends sdk_1.ScryptedDeviceBase {
|
|
|
34680
34735
|
this.pendingTarget = sdk_1.SecuritySystemMode.Disarmed;
|
|
34681
34736
|
this.publishSetTarget(payload);
|
|
34682
34737
|
}
|
|
34683
|
-
getOutgoing(mode) {
|
|
34684
|
-
const map = {
|
|
34685
|
-
[sdk_1.SecuritySystemMode.Disarmed]: this.storage.getItem('payloadDisarm') || DEFAULT_OUTGOING[sdk_1.SecuritySystemMode.Disarmed],
|
|
34686
|
-
[sdk_1.SecuritySystemMode.HomeArmed]: this.storage.getItem('payloadHome') || DEFAULT_OUTGOING[sdk_1.SecuritySystemMode.HomeArmed],
|
|
34687
|
-
[sdk_1.SecuritySystemMode.AwayArmed]: this.storage.getItem('payloadAway') || DEFAULT_OUTGOING[sdk_1.SecuritySystemMode.AwayArmed],
|
|
34688
|
-
[sdk_1.SecuritySystemMode.NightArmed]: this.storage.getItem('payloadNight') || DEFAULT_OUTGOING[sdk_1.SecuritySystemMode.NightArmed],
|
|
34689
|
-
};
|
|
34690
|
-
return map[mode];
|
|
34691
|
-
}
|
|
34692
34738
|
}
|
|
34693
34739
|
exports["default"] = ParadoxMqttSecuritySystem;
|
|
34694
34740
|
|
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED