@rfranzoi/scrypted-mqtt-securitysystem 1.0.40 → 1.0.41
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 +141 -154
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
package/dist/main.nodejs.js
CHANGED
|
@@ -34028,23 +34028,25 @@ 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
|
-
|
|
34035
|
-
|
|
34036
|
-
|
|
34037
|
-
|
|
34038
|
-
|
|
34039
|
-
|
|
34040
|
-
|
|
34041
|
-
}
|
|
34042
|
-
// Carica lo SDK
|
|
34031
|
+
// --- Silence ONLY the optional sdk.json warning from @scrypted/sdk ---
|
|
34032
|
+
const __origConsoleError = console.error.bind(console);
|
|
34033
|
+
console.error = (...args) => {
|
|
34034
|
+
const first = args?.[0];
|
|
34035
|
+
const msg = typeof first === 'string' ? first : (first?.message || '');
|
|
34036
|
+
if (typeof msg === 'string' && msg.includes('failed to load custom interface descriptors')) {
|
|
34037
|
+
// swallow just this warning
|
|
34038
|
+
return;
|
|
34039
|
+
}
|
|
34040
|
+
__origConsoleError(...args);
|
|
34041
|
+
};
|
|
34042
|
+
// Carica lo SDK (non tipizziamo qui per non perdere le proprietà runtime)
|
|
34043
34043
|
const sdk = __webpack_require__(/*! @scrypted/sdk */ "./node_modules/@scrypted/sdk/dist/src/index.js");
|
|
34044
|
-
// Valori runtime (enum
|
|
34045
|
-
const { ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, // enum
|
|
34046
|
-
SecuritySystemMode, // enum
|
|
34047
|
-
systemManager,
|
|
34044
|
+
// Valori runtime (enum/classi/manager) dal modulo SDK
|
|
34045
|
+
const { ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, // valore (enum)
|
|
34046
|
+
SecuritySystemMode, // valore (enum)
|
|
34047
|
+
systemManager,
|
|
34048
|
+
// ⚠️ NON destrutturare deviceManager: va letto sempre "al volo" da sdk.deviceManager.
|
|
34049
|
+
} = sdk;
|
|
34048
34050
|
const mqtt_1 = __importDefault(__webpack_require__(/*! mqtt */ "./node_modules/mqtt/build/index.js"));
|
|
34049
34051
|
/** utils */
|
|
34050
34052
|
function truthy(v) {
|
|
@@ -34059,16 +34061,21 @@ function falsy(v) {
|
|
|
34059
34061
|
const s = v.toString().trim().toLowerCase();
|
|
34060
34062
|
return s === '0' || s === 'false' || s === 'offline' || s === 'no' || s === 'off';
|
|
34061
34063
|
}
|
|
34062
|
-
function normalize(s) {
|
|
34063
|
-
|
|
34064
|
-
|
|
34064
|
+
function normalize(s) {
|
|
34065
|
+
return (s || '').trim().toLowerCase();
|
|
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. */
|
|
34065
34072
|
const DEFAULT_OUTGOING = {
|
|
34066
34073
|
[SecuritySystemMode.Disarmed]: 'disarm',
|
|
34067
34074
|
[SecuritySystemMode.HomeArmed]: 'arm_home',
|
|
34068
34075
|
[SecuritySystemMode.AwayArmed]: 'arm_away',
|
|
34069
34076
|
[SecuritySystemMode.NightArmed]: 'arm_night',
|
|
34070
34077
|
};
|
|
34071
|
-
/** Parse incoming payload -> final mode (
|
|
34078
|
+
/** Parse incoming payload -> final mode (ignore transition states) */
|
|
34072
34079
|
function payloadToMode(payload) {
|
|
34073
34080
|
if (payload == null)
|
|
34074
34081
|
return;
|
|
@@ -34081,6 +34088,7 @@ function payloadToMode(payload) {
|
|
|
34081
34088
|
return SecuritySystemMode.AwayArmed;
|
|
34082
34089
|
if (['arm_night', 'night', 'armed_night', 'sleep', 'arm_sleep', 'armed_sleep'].includes(p))
|
|
34083
34090
|
return SecuritySystemMode.NightArmed;
|
|
34091
|
+
// transitori: non cambiano il mode
|
|
34084
34092
|
if (['entry_delay', 'exit_delay', 'pending', 'arming', 'disarming'].includes(p))
|
|
34085
34093
|
return undefined;
|
|
34086
34094
|
return undefined;
|
|
@@ -34090,66 +34098,76 @@ class BaseMqttSensor extends ScryptedDeviceBase {
|
|
|
34090
34098
|
super(nativeId);
|
|
34091
34099
|
this.cfg = cfg;
|
|
34092
34100
|
}
|
|
34101
|
+
/** Called by parent on each MQTT message */
|
|
34093
34102
|
handleMqtt(topic, payload) {
|
|
34094
34103
|
const p = payload?.toString() ?? '';
|
|
34095
34104
|
const np = normalize(p);
|
|
34105
|
+
// online
|
|
34096
34106
|
if (topic === this.cfg.topics.online) {
|
|
34097
34107
|
if (truthy(np) || np === 'online')
|
|
34098
34108
|
this.online = true;
|
|
34099
34109
|
if (falsy(np) || np === 'offline')
|
|
34100
34110
|
this.online = false;
|
|
34101
34111
|
}
|
|
34112
|
+
// tamper
|
|
34102
34113
|
if (topic === this.cfg.topics.tamper) {
|
|
34103
34114
|
if (truthy(np) || ['tamper', 'intrusion', 'cover', 'motion', 'magnetic'].includes(np)) {
|
|
34104
34115
|
this.tampered = ['cover', 'intrusion', 'motion', 'magnetic'].find(x => x === np) || true;
|
|
34105
34116
|
}
|
|
34106
|
-
else if (falsy(np))
|
|
34117
|
+
else if (falsy(np)) {
|
|
34107
34118
|
this.tampered = false;
|
|
34119
|
+
}
|
|
34108
34120
|
}
|
|
34121
|
+
// battery
|
|
34109
34122
|
if (topic === this.cfg.topics.batteryLevel) {
|
|
34110
34123
|
const n = clamp(parseFloat(p), 0, 100);
|
|
34111
34124
|
if (isFinite(n))
|
|
34112
34125
|
this.batteryLevel = n;
|
|
34113
34126
|
}
|
|
34114
34127
|
else if (topic === this.cfg.topics.lowBattery && !this.cfg.topics.batteryLevel) {
|
|
34128
|
+
// se abbiamo SOLO lowBattery (bool):
|
|
34115
34129
|
this.batteryLevel = truthy(np) ? 10 : 100;
|
|
34116
34130
|
}
|
|
34131
|
+
// primary handled by subclasses
|
|
34117
34132
|
this.handlePrimary(topic, np, p);
|
|
34118
34133
|
}
|
|
34119
34134
|
}
|
|
34120
34135
|
class ContactMqttSensor extends BaseMqttSensor {
|
|
34121
34136
|
handlePrimary(topic, np) {
|
|
34122
|
-
if (topic === this.cfg.topics.contact)
|
|
34137
|
+
if (topic === this.cfg.topics.contact) {
|
|
34123
34138
|
this.entryOpen = truthy(np);
|
|
34139
|
+
}
|
|
34124
34140
|
}
|
|
34125
34141
|
}
|
|
34126
34142
|
class MotionMqttSensor extends BaseMqttSensor {
|
|
34127
34143
|
handlePrimary(topic, np) {
|
|
34128
|
-
if (topic === this.cfg.topics.motion)
|
|
34144
|
+
if (topic === this.cfg.topics.motion) {
|
|
34129
34145
|
this.motionDetected = truthy(np);
|
|
34146
|
+
}
|
|
34130
34147
|
}
|
|
34131
34148
|
}
|
|
34132
34149
|
class OccupancyMqttSensor extends BaseMqttSensor {
|
|
34133
34150
|
handlePrimary(topic, np) {
|
|
34134
|
-
if (topic === this.cfg.topics.occupancy)
|
|
34151
|
+
if (topic === this.cfg.topics.occupancy) {
|
|
34135
34152
|
this.occupied = truthy(np);
|
|
34153
|
+
}
|
|
34136
34154
|
}
|
|
34137
34155
|
}
|
|
34138
34156
|
/** ----------------- Main Plugin ----------------- */
|
|
34139
34157
|
class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
34140
34158
|
constructor() {
|
|
34141
34159
|
super();
|
|
34160
|
+
// sensor management
|
|
34142
34161
|
this.sensorsCfg = [];
|
|
34143
34162
|
this.devices = new Map();
|
|
34144
|
-
|
|
34145
|
-
// Tipo in UI (best-effort)
|
|
34163
|
+
// (facoltativo) Imposta il device type in UI
|
|
34146
34164
|
setTimeout(() => {
|
|
34147
34165
|
try {
|
|
34148
34166
|
systemManager.getDeviceById(this.id)?.setType?.(ScryptedDeviceType.SecuritySystem);
|
|
34149
34167
|
}
|
|
34150
34168
|
catch { }
|
|
34151
34169
|
});
|
|
34152
|
-
//
|
|
34170
|
+
// Default state
|
|
34153
34171
|
this.securitySystemState = this.securitySystemState || {
|
|
34154
34172
|
mode: SecuritySystemMode.Disarmed,
|
|
34155
34173
|
supportedModes: [
|
|
@@ -34160,12 +34178,12 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34160
34178
|
],
|
|
34161
34179
|
};
|
|
34162
34180
|
this.online = this.online ?? false;
|
|
34163
|
-
//
|
|
34181
|
+
// Load sensors config and announce devices
|
|
34164
34182
|
this.loadSensorsFromStorage();
|
|
34165
|
-
this.
|
|
34166
|
-
// Connect
|
|
34183
|
+
this.discoverSensors().catch((e) => this.console.error('discoverSensors error', e));
|
|
34184
|
+
// Connect on start
|
|
34167
34185
|
this.connectMqtt().catch((e) => this.console.error('MQTT connect error:', e));
|
|
34168
|
-
//
|
|
34186
|
+
// chiusura pulita del client MQTT ai reload/stop del plugin
|
|
34169
34187
|
try {
|
|
34170
34188
|
process.once('SIGTERM', () => { try {
|
|
34171
34189
|
this.client?.end(true);
|
|
@@ -34182,7 +34200,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34182
34200
|
}
|
|
34183
34201
|
catch { }
|
|
34184
34202
|
}
|
|
34185
|
-
|
|
34203
|
+
// helpers persistenza
|
|
34186
34204
|
saveSensorsToStorage() {
|
|
34187
34205
|
try {
|
|
34188
34206
|
this.storage.setItem('sensorsJson', JSON.stringify(this.sensorsCfg));
|
|
@@ -34191,14 +34209,17 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34191
34209
|
this.console.error('saveSensorsToStorage error', e);
|
|
34192
34210
|
}
|
|
34193
34211
|
}
|
|
34212
|
+
/** ---- Settings UI ---- */
|
|
34194
34213
|
async getSettings() {
|
|
34195
34214
|
const out = [
|
|
34215
|
+
// MQTT Core
|
|
34196
34216
|
{ 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' },
|
|
34197
34217
|
{ group: 'MQTT', key: 'username', title: 'Username', type: 'string', value: this.storage.getItem('username') || '' },
|
|
34198
34218
|
{ group: 'MQTT', key: 'password', title: 'Password', type: 'password', value: this.storage.getItem('password') || '' },
|
|
34199
34219
|
{ group: 'MQTT', key: 'clientId', title: 'Client ID', placeholder: 'scrypted-paradox', value: this.storage.getItem('clientId') || 'scrypted-paradox' },
|
|
34200
34220
|
{ group: 'MQTT', key: 'tls', title: 'Use TLS', type: 'boolean', value: this.storage.getItem('tls') === 'true' },
|
|
34201
34221
|
{ 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
|
|
34202
34223
|
{ group: 'Alarm Topics', key: 'topicSetTarget', title: 'Set Target State (publish)', placeholder: 'paradox/control/partitions/Area_1', value: this.storage.getItem('topicSetTarget') || '' },
|
|
34203
34224
|
{ group: 'Alarm Topics', key: 'topicGetTarget', title: 'Get Target State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/target_state', value: this.storage.getItem('topicGetTarget') || '' },
|
|
34204
34225
|
{ group: 'Alarm Topics', key: 'topicGetCurrent', title: 'Get Current State (subscribe)', placeholder: 'paradox/states/partitions/Area_1/current_state', value: this.storage.getItem('topicGetCurrent') || '' },
|
|
@@ -34206,69 +34227,32 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34206
34227
|
{ group: 'Alarm Topics', key: 'topicOnline', title: 'Get Online (subscribe)', placeholder: 'paradox/interface/availability', value: this.storage.getItem('topicOnline') || '' },
|
|
34207
34228
|
{ group: 'Publish Options', key: 'qos', title: 'QoS', type: 'integer', value: parseInt(this.storage.getItem('qos') || '0') },
|
|
34208
34229
|
{ 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.' },
|
|
34213
34230
|
];
|
|
34214
|
-
// ---- Add Sensor
|
|
34215
|
-
out.push({ group: 'Add Sensor', key: 'new.id', title: 'New Sensor ID', placeholder: 'porta-
|
|
34216
|
-
// ----
|
|
34231
|
+
// ---- UI Add Sensor ----
|
|
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: '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.' });
|
|
34233
|
+
// ---- UI per sensori esistenti ----
|
|
34217
34234
|
for (const cfg of this.sensorsCfg) {
|
|
34218
34235
|
const gid = `Sensor: ${cfg.name} [${cfg.id}]`;
|
|
34219
|
-
out.push({ group: gid, key: `sensor.${cfg.id}.
|
|
34220
|
-
|
|
34221
|
-
|
|
34222
|
-
|
|
34223
|
-
|
|
34224
|
-
else
|
|
34225
|
-
out.push({ group: gid, key: `sensor.${cfg.id}.topic.
|
|
34236
|
+
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
|
+
// primary per tipo
|
|
34238
|
+
if (cfg.kind === 'contact') {
|
|
34239
|
+
out.push({ group: gid, key: `sensor.${cfg.id}.topic.contact`, title: 'Contact State Topic', value: cfg.topics.contact || '', placeholder: 'paradox/states/zones/XYZ/open' });
|
|
34240
|
+
}
|
|
34241
|
+
else if (cfg.kind === 'motion') {
|
|
34242
|
+
out.push({ group: gid, key: `sensor.${cfg.id}.topic.motion`, title: 'Motion Detected Topic', value: cfg.topics.motion || '', placeholder: 'paradox/states/zones/XYZ/open' });
|
|
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
|
|
34226
34248
|
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' });
|
|
34227
34249
|
}
|
|
34228
34250
|
return out;
|
|
34229
34251
|
}
|
|
34230
34252
|
async putSetting(key, value) {
|
|
34253
|
+
// salva sempre nella storage la value del campo (così resta in UI)
|
|
34231
34254
|
this.storage.setItem(key, String(value));
|
|
34232
|
-
//
|
|
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 ----
|
|
34255
|
+
// --- Add Sensor workflow ---
|
|
34272
34256
|
if (key === 'new.create' && String(value) === 'true') {
|
|
34273
34257
|
const id = (this.storage.getItem('new.id') || '').trim();
|
|
34274
34258
|
const name = (this.storage.getItem('new.name') || '').trim() || id;
|
|
@@ -34283,15 +34267,16 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34283
34267
|
}
|
|
34284
34268
|
this.sensorsCfg.push({ id, name, kind, topics: {} });
|
|
34285
34269
|
this.saveSensorsToStorage();
|
|
34270
|
+
// pulisci i campi "new.*"
|
|
34286
34271
|
this.storage.removeItem('new.id');
|
|
34287
34272
|
this.storage.removeItem('new.name');
|
|
34288
34273
|
this.storage.removeItem('new.kind');
|
|
34289
34274
|
this.storage.removeItem('new.create');
|
|
34290
|
-
this.
|
|
34275
|
+
await this.discoverSensors();
|
|
34291
34276
|
await this.connectMqtt(true);
|
|
34292
34277
|
return;
|
|
34293
34278
|
}
|
|
34294
|
-
//
|
|
34279
|
+
// --- Edit/Remove sensore esistente ---
|
|
34295
34280
|
const m = key.match(/^sensor\.([^\.]+)\.(.+)$/);
|
|
34296
34281
|
if (m) {
|
|
34297
34282
|
const sid = m[1];
|
|
@@ -34301,60 +34286,41 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34301
34286
|
this.console.warn('putSetting: sensor non trovato', sid);
|
|
34302
34287
|
return;
|
|
34303
34288
|
}
|
|
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
|
|
34328
34289
|
if (prop === 'remove' && String(value) === 'true') {
|
|
34290
|
+
// elimina
|
|
34329
34291
|
this.sensorsCfg = this.sensorsCfg.filter(s => s.id !== sid);
|
|
34330
34292
|
this.saveSensorsToStorage();
|
|
34331
34293
|
try {
|
|
34294
|
+
this.devices.delete(`sensor:${sid}`);
|
|
34332
34295
|
sdk?.deviceManager?.onDeviceRemoved?.(`sensor:${sid}`);
|
|
34333
34296
|
}
|
|
34334
34297
|
catch { }
|
|
34298
|
+
// pulisci flag
|
|
34335
34299
|
this.storage.removeItem(key);
|
|
34336
|
-
this.
|
|
34300
|
+
await this.discoverSensors();
|
|
34337
34301
|
await this.connectMqtt(true);
|
|
34338
34302
|
return;
|
|
34339
34303
|
}
|
|
34340
|
-
|
|
34341
|
-
if (prop === 'name')
|
|
34304
|
+
if (prop === 'name') {
|
|
34342
34305
|
cfg.name = String(value);
|
|
34343
|
-
|
|
34306
|
+
}
|
|
34307
|
+
else if (prop === 'kind') {
|
|
34344
34308
|
cfg.kind = String(value);
|
|
34309
|
+
}
|
|
34345
34310
|
else if (prop.startsWith('topic.')) {
|
|
34346
34311
|
const tk = prop.substring('topic.'.length);
|
|
34347
34312
|
cfg.topics[tk] = String(value).trim();
|
|
34348
34313
|
}
|
|
34349
34314
|
this.saveSensorsToStorage();
|
|
34350
|
-
this.
|
|
34315
|
+
await this.discoverSensors();
|
|
34351
34316
|
await this.connectMqtt(true);
|
|
34352
34317
|
return;
|
|
34353
34318
|
}
|
|
34354
|
-
//
|
|
34319
|
+
// --- Altro (MQTT / Alarm settings) ---
|
|
34355
34320
|
if (key === 'sensorsJson') {
|
|
34321
|
+
// non più mostrato, ma se presente da vecchie versioni
|
|
34356
34322
|
this.loadSensorsFromStorage();
|
|
34357
|
-
this.
|
|
34323
|
+
await this.discoverSensors();
|
|
34358
34324
|
await this.connectMqtt(true);
|
|
34359
34325
|
}
|
|
34360
34326
|
else {
|
|
@@ -34362,12 +34328,15 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34362
34328
|
}
|
|
34363
34329
|
}
|
|
34364
34330
|
/** ---- DeviceProvider ---- */
|
|
34365
|
-
async getDevice(nativeId) {
|
|
34331
|
+
async getDevice(nativeId) {
|
|
34332
|
+
return this.devices.get(nativeId);
|
|
34333
|
+
}
|
|
34366
34334
|
async releaseDevice(_id, nativeId) {
|
|
34367
34335
|
try {
|
|
34368
34336
|
const dev = this.devices.get(nativeId);
|
|
34369
|
-
if (dev)
|
|
34337
|
+
if (dev) {
|
|
34370
34338
|
this.devices.delete(nativeId);
|
|
34339
|
+
}
|
|
34371
34340
|
try {
|
|
34372
34341
|
sdk?.deviceManager?.onDeviceRemoved?.(nativeId);
|
|
34373
34342
|
}
|
|
@@ -34381,6 +34350,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34381
34350
|
try {
|
|
34382
34351
|
const raw = this.storage.getItem('sensorsJson') || '[]';
|
|
34383
34352
|
const parsed = JSON.parse(raw);
|
|
34353
|
+
// sanitize
|
|
34384
34354
|
this.sensorsCfg = (parsed || []).filter(x => x && x.id && x.name && x.kind && x.topics);
|
|
34385
34355
|
}
|
|
34386
34356
|
catch (e) {
|
|
@@ -34388,22 +34358,15 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34388
34358
|
this.sensorsCfg = [];
|
|
34389
34359
|
}
|
|
34390
34360
|
}
|
|
34391
|
-
/**
|
|
34392
|
-
|
|
34361
|
+
/** ===== discoverSensors: annuncia PRIMA, istanzia DOPO (con retry se manager non pronto) ===== */
|
|
34362
|
+
async discoverSensors() {
|
|
34393
34363
|
const dmAny = sdk?.deviceManager;
|
|
34394
34364
|
if (!dmAny) {
|
|
34395
|
-
|
|
34396
|
-
|
|
34397
|
-
this.triedDiscoveryOnce = true;
|
|
34398
|
-
}
|
|
34365
|
+
this.console.warn('deviceManager not ready yet, retrying in 1s…');
|
|
34366
|
+
setTimeout(() => this.discoverSensors().catch(e => this.console.error('discoverSensors retry error', e)), 1000);
|
|
34399
34367
|
return;
|
|
34400
34368
|
}
|
|
34401
|
-
|
|
34402
|
-
this.discoverSensors(dmAny);
|
|
34403
|
-
}
|
|
34404
|
-
/** discoverSensors con deviceManager garantito */
|
|
34405
|
-
discoverSensors(dmAny) {
|
|
34406
|
-
// 1) Manifests
|
|
34369
|
+
// 1) Prepara i manifest
|
|
34407
34370
|
const manifests = this.sensorsCfg.map(cfg => {
|
|
34408
34371
|
const nativeId = `sensor:${cfg.id}`;
|
|
34409
34372
|
const t = cfg.topics || {};
|
|
@@ -34423,10 +34386,18 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34423
34386
|
// 2) Annuncio
|
|
34424
34387
|
if (typeof dmAny.onDevicesChanged === 'function') {
|
|
34425
34388
|
dmAny.onDevicesChanged({ devices: manifests });
|
|
34389
|
+
this.console.log('Annunciati (batch):', manifests.map(m => m.nativeId).join(', '));
|
|
34426
34390
|
}
|
|
34427
34391
|
else if (typeof dmAny.onDeviceDiscovered === 'function') {
|
|
34428
|
-
for (const m of manifests)
|
|
34392
|
+
for (const m of manifests) {
|
|
34429
34393
|
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;
|
|
34430
34401
|
}
|
|
34431
34402
|
// 3) Istanzia/aggiorna
|
|
34432
34403
|
for (const cfg of this.sensorsCfg) {
|
|
@@ -34445,16 +34416,18 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34445
34416
|
dev.cfg = cfg;
|
|
34446
34417
|
}
|
|
34447
34418
|
const hasBattery = !!(cfg.topics.batteryLevel || cfg.topics.lowBattery);
|
|
34448
|
-
if (hasBattery && dev.batteryLevel === undefined)
|
|
34419
|
+
if (hasBattery && dev.batteryLevel === undefined) {
|
|
34449
34420
|
dev.batteryLevel = 100;
|
|
34421
|
+
}
|
|
34450
34422
|
}
|
|
34451
|
-
// 4)
|
|
34423
|
+
// 4) Rimuovi quelli spariti
|
|
34452
34424
|
const announced = new Set(manifests.map(m => m.nativeId));
|
|
34453
34425
|
for (const [nativeId] of this.devices) {
|
|
34454
34426
|
if (!announced.has(nativeId)) {
|
|
34455
34427
|
try {
|
|
34456
34428
|
this.devices.delete(nativeId);
|
|
34457
|
-
|
|
34429
|
+
sdk?.deviceManager?.onDeviceRemoved?.(nativeId);
|
|
34430
|
+
this.console.log('Rimosso:', nativeId);
|
|
34458
34431
|
}
|
|
34459
34432
|
catch { }
|
|
34460
34433
|
}
|
|
@@ -34468,7 +34441,13 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34468
34441
|
const clientId = this.storage.getItem('clientId') || 'scrypted-paradox';
|
|
34469
34442
|
const tls = this.storage.getItem('tls') === 'true';
|
|
34470
34443
|
const rejectUnauthorized = this.storage.getItem('rejectUnauthorized') !== 'false';
|
|
34471
|
-
const opts = {
|
|
34444
|
+
const opts = {
|
|
34445
|
+
clientId,
|
|
34446
|
+
username,
|
|
34447
|
+
password,
|
|
34448
|
+
clean: true,
|
|
34449
|
+
reconnectPeriod: 3000,
|
|
34450
|
+
};
|
|
34472
34451
|
if (tls) {
|
|
34473
34452
|
opts.protocol = 'mqtts';
|
|
34474
34453
|
opts.rejectUnauthorized = rejectUnauthorized;
|
|
@@ -34477,34 +34456,38 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34477
34456
|
}
|
|
34478
34457
|
collectAllSubscriptions() {
|
|
34479
34458
|
const subs = new Set();
|
|
34459
|
+
// alarm
|
|
34480
34460
|
for (const k of ['topicGetTarget', 'topicGetCurrent', 'topicTamper', 'topicOnline']) {
|
|
34481
34461
|
const v = this.storage.getItem(k);
|
|
34482
34462
|
if (v)
|
|
34483
34463
|
subs.add(v);
|
|
34484
34464
|
}
|
|
34465
|
+
// sensors
|
|
34485
34466
|
for (const s of this.sensorsCfg) {
|
|
34486
34467
|
const t = s.topics || {};
|
|
34487
34468
|
[t.contact, t.motion, t.occupancy, t.batteryLevel, t.lowBattery, t.tamper, t.online]
|
|
34488
|
-
.filter(Boolean)
|
|
34469
|
+
.filter(Boolean)
|
|
34470
|
+
.forEach(x => subs.add(String(x)));
|
|
34489
34471
|
}
|
|
34490
34472
|
return Array.from(subs);
|
|
34491
34473
|
}
|
|
34492
34474
|
async connectMqtt(_reconnect = false) {
|
|
34493
34475
|
const subs = this.collectAllSubscriptions();
|
|
34494
|
-
if (!subs.length && !this.storage.getItem('topicSetTarget'))
|
|
34476
|
+
if (!subs.length && !this.storage.getItem('topicSetTarget')) {
|
|
34495
34477
|
this.console.warn('Configura almeno un topic nelle impostazioni.');
|
|
34478
|
+
}
|
|
34496
34479
|
if (this.client) {
|
|
34497
34480
|
try {
|
|
34498
34481
|
this.client.end(true);
|
|
34499
34482
|
}
|
|
34500
34483
|
catch { }
|
|
34501
|
-
;
|
|
34502
34484
|
this.client = undefined;
|
|
34503
34485
|
}
|
|
34504
34486
|
const { url, opts } = this.getMqttOptions();
|
|
34505
34487
|
this.console.log(`Connecting MQTT ${url} ...`);
|
|
34506
34488
|
const client = mqtt_1.default.connect(url, opts);
|
|
34507
34489
|
this.client = client;
|
|
34490
|
+
// cache alarm topics for fast compare
|
|
34508
34491
|
const tTarget = this.storage.getItem('topicGetTarget') || '';
|
|
34509
34492
|
const tCurrent = this.storage.getItem('topicGetCurrent') || '';
|
|
34510
34493
|
const tTamper = this.storage.getItem('topicTamper') || '';
|
|
@@ -34512,11 +34495,12 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34512
34495
|
client.on('connect', () => {
|
|
34513
34496
|
this.console.log('MQTT connected');
|
|
34514
34497
|
this.online = true;
|
|
34515
|
-
if (subs.length)
|
|
34516
|
-
client.subscribe(subs, { qos: 0 }, (err) => {
|
|
34517
|
-
|
|
34518
|
-
|
|
34519
|
-
|
|
34498
|
+
if (subs.length) {
|
|
34499
|
+
client.subscribe(subs, { qos: 0 }, (err) => {
|
|
34500
|
+
if (err)
|
|
34501
|
+
this.console.error('subscribe error', err);
|
|
34502
|
+
});
|
|
34503
|
+
}
|
|
34520
34504
|
});
|
|
34521
34505
|
client.on('reconnect', () => this.console.log('MQTT reconnecting...'));
|
|
34522
34506
|
client.on('close', () => { this.console.log('MQTT closed'); this.online = false; });
|
|
@@ -34525,6 +34509,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34525
34509
|
try {
|
|
34526
34510
|
const p = payload?.toString() ?? '';
|
|
34527
34511
|
const np = normalize(p);
|
|
34512
|
+
// ---- Alarm handling ----
|
|
34528
34513
|
if (topic === tOnline) {
|
|
34529
34514
|
if (truthy(np) || np === 'online')
|
|
34530
34515
|
this.online = true;
|
|
@@ -34533,17 +34518,19 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34533
34518
|
return;
|
|
34534
34519
|
}
|
|
34535
34520
|
if (topic === tTamper) {
|
|
34536
|
-
if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np))
|
|
34521
|
+
if (truthy(np) || ['tamper', 'intrusion', 'cover'].includes(np)) {
|
|
34537
34522
|
this.tampered = ['cover', 'intrusion'].find(x => x === np) || true;
|
|
34538
|
-
|
|
34523
|
+
}
|
|
34524
|
+
else if (falsy(np)) {
|
|
34539
34525
|
this.tampered = false;
|
|
34526
|
+
}
|
|
34540
34527
|
return;
|
|
34541
34528
|
}
|
|
34542
34529
|
if (topic === tCurrent) {
|
|
34543
34530
|
const mode = payloadToMode(payload);
|
|
34544
34531
|
const isAlarm = ['alarm', 'triggered'].includes(np);
|
|
34545
34532
|
const current = this.securitySystemState || { mode: SecuritySystemMode.Disarmed };
|
|
34546
|
-
|
|
34533
|
+
const newState = {
|
|
34547
34534
|
mode: mode ?? current.mode,
|
|
34548
34535
|
supportedModes: current.supportedModes ?? [
|
|
34549
34536
|
SecuritySystemMode.Disarmed,
|
|
@@ -34553,6 +34540,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34553
34540
|
],
|
|
34554
34541
|
triggered: isAlarm || undefined,
|
|
34555
34542
|
};
|
|
34543
|
+
this.securitySystemState = newState;
|
|
34556
34544
|
return;
|
|
34557
34545
|
}
|
|
34558
34546
|
if (topic === tTarget) {
|
|
@@ -34560,11 +34548,10 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34560
34548
|
this.console.log('Target state reported:', p, '->', this.pendingTarget);
|
|
34561
34549
|
return;
|
|
34562
34550
|
}
|
|
34563
|
-
//
|
|
34564
|
-
|
|
34565
|
-
this.safeDiscoverSensors(true);
|
|
34566
|
-
for (const dev of this.devices.values())
|
|
34551
|
+
// ---- Sensor dispatch ----
|
|
34552
|
+
for (const dev of this.devices.values()) {
|
|
34567
34553
|
dev.handleMqtt(topic, payload);
|
|
34554
|
+
}
|
|
34568
34555
|
}
|
|
34569
34556
|
catch (e) {
|
|
34570
34557
|
this.console.error('MQTT message handler error', e);
|
|
@@ -34589,7 +34576,7 @@ class ParadoxMqttSecuritySystem extends ScryptedDeviceBase {
|
|
|
34589
34576
|
async armSecuritySystem(mode) {
|
|
34590
34577
|
const payload = this.getOutgoing(mode);
|
|
34591
34578
|
this.console.log('armSecuritySystem', mode, '->', payload);
|
|
34592
|
-
this.pendingTarget = mode;
|
|
34579
|
+
this.pendingTarget = mode; // memorizza target, ma NON cambiare il current
|
|
34593
34580
|
this.publishSetTarget(payload);
|
|
34594
34581
|
}
|
|
34595
34582
|
async disarmSecuritySystem() {
|
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED