dsc-itv2-client 2.0.5 → 2.0.7
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/package.json +1 -1
- package/src/ITV2Client.js +92 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsc-itv2-client",
|
|
3
3
|
"author": "fajitacat",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.7",
|
|
5
5
|
"description": "Reverse engineered DSC ITV2 Protocol Client Library for TL280/TL280E - Monitor and control DSC Neo alarm panels with real-time zone/partition status, arming, and trouble detail",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"type": "module",
|
package/src/ITV2Client.js
CHANGED
|
@@ -758,6 +758,9 @@ export class ITV2Client extends EventEmitter {
|
|
|
758
758
|
case CMD.SINGLE_ZONE_BYPASS_STATUS:
|
|
759
759
|
this._handleZoneBypassNotification(parsed);
|
|
760
760
|
break;
|
|
761
|
+
case CMD.ZONE_ALARM_STATUS:
|
|
762
|
+
this._handleZoneAlarmStatus(parsed);
|
|
763
|
+
break;
|
|
761
764
|
case CMD.MULTIPLE_MESSAGE:
|
|
762
765
|
this._handleMultipleMessagePacket(parsed);
|
|
763
766
|
break;
|
|
@@ -1219,6 +1222,10 @@ export class ITV2Client extends EventEmitter {
|
|
|
1219
1222
|
rawBytes: statusBytes.toString('hex'),
|
|
1220
1223
|
};
|
|
1221
1224
|
|
|
1225
|
+
// Check alarm state change before updating event handler
|
|
1226
|
+
const prevPartState = this.eventHandler.getPartitionState(partitionNum);
|
|
1227
|
+
const wasInAlarm = prevPartState?.alarm || false;
|
|
1228
|
+
|
|
1222
1229
|
// Update event handler
|
|
1223
1230
|
const armedState = partStatus.awayArmed ? 0x02 :
|
|
1224
1231
|
partStatus.stayArmed ? 0x01 :
|
|
@@ -1226,6 +1233,22 @@ export class ITV2Client extends EventEmitter {
|
|
|
1226
1233
|
partStatus.armed ? 0x01 : 0x00;
|
|
1227
1234
|
this.eventHandler.handlePartitionArming(partitionNum, armedState);
|
|
1228
1235
|
|
|
1236
|
+
// Store alarm flag in partition state
|
|
1237
|
+
const currentPartState = this.eventHandler.getPartitionState(partitionNum);
|
|
1238
|
+
if (currentPartState) {
|
|
1239
|
+
currentPartState.alarm = partStatus.alarm;
|
|
1240
|
+
currentPartState.alarmInMemory = partStatus.alarmInMemory;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// Emit alarm events on state change
|
|
1244
|
+
if (partStatus.alarm && !wasInAlarm) {
|
|
1245
|
+
this._logMinimal(`[Partition ${partitionNum}] ALARM!`);
|
|
1246
|
+
this.emit('partition:alarm', { partition: partitionNum, ...partStatus });
|
|
1247
|
+
} else if (!partStatus.alarm && wasInAlarm) {
|
|
1248
|
+
this._logMinimal(`[Partition ${partitionNum}] Alarm restored`);
|
|
1249
|
+
this.emit('partition:alarm:restored', { partition: partitionNum, ...partStatus });
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1229
1252
|
this.emit('partition:statusResponse', partitionNum, partStatus);
|
|
1230
1253
|
this._resolvePendingRequest(CMD.PARTITION_STATUS, partStatus);
|
|
1231
1254
|
} catch (e) {
|
|
@@ -1460,6 +1483,75 @@ export class ITV2Client extends EventEmitter {
|
|
|
1460
1483
|
this._ack();
|
|
1461
1484
|
}
|
|
1462
1485
|
|
|
1486
|
+
_handleZoneAlarmStatus(parsed) {
|
|
1487
|
+
// 0x0840 ModuleStatus_Zone_Alarm_Status (from decompiled SDK):
|
|
1488
|
+
// [Partition CompactInt][Count CompactInt][repeated: Zone CompactInt, AlarmType 1B, AlarmState 1B]
|
|
1489
|
+
//
|
|
1490
|
+
// AlarmType: 1=Unknown, 2=Burglary, 3=24HR_Supervisory, 4=Fire, 5=Fire_Supervisory,
|
|
1491
|
+
// 6=CO, 7=Gas, 8=HighTemp, 9=LowTemp, 10=Medical, 11=Panic, 12=Waterflow,
|
|
1492
|
+
// 13=Water_Leakage, 14=Pendant, 15=Tamper, 16=RF_Jam, 17=Hardware_Fault,
|
|
1493
|
+
// 18=Duress, 19=Personal_Emergency, 20=Holdup, 21=Sprinkler
|
|
1494
|
+
// AlarmState: bit 0 = 0 → NotInAlarm, bit 0 = 1 → InAlarm
|
|
1495
|
+
const fullPayload = this._reconstructPayload(parsed);
|
|
1496
|
+
if (!fullPayload || fullPayload.length < 6) {
|
|
1497
|
+
this._ack();
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
const ALARM_TYPES = {
|
|
1502
|
+
1: 'Unknown', 2: 'Burglary', 3: '24HR_Supervisory', 4: 'Fire',
|
|
1503
|
+
5: 'Fire_Supervisory', 6: 'CO', 7: 'Gas', 8: 'HighTemp',
|
|
1504
|
+
9: 'LowTemp', 10: 'Medical', 11: 'Panic', 12: 'Waterflow',
|
|
1505
|
+
13: 'Water_Leakage', 14: 'Pendant', 15: 'Tamper', 16: 'RF_Jam',
|
|
1506
|
+
17: 'Hardware_Fault', 18: 'Duress', 19: 'Personal_Emergency',
|
|
1507
|
+
20: 'Holdup', 21: 'Sprinkler',
|
|
1508
|
+
};
|
|
1509
|
+
|
|
1510
|
+
try {
|
|
1511
|
+
let offset = 0;
|
|
1512
|
+
const partition = ITv2Session.decodeVarBytes(fullPayload, offset);
|
|
1513
|
+
offset += partition.bytesRead;
|
|
1514
|
+
|
|
1515
|
+
const count = ITv2Session.decodeVarBytes(fullPayload, offset);
|
|
1516
|
+
offset += count.bytesRead;
|
|
1517
|
+
|
|
1518
|
+
while (offset < fullPayload.length) {
|
|
1519
|
+
const zone = ITv2Session.decodeVarBytes(fullPayload, offset);
|
|
1520
|
+
offset += zone.bytesRead;
|
|
1521
|
+
if (offset >= fullPayload.length) break;
|
|
1522
|
+
const alarmType = fullPayload[offset++];
|
|
1523
|
+
if (offset >= fullPayload.length) break;
|
|
1524
|
+
const alarmState = fullPayload[offset++];
|
|
1525
|
+
|
|
1526
|
+
const inAlarm = !!(alarmState & 0x01);
|
|
1527
|
+
const alarmTypeName = ALARM_TYPES[alarmType] || `Unknown(${alarmType})`;
|
|
1528
|
+
|
|
1529
|
+
this._logMinimal(`[Zone ${zone.value}] Alarm: ${inAlarm ? 'ACTIVE' : 'CLEARED'} (${alarmTypeName}, partition ${partition.value})`);
|
|
1530
|
+
|
|
1531
|
+
const alarmData = {
|
|
1532
|
+
zone: zone.value,
|
|
1533
|
+
partition: partition.value,
|
|
1534
|
+
alarmType,
|
|
1535
|
+
alarmTypeName,
|
|
1536
|
+
inAlarm,
|
|
1537
|
+
timestamp: new Date(),
|
|
1538
|
+
};
|
|
1539
|
+
|
|
1540
|
+
if (inAlarm) {
|
|
1541
|
+
this.eventHandler.handleZoneAlarm(zone.value, alarmTypeName);
|
|
1542
|
+
this.emit('zone:alarm', alarmData);
|
|
1543
|
+
this.emit('partition:alarm', alarmData);
|
|
1544
|
+
} else {
|
|
1545
|
+
this.emit('zone:alarm:restored', alarmData);
|
|
1546
|
+
this.emit('partition:alarm:restored', alarmData);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
} catch (e) {
|
|
1550
|
+
this._log(`[Zone Alarm Status] Parse error: ${e.message}`);
|
|
1551
|
+
}
|
|
1552
|
+
this._ack();
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1463
1555
|
_handleExitDelayNotification(parsed) {
|
|
1464
1556
|
// 0x0230 NotificationExitDelay (from neohub):
|
|
1465
1557
|
// [CompactInt:Partition][DelayFlags:1B][CompactInt:DurationInSeconds]
|