dsc-itv2-client 1.0.22 → 1.0.23

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/ITV2Client.js +93 -3
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsc-itv2-client",
3
3
  "author": "fajitacat",
4
- "version": "1.0.22",
4
+ "version": "1.0.23",
5
5
  "description": "Reverse engineered DSC ITV2 Protocol Client Library for TL280R Communicator - Monitor and control DSC alarm panels",
6
6
  "main": "src/index.js",
7
7
  "type": "module",
package/src/ITV2Client.js CHANGED
@@ -379,9 +379,23 @@ export class ITV2Client extends EventEmitter {
379
379
  case CMD.TIME_DATE_BROADCAST:
380
380
  this._handleTimeDateBroadcast(parsed);
381
381
  break;
382
+ case CMD.ZONE_STATUS:
383
+ this._handleZoneStatusResponse(parsed);
384
+ break;
385
+ case CMD.PARTITION_STATUS:
386
+ this._handlePartitionStatusResponse(parsed);
387
+ break;
388
+ case CMD.GLOBAL_STATUS:
389
+ this._handleGlobalStatusResponse(parsed);
390
+ break;
382
391
  default:
383
392
  if (this.handshakeState === 'ESTABLISHED') {
384
- this._log(`[Session] Unhandled command ${CMD_NAMES[cmd] || '0x' + cmd?.toString(16)}`);
393
+ const cmdHex = cmd ? '0x' + cmd.toString(16).padStart(4, '0') : 'null';
394
+ const cmdName = CMD_NAMES[cmd] || 'UNKNOWN';
395
+ this._logMinimal(`[Session] Received command ${cmdName} (${cmdHex})`);
396
+ if (parsed.commandData && parsed.commandData.length > 0) {
397
+ this._logMinimal(`[Session] Data (${parsed.commandData.length} bytes): ${parsed.commandData.toString('hex')}`);
398
+ }
385
399
  this._sendPacket(this.session.buildSimpleAck());
386
400
  }
387
401
  break;
@@ -569,10 +583,17 @@ export class ITV2Client extends EventEmitter {
569
583
  }
570
584
 
571
585
  _handleCommandResponse(parsed) {
572
- const responseCode = parsed.commandData?.[0] || 0;
586
+ const data = parsed.commandData;
587
+ const responseCode = data?.[0] || 0;
573
588
  const appSeqAsEcho = parsed.appSequence;
574
589
 
575
- this._log(`[Handshake] Got COMMAND_RESPONSE (echoed app_seq: ${appSeqAsEcho}, response_code: ${responseCode}), sending ACK`);
590
+ this._log(`[Session] Got COMMAND_RESPONSE (app_seq: ${appSeqAsEcho}, response_code: ${responseCode})`);
591
+
592
+ // Log full data in established state for debugging query responses
593
+ if (this.handshakeState === 'ESTABLISHED' && data && data.length > 1) {
594
+ this._logMinimal(`[Command Response] Response code: ${responseCode}, data (${data.length} bytes): ${data.toString('hex')}`);
595
+ this.emit('command:response', { responseCode, appSequence: appSeqAsEcho, data });
596
+ }
576
597
 
577
598
  const ack = this.session.buildSimpleAck();
578
599
  this._sendPacket(ack);
@@ -655,6 +676,75 @@ export class ITV2Client extends EventEmitter {
655
676
  this._sendPacket(this.session.buildSimpleAck());
656
677
  }
657
678
 
679
+ // ==================== Status Query Response Handlers ====================
680
+
681
+ _handleZoneStatusResponse(parsed) {
682
+ const data = parsed.commandData;
683
+ this._logMinimal(`[Zone Status] Received zone status response`);
684
+ this._log(`[Zone Status] Raw data (${data?.length || 0} bytes): ${data?.toString('hex') || 'none'}`);
685
+
686
+ if (data && data.length >= 3) {
687
+ // Response format: [ZoneNumber 2B BE][StatusByte 1B][...more zones...]
688
+ const zoneNum = data.readUInt16BE(0);
689
+ const statusByte = data[2];
690
+
691
+ // Update internal state
692
+ const fullStatus = this.eventHandler.handleZoneStatus(zoneNum, statusByte);
693
+
694
+ this._logMinimal(`[Zone Status] Zone ${zoneNum}: ${fullStatus.open ? 'OPEN' : 'CLOSED'} (0x${statusByte.toString(16)})`);
695
+
696
+ // Emit event with full status
697
+ this.emit('zone:statusResponse', zoneNum, fullStatus);
698
+ this.emit('zone:status', zoneNum, fullStatus);
699
+ } else {
700
+ this._log(`[Zone Status] Empty or invalid response`);
701
+ }
702
+
703
+ this._sendPacket(this.session.buildSimpleAck());
704
+ }
705
+
706
+ _handlePartitionStatusResponse(parsed) {
707
+ const data = parsed.commandData;
708
+ this._logMinimal(`[Partition Status] Received partition status response`);
709
+ this._log(`[Partition Status] Raw data (${data?.length || 0} bytes): ${data?.toString('hex') || 'none'}`);
710
+
711
+ if (data && data.length >= 3) {
712
+ // Response format: [PartitionNumber 2B BE][StatusBytes...]
713
+ const partitionNum = data.readUInt16BE(0);
714
+ const statusBytes = data.slice(2);
715
+
716
+ this._logMinimal(`[Partition Status] Partition ${partitionNum}: status bytes ${statusBytes.toString('hex')}`);
717
+
718
+ // Emit raw event - let consumers parse the detailed status
719
+ this.emit('partition:statusResponse', partitionNum, statusBytes);
720
+ } else {
721
+ this._log(`[Partition Status] Empty or invalid response`);
722
+ }
723
+
724
+ this._sendPacket(this.session.buildSimpleAck());
725
+ }
726
+
727
+ _handleGlobalStatusResponse(parsed) {
728
+ const data = parsed.commandData;
729
+ this._logMinimal(`[Global Status] Received global status response`);
730
+ this._log(`[Global Status] Raw data (${data?.length || 0} bytes): ${data?.toString('hex') || 'none'}`);
731
+
732
+ if (data && data.length > 0) {
733
+ // Emit raw event with full data
734
+ this.emit('global:statusResponse', data);
735
+
736
+ // Log byte breakdown for debugging
737
+ this._log(`[Global Status] Byte breakdown:`);
738
+ for (let i = 0; i < data.length && i < 32; i++) {
739
+ this._log(` Byte ${i}: 0x${data[i].toString(16).padStart(2, '0')} (${data[i]})`);
740
+ }
741
+ } else {
742
+ this._log(`[Global Status] Empty response`);
743
+ }
744
+
745
+ this._sendPacket(this.session.buildSimpleAck());
746
+ }
747
+
658
748
  // ==================== Utility Methods ====================
659
749
 
660
750
  _handleError(error) {