icom-wlan-node 0.5.1 → 0.6.1

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.
@@ -49,27 +49,11 @@ const smeter_1 = require("../utils/smeter");
49
49
  const IcomScopeCommands_1 = require("../scope/IcomScopeCommands");
50
50
  const IcomScopeParser_1 = require("../scope/IcomScopeParser");
51
51
  const IcomScopeService_1 = require("../scope/IcomScopeService");
52
- const DEFAULT_SCOPE_EDGE_SLOTS = [1, 2, 3, 4];
52
+ const IcomCivFrame_1 = require("./IcomCivFrame");
53
+ const IcomCivSpec_1 = require("./IcomCivSpec");
54
+ const IcomProfiles_1 = require("./IcomProfiles");
55
+ const IcomCivRequestManager_1 = require("./IcomCivRequestManager");
53
56
  const DEFAULT_SCOPE_SPANS_HZ = [25000000, 10000000, 5000000, 2500000, 1000000, 500000, 250000, 100000, 50000, 25000, 10000, 5000, 2500];
54
- const DEFAULT_SCOPE_FREQUENCY_RANGES = [
55
- { rangeId: 1, lowHz: 30000, highHz: 1600000 },
56
- { rangeId: 2, lowHz: 1600000, highHz: 2000000 },
57
- { rangeId: 3, lowHz: 2000000, highHz: 6000000 },
58
- { rangeId: 4, lowHz: 6000000, highHz: 8000000 },
59
- { rangeId: 5, lowHz: 8000000, highHz: 11000000 },
60
- { rangeId: 6, lowHz: 11000000, highHz: 15000000 },
61
- { rangeId: 7, lowHz: 15000000, highHz: 20000000 },
62
- { rangeId: 8, lowHz: 20000000, highHz: 22000000 },
63
- { rangeId: 9, lowHz: 22000000, highHz: 26000000 },
64
- { rangeId: 10, lowHz: 26000000, highHz: 30000000 },
65
- { rangeId: 11, lowHz: 30000000, highHz: 45000000 },
66
- { rangeId: 12, lowHz: 45000000, highHz: 60000000 },
67
- { rangeId: 13, lowHz: 60000000, highHz: 74800000 },
68
- { rangeId: 14, lowHz: 74800000, highHz: 108000000 },
69
- { rangeId: 15, lowHz: 108000000, highHz: 137000000 },
70
- { rangeId: 16, lowHz: 137000000, highHz: 200000000 },
71
- { rangeId: 17, lowHz: 400000000, highHz: 470000000 },
72
- ];
73
57
  function modeCodeToName(mode) {
74
58
  switch (mode) {
75
59
  case 0: return 'center';
@@ -92,6 +76,8 @@ class IcomControl {
92
76
  this.rigName = '';
93
77
  this.macAddress = Buffer.alloc(6);
94
78
  this.civAssembleBuf = Buffer.alloc(0); // CIV stream reassembler
79
+ this.activeProfile = (0, IcomProfiles_1.getProfileByModel)('generic-modern-icom');
80
+ this.lastFilter = 1;
95
81
  // Connection state machine (replaces old fragmented state flags)
96
82
  this.connectionSession = {
97
83
  phase: types_1.ConnectionPhase.IDLE,
@@ -109,7 +95,8 @@ class IcomControl {
109
95
  reconnectBaseDelay: 2000,
110
96
  reconnectMaxDelay: 30000
111
97
  };
112
- this.options = options;
98
+ this.options = { ...options, model: options.model ?? 'auto' };
99
+ this.activeProfile = (0, IcomProfiles_1.resolveIcomProfile)({ requestedModel: this.options.model });
113
100
  // Setup control session
114
101
  this.sess = new Session_1.Session({ ip: options.control.ip, port: options.control.port }, {
115
102
  onData: (data) => this.onData(data),
@@ -133,10 +120,30 @@ class IcomControl {
133
120
  this.civ = new IcomCiv_1.IcomCiv(this.civSess);
134
121
  this.audio = new IcomAudio_1.IcomAudio(this.audioSess);
135
122
  this.scope = new IcomScopeService_1.IcomScopeService();
123
+ this.civRequestManager = new IcomCivRequestManager_1.IcomCivRequestManager(this.ev);
136
124
  this.scope.on('scopeSegment', (segment) => this.ev.emit('scopeSegment', segment));
137
125
  this.scope.on('scopeFrame', (frame) => this.ev.emit('scopeFrame', frame));
138
126
  }
139
127
  get events() { return this.ev; }
128
+ get profile() { return this.activeProfile; }
129
+ resolveActiveProfile(context = {}) {
130
+ const next = (0, IcomProfiles_1.resolveIcomProfile)({
131
+ requestedModel: this.options.model,
132
+ rigName: context.rigName ?? this.rigName,
133
+ civAddress: context.civAddress ?? this.civ.civAddress,
134
+ });
135
+ if (this.options.model && this.options.model !== 'auto' && context.civAddress !== undefined && next.defaultCivAddress !== (context.civAddress & 0xff)) {
136
+ (0, debug_1.dbg)(`Configured profile ${next.modelId} default CI-V 0x${next.defaultCivAddress.toString(16)} differs from radio CI-V 0x${(context.civAddress & 0xff).toString(16)}`);
137
+ }
138
+ if (next.modelId !== this.activeProfile.modelId) {
139
+ (0, debug_1.dbg)(`ICOM profile selected: ${next.profileName}`);
140
+ }
141
+ this.activeProfile = next;
142
+ this.lastFilter = next.defaultFilter;
143
+ }
144
+ getProfileModelId() {
145
+ return this.activeProfile.modelId;
146
+ }
140
147
  // ============================================================================
141
148
  // State Machine Management
142
149
  // ============================================================================
@@ -571,14 +578,14 @@ class IcomControl {
571
578
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
572
579
  const rigAddr = this.civ.civAddress & 0xff;
573
580
  const req = IcomScopeCommands_1.IcomScopeCommands.readScopeSpan(ctrAddr, rigAddr, receiver);
574
- const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, 0x27, [0x15, receiver], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
581
+ const resp = await this.waitForCivFrame(`scope:0x27:0x15:${receiver}`, (frame) => IcomControl.matchCommandFrame(frame, 0x27, [0x15, receiver], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
575
582
  if (!resp || resp.length < 13) {
576
583
  return null;
577
584
  }
578
- const spanHz = (0, IcomScopeParser_1.parseIcomBcdFreqLE)(resp.subarray(7, 12));
585
+ const encodedSpanHz = (0, IcomScopeParser_1.parseIcomBcdFreqLE)(resp.subarray(7, 12));
579
586
  return {
580
587
  receiver,
581
- spanHz,
588
+ spanHz: encodedSpanHz * 2,
582
589
  };
583
590
  }
584
591
  async setScopeSpan(spanHz, options) {
@@ -593,7 +600,7 @@ class IcomControl {
593
600
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
594
601
  const rigAddr = this.civ.civAddress & 0xff;
595
602
  const req = IcomScopeCommands_1.IcomScopeCommands.readScopeMode(ctrAddr, rigAddr, receiver);
596
- const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, 0x27, [0x14, receiver], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
603
+ const resp = await this.waitForCivFrame(`scope:0x27:0x14:${receiver}`, (frame) => IcomControl.matchCommandFrame(frame, 0x27, [0x14, receiver], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
597
604
  if (!resp || resp.length < 9) {
598
605
  return null;
599
606
  }
@@ -617,7 +624,7 @@ class IcomControl {
617
624
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
618
625
  const rigAddr = this.civ.civAddress & 0xff;
619
626
  const req = IcomScopeCommands_1.IcomScopeCommands.readScopeEdge(ctrAddr, rigAddr, receiver);
620
- const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, 0x27, [0x16, receiver], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
627
+ const resp = await this.waitForCivFrame(`scope:0x27:0x16:${receiver}`, (frame) => IcomControl.matchCommandFrame(frame, 0x27, [0x16, receiver], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
621
628
  if (!resp || resp.length < 9) {
622
629
  return null;
623
630
  }
@@ -628,7 +635,9 @@ class IcomControl {
628
635
  }
629
636
  async setScopeEdge(edgeSlot, options) {
630
637
  const receiver = options?.receiver ?? 0;
631
- const safeEdgeSlot = Math.max(1, Math.min(4, Math.trunc(edgeSlot)));
638
+ const slots = this.activeProfile.scopeEdgeSlots;
639
+ const maxSlot = slots.length ? Math.max(...slots) : 4;
640
+ const safeEdgeSlot = Math.max(1, Math.min(maxSlot, Math.trunc(edgeSlot)));
632
641
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
633
642
  const rigAddr = this.civ.civAddress & 0xff;
634
643
  this.sendCiv(IcomScopeCommands_1.IcomScopeCommands.setScopeEdge(ctrAddr, rigAddr, safeEdgeSlot, receiver));
@@ -638,7 +647,7 @@ class IcomControl {
638
647
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
639
648
  const rigAddr = this.civ.civAddress & 0xff;
640
649
  const req = IcomScopeCommands_1.IcomScopeCommands.readScopeFixedEdge(ctrAddr, rigAddr, rangeId, edgeSlot);
641
- const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, 0x27, [0x1e, rangeId, edgeSlot], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
650
+ const resp = await this.waitForCivFrame(`scope:0x27:0x1e:${rangeId}:${edgeSlot}`, (frame) => IcomControl.matchCommandFrame(frame, 0x27, [0x1e, rangeId, edgeSlot], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
642
651
  if (!resp || resp.length < 18) {
643
652
  return null;
644
653
  }
@@ -670,14 +679,14 @@ class IcomControl {
670
679
  if (!targetFrequency) {
671
680
  throw new Error('Unable to resolve scope frequency range without operating frequency');
672
681
  }
673
- const matched = DEFAULT_SCOPE_FREQUENCY_RANGES.find((range) => targetFrequency >= range.lowHz && targetFrequency < range.highHz);
682
+ const matched = this.activeProfile.scopeRanges.find((range) => targetFrequency >= range.lowHz && targetFrequency < range.highHz);
674
683
  if (!matched) {
675
684
  throw new Error(`No scope frequency range matches ${targetFrequency} Hz`);
676
685
  }
677
686
  return matched.rangeId;
678
687
  }
679
688
  getScopeSupportedEdgeSlots() {
680
- return [...DEFAULT_SCOPE_EDGE_SLOTS];
689
+ return [...this.activeProfile.scopeEdgeSlots];
681
690
  }
682
691
  async getSpectrumDisplayState(options) {
683
692
  const receiver = options?.receiver ?? 0;
@@ -706,8 +715,8 @@ class IcomControl {
706
715
  supportedModes: ['center', 'fixed', 'scroll-center', 'scroll-fixed'],
707
716
  supportedSpans: [...DEFAULT_SCOPE_SPANS_HZ],
708
717
  supportedEdgeSlots: this.getScopeSupportedEdgeSlots(),
709
- supportsFixedEdges: true,
710
- supportsEdgeSlotSelection: true,
718
+ supportsFixedEdges: this.activeProfile.scopeRanges.length > 0,
719
+ supportsEdgeSlotSelection: this.activeProfile.scopeEdgeSlots.length > 0,
711
720
  };
712
721
  }
713
722
  async configureSpectrumDisplay(config = {}) {
@@ -800,7 +809,11 @@ class IcomControl {
800
809
  async setFrequency(hz) {
801
810
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
802
811
  const rigAddr = this.civ.civAddress & 0xff;
803
- this.sendCiv(IcomRigCommands_1.IcomRigCommands.setFrequency(ctrAddr, rigAddr, hz));
812
+ const bcdBytes = this.activeProfile.frequencyBcdBytes(hz);
813
+ const frame = this.activeProfile.supportsX25X26
814
+ ? IcomRigCommands_1.IcomRigCommands.setSelectedFrequency(ctrAddr, rigAddr, hz, bcdBytes, 0)
815
+ : IcomRigCommands_1.IcomRigCommands.setFrequency(ctrAddr, rigAddr, hz, bcdBytes);
816
+ this.sendCiv(frame);
804
817
  }
805
818
  /**
806
819
  * Set operating mode
@@ -816,11 +829,16 @@ class IcomControl {
816
829
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
817
830
  const rigAddr = this.civ.civAddress & 0xff;
818
831
  const modeCode = typeof mode === 'string' ? (0, IcomConstants_1.getModeCode)(mode) : mode;
819
- if (options?.dataMode) {
820
- this.sendCiv(IcomRigCommands_1.IcomRigCommands.setOperationDataMode(ctrAddr, rigAddr, modeCode));
832
+ const filter = options?.filter ?? this.lastFilter ?? this.activeProfile.defaultFilter;
833
+ if (this.activeProfile.supportsX25X26 && this.activeProfile.modeWithFilter) {
834
+ this.sendCiv(IcomRigCommands_1.IcomRigCommands.setSelectedMode(ctrAddr, rigAddr, modeCode, !!options?.dataMode, filter, 0));
835
+ return;
836
+ }
837
+ if (options?.dataMode && this.activeProfile.dataModeSupported) {
838
+ this.sendCiv(IcomRigCommands_1.IcomRigCommands.setOperationDataMode(ctrAddr, rigAddr, modeCode, filter));
821
839
  }
822
840
  else {
823
- this.sendCiv(IcomRigCommands_1.IcomRigCommands.setMode(ctrAddr, rigAddr, modeCode));
841
+ this.sendCiv(IcomRigCommands_1.IcomRigCommands.setMode(ctrAddr, rigAddr, modeCode, filter));
824
842
  }
825
843
  }
826
844
  /**
@@ -835,12 +853,16 @@ class IcomControl {
835
853
  const timeoutMs = options?.timeout ?? 3000;
836
854
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
837
855
  const rigAddr = this.civ.civAddress & 0xff;
838
- const req = IcomRigCommands_1.IcomRigCommands.readOperatingFrequency(ctrAddr, rigAddr);
839
- const resp = await this.waitForCivFrame((frame) => IcomControl.isReplyOf(frame, 0x03, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
856
+ const useX25 = this.activeProfile.supportsX25X26;
857
+ const req = useX25
858
+ ? IcomRigCommands_1.IcomRigCommands.readSelectedFrequency(ctrAddr, rigAddr, 0)
859
+ : IcomRigCommands_1.IcomRigCommands.readOperatingFrequency(ctrAddr, rigAddr);
860
+ const resp = await this.waitForCivFrame(useX25 ? 'freq:0x25:0' : 'freq:0x03', (frame) => useX25
861
+ ? IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_SEND_SEL_FREQ, [0x00], ctrAddr, rigAddr)
862
+ : IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_RD_FREQ, [], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
840
863
  if (!resp)
841
864
  return null;
842
- const freq = IcomControl.parseIcomFreqFromReply(resp);
843
- return freq;
865
+ return IcomControl.parseFrequencyReply(resp, useX25 ? 1 : 0);
844
866
  }
845
867
  /**
846
868
  * Read current operating mode and filter
@@ -850,78 +872,56 @@ class IcomControl {
850
872
  const timeoutMs = options?.timeout ?? 3000;
851
873
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
852
874
  const rigAddr = this.civ.civAddress & 0xff;
853
- const req = IcomRigCommands_1.IcomRigCommands.readOperatingMode(ctrAddr, rigAddr);
854
- const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, 0x04, [], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
875
+ const useX26 = this.activeProfile.supportsX25X26 && this.activeProfile.modeWithFilter;
876
+ const req = useX26 ? IcomRigCommands_1.IcomRigCommands.readSelectedMode(ctrAddr, rigAddr, 0) : IcomRigCommands_1.IcomRigCommands.readOperatingMode(ctrAddr, rigAddr);
877
+ const resp = await this.waitForCivFrame(useX26 ? 'mode:0x26:0' : 'mode:0x04', (frame) => useX26
878
+ ? IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_SEND_SEL_MODE, [0x00], ctrAddr, rigAddr)
879
+ : IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_RD_MODE, [], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
855
880
  if (!resp)
856
881
  return null;
857
- // Expect FE FE [ctr] [rig] 0x04 [mode] [filter] FD (some rigs may omit filter)
858
- const mode = resp.length > 5 ? resp[5] : undefined;
859
- const filter = resp.length > 6 ? resp[6] : undefined;
882
+ const mode = useX26 ? resp[6] : resp[5];
883
+ const dataMode = useX26 ? resp[7] !== 0x00 : undefined;
884
+ const filter = useX26 ? resp[8] : (resp.length > 6 ? resp[6] : undefined);
860
885
  if (mode === undefined)
861
886
  return null;
862
- // Map names using constants
887
+ if (filter === 1 || filter === 2 || filter === 3)
888
+ this.lastFilter = filter;
863
889
  const { getModeString, getFilterString } = await Promise.resolve().then(() => __importStar(require('./IcomConstants')));
864
890
  const modeName = getModeString(mode);
865
891
  const filterName = getFilterString(filter);
866
- return { mode, filter, modeName, filterName };
892
+ return { mode, filter, modeName, filterName, dataMode };
867
893
  }
868
894
  /**
869
895
  * Read current transmit frequency (when TX)
870
896
  */
871
897
  async readTransmitFrequency(options) {
898
+ if (!this.activeProfile.supportsX1C03TxFreq)
899
+ return null;
872
900
  const timeoutMs = options?.timeout ?? 3000;
873
901
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
874
902
  const rigAddr = this.civ.civAddress & 0xff;
875
903
  const req = IcomRigCommands_1.IcomRigCommands.readTransmitFrequency(ctrAddr, rigAddr);
876
- const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, 0x1c, [0x03], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
904
+ const resp = await this.waitForCivFrame('freq:0x1c:0x03', (frame) => IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_CTL_PTT, [IcomCivSpec_1.CIV.S_RD_TX_FREQ], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
877
905
  if (!resp)
878
906
  return null;
879
- // Parse BCD like readOperatingFrequency, but starting after [0x1c, 0x03]
880
- // Find 0x1c position and read next 2 bytes (0x03 + 5 BCD bytes)
881
- let idx = resp.indexOf(0x1c, 4);
882
- if (idx < 0 || idx + 6 >= resp.length)
883
- idx = 4;
884
- if (idx + 6 >= resp.length)
885
- return null;
886
- // After 0x1c 0x03, we expect 5 BCD bytes
887
- if (resp[idx + 1] !== 0x03)
888
- return null;
889
- const d0 = resp[idx + 2];
890
- const d1 = resp[idx + 3];
891
- const d2 = resp[idx + 4];
892
- const d3 = resp[idx + 5];
893
- const d4 = resp[idx + 6];
894
- const bcdToInt = (b) => ((b >> 4) & 0x0f) * 10 + (b & 0x0f);
895
- const v0 = bcdToInt(d0);
896
- const v1 = bcdToInt(d1);
897
- const v2 = bcdToInt(d2);
898
- const v3 = bcdToInt(d3);
899
- const v4 = bcdToInt(d4);
900
- const hz = v0 + v1 * 100 + v2 * 10000 + v3 * 1000000 + v4 * 100000000;
901
- return hz;
907
+ return IcomControl.parseFrequencyReply(resp, 1);
902
908
  }
903
- /**
904
- * Read transceiver state (TX/RX) via 0x1A 0x00 0x48
905
- * Note: Java comments mark this as not recommended; use with caution.
906
- */
907
- async readTransceiverState(options) {
909
+ async readPtt(options) {
908
910
  const timeoutMs = options?.timeout ?? 3000;
909
911
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
910
912
  const rigAddr = this.civ.civAddress & 0xff;
911
- const req = IcomRigCommands_1.IcomRigCommands.readTransceiverState(ctrAddr, rigAddr);
912
- const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, 0x1a, [0x00, 0x48], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
913
- if (!resp)
913
+ const req = IcomRigCommands_1.IcomRigCommands.readPTT(ctrAddr, rigAddr);
914
+ const resp = await this.waitForCivFrame('ptt:0x1c:0x00', (frame) => IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_CTL_PTT, [IcomCivSpec_1.CIV.S_PTT], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
915
+ if (!resp || resp.length < 7)
914
916
  return null;
915
- // Heuristic: take first data byte after subcmd2 as state
916
- const pos = 5 + 2; // after 0x1a [0x00,0x48]
917
- const state = resp.length > pos ? resp[pos] : undefined;
918
- if (state === undefined)
919
- return 'UNKNOWN';
920
- if (state === 0x01)
921
- return 'TX';
922
- if (state === 0x00)
923
- return 'RX';
924
- return 'UNKNOWN';
917
+ return resp[6] !== 0x00;
918
+ }
919
+ /** Read transceiver state (TX/RX) using standard Hamlib-aligned PTT status. */
920
+ async readTransceiverState(options) {
921
+ const ptt = await this.readPtt(options);
922
+ if (ptt === null)
923
+ return null;
924
+ return ptt ? 'TX' : 'RX';
925
925
  }
926
926
  /**
927
927
  * Read band edge data (0x02). Format may vary by rig; returns raw data bytes after command.
@@ -931,7 +931,7 @@ class IcomControl {
931
931
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
932
932
  const rigAddr = this.civ.civAddress & 0xff;
933
933
  const req = IcomRigCommands_1.IcomRigCommands.readBandEdges(ctrAddr, rigAddr);
934
- const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, 0x02, [], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
934
+ const resp = await this.waitForCivFrame('band:0x02', (frame) => IcomControl.matchCommandFrame(frame, 0x02, [], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
935
935
  if (!resp)
936
936
  return null;
937
937
  // Return raw payload bytes after command
@@ -952,14 +952,14 @@ class IcomControl {
952
952
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
953
953
  const rigAddr = this.civ.civAddress & 0xff;
954
954
  const req = IcomRigCommands_1.IcomRigCommands.getSWRState(ctrAddr, rigAddr);
955
- const resp = await this.waitForCivFrame((frame) => IcomControl.isMeterReply(frame, 0x12, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
955
+ const resp = await this.waitForCivFrame('meter:0x15:0x12', (frame) => IcomControl.isMeterReply(frame, 0x12, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
956
956
  const raw = IcomControl.extractMeterData(resp);
957
957
  if (raw === null)
958
958
  return null;
959
959
  return {
960
960
  raw,
961
- swr: raw / 100,
962
- alert: raw >= IcomConstants_1.METER_THRESHOLDS.SWR_ALERT
961
+ swr: (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.swr),
962
+ alert: (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.swr) >= 3.0
963
963
  };
964
964
  }
965
965
  /**
@@ -977,14 +977,14 @@ class IcomControl {
977
977
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
978
978
  const rigAddr = this.civ.civAddress & 0xff;
979
979
  const req = IcomRigCommands_1.IcomRigCommands.getALCState(ctrAddr, rigAddr);
980
- const resp = await this.waitForCivFrame((frame) => IcomControl.isMeterReply(frame, 0x13, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
980
+ const resp = await this.waitForCivFrame('meter:0x15:0x13', (frame) => IcomControl.isMeterReply(frame, 0x13, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
981
981
  const raw = IcomControl.extractMeterData(resp);
982
982
  if (raw === null)
983
983
  return null;
984
984
  return {
985
985
  raw,
986
- percent: (raw / IcomConstants_1.METER_THRESHOLDS.ALC_MAX) * 100,
987
- alert: raw > IcomConstants_1.METER_THRESHOLDS.ALC_ALERT_MAX
986
+ percent: (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.alc),
987
+ alert: (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.alc) > 100
988
988
  };
989
989
  }
990
990
  /**
@@ -998,12 +998,15 @@ class IcomControl {
998
998
  * }
999
999
  */
1000
1000
  async getConnectorWLanLevel(options) {
1001
+ const ext = this.activeProfile.vendorExtensions.connectorWlanLevel;
1002
+ if (!ext)
1003
+ return null;
1001
1004
  const timeoutMs = options?.timeout ?? 3000;
1002
1005
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1003
1006
  const rigAddr = this.civ.civAddress & 0xff;
1004
- const req = IcomRigCommands_1.IcomRigCommands.getConnectorWLanLevel(ctrAddr, rigAddr);
1005
- const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, 0x1a, [0x05, 0x01, 0x17], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1006
- const raw = IcomControl.extractMeterData(resp);
1007
+ const req = IcomRigCommands_1.IcomRigCommands.getConnectorWLanLevel(ctrAddr, rigAddr, ext.subext);
1008
+ const resp = await this.waitForCivFrame(`ext:0x${ext.command.toString(16)}:0x${ext.subcmd.toString(16)}:${ext.subext.join('.')}`, (frame) => IcomControl.matchCommandFrame(frame, ext.command, [ext.subcmd, ...ext.subext], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1009
+ const raw = IcomControl.extractTrailingBcd(resp, ext.dataBytes);
1007
1010
  if (raw === null)
1008
1011
  return null;
1009
1012
  return {
@@ -1033,25 +1036,51 @@ class IcomControl {
1033
1036
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1034
1037
  const rigAddr = this.civ.civAddress & 0xff;
1035
1038
  const req = IcomRigCommands_1.IcomRigCommands.getLevelMeter(ctrAddr, rigAddr);
1036
- const resp = await this.waitForCivFrame((frame) => IcomControl.isMeterReply(frame, 0x02, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1039
+ const resp = await this.waitForCivFrame('meter:0x15:0x02', (frame) => IcomControl.isMeterReply(frame, 0x02, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1037
1040
  if (!resp)
1038
1041
  return null;
1039
1042
  const data = resp.subarray(6, resp.length - 1);
1040
1043
  if (data.length === 0)
1041
1044
  return null;
1042
1045
  const raw = data[data.length - 1] & 0xff; // use low byte as 0-255 level
1043
- // Convert raw value to S-meter reading with physical units
1044
- // Uses IC-705 calibration by default (can be extended to support other models)
1045
- return (0, smeter_1.rawToSMeter)(raw, 'IC-705');
1046
+ return (0, smeter_1.rawToSMeter)(raw, this.activeProfile.calibrations.sMeterModel);
1046
1047
  }
1047
1048
  /**
1048
1049
  * Set WLAN connector audio level
1049
1050
  * @param level - Audio level (0-255)
1050
1051
  */
1051
1052
  async setConnectorWLanLevel(level) {
1053
+ const ext = this.activeProfile.vendorExtensions.connectorWlanLevel;
1054
+ if (!ext) {
1055
+ throw new errors_1.UnsupportedCommandError({ modelId: this.getProfileModelId(), commandName: 'setConnectorWLanLevel', civCommand: '0x1a/0x05', reason: 'No vendor WLAN level extension for active profile' });
1056
+ }
1057
+ const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1058
+ const rigAddr = this.civ.civAddress & 0xff;
1059
+ this.sendCiv(IcomRigCommands_1.IcomRigCommands.setConnectorWLanLevel(ctrAddr, rigAddr, level, ext.subext));
1060
+ }
1061
+ async getUsbAfLevel(options) {
1062
+ const ext = this.activeProfile.extParams.usbAfLevel;
1063
+ if (!ext)
1064
+ return null;
1065
+ const timeoutMs = options?.timeout ?? 3000;
1066
+ const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1067
+ const rigAddr = this.civ.civAddress & 0xff;
1068
+ const req = IcomRigCommands_1.IcomRigCommands.getUsbAfLevel(ctrAddr, rigAddr, ext.subext);
1069
+ const resp = await this.waitForCivFrame(`ext:0x${ext.command.toString(16)}:0x${ext.subcmd.toString(16)}:${ext.subext.join('.')}`, (frame) => IcomControl.matchCommandFrame(frame, ext.command, [ext.subcmd, ...ext.subext], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1070
+ const raw = IcomControl.extractTrailingBcd(resp, ext.dataBytes);
1071
+ if (raw === null)
1072
+ return null;
1073
+ return { raw, percent: (raw / 255) * 100 };
1074
+ }
1075
+ async setUsbAfLevel(level) {
1076
+ const ext = this.activeProfile.extParams.usbAfLevel;
1077
+ if (!ext) {
1078
+ throw new errors_1.UnsupportedCommandError({ modelId: this.getProfileModelId(), commandName: 'setUsbAfLevel', civCommand: '0x1a/0x05', reason: 'No USB AF level extension for active profile' });
1079
+ }
1052
1080
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1053
1081
  const rigAddr = this.civ.civAddress & 0xff;
1054
- this.sendCiv(IcomRigCommands_1.IcomRigCommands.setConnectorWLanLevel(ctrAddr, rigAddr, level));
1082
+ const raw = Math.max(0, Math.min(255, Math.round(level)));
1083
+ this.sendCiv(IcomRigCommands_1.IcomRigCommands.setUsbAfLevel(ctrAddr, rigAddr, raw, ext.subext));
1055
1084
  }
1056
1085
  /**
1057
1086
  * Set connector data routing mode
@@ -1061,10 +1090,14 @@ class IcomControl {
1061
1090
  * await rig.setConnectorDataMode('WLAN');
1062
1091
  */
1063
1092
  async setConnectorDataMode(mode) {
1093
+ const ext = this.activeProfile.vendorExtensions.connectorDataMode;
1094
+ if (!ext) {
1095
+ throw new errors_1.UnsupportedCommandError({ modelId: this.getProfileModelId(), commandName: 'setConnectorDataMode', civCommand: '0x1a/0x05', reason: 'No vendor connector data-mode extension for active profile' });
1096
+ }
1064
1097
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1065
1098
  const rigAddr = this.civ.civAddress & 0xff;
1066
1099
  const modeCode = typeof mode === 'string' ? (0, IcomConstants_1.getConnectorModeCode)(mode) : mode;
1067
- this.sendCiv(IcomRigCommands_1.IcomRigCommands.setConnectorDataMode(ctrAddr, rigAddr, modeCode));
1100
+ this.sendCiv(IcomRigCommands_1.IcomRigCommands.setConnectorDataMode(ctrAddr, rigAddr, modeCode, ext.subext));
1068
1101
  }
1069
1102
  /**
1070
1103
  * ==============================
@@ -1072,7 +1105,7 @@ class IcomControl {
1072
1105
  * ==============================
1073
1106
  */
1074
1107
  /**
1075
- * Read antenna tuner status (CI-V 0x1A/0x00)
1108
+ * Read antenna tuner status (CI-V 0x1C/0x01)
1076
1109
  * 00=OFF, 01=ON, 02=TUNING
1077
1110
  */
1078
1111
  async readTunerStatus(options) {
@@ -1080,10 +1113,10 @@ class IcomControl {
1080
1113
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1081
1114
  const rigAddr = this.civ.civAddress & 0xff;
1082
1115
  const req = IcomRigCommands_1.IcomRigCommands.getTunerStatus(ctrAddr, rigAddr);
1083
- const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, 0x1a, [0x00], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1116
+ const resp = await this.waitForCivFrame('tuner:0x1c:0x01', (frame) => IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_CTL_PTT, [IcomCivSpec_1.CIV.S_ANT_TUN], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1084
1117
  if (!resp)
1085
1118
  return null;
1086
- // Expect FE FE [ctr] [rig] 0x1A 0x00 [status] FD
1119
+ // Expect FE FE [ctr] [rig] 0x1C 0x01 [status] FD
1087
1120
  const raw = resp.length > 6 ? (resp[6] & 0xff) : undefined;
1088
1121
  if (raw === undefined)
1089
1122
  return null;
@@ -1091,7 +1124,7 @@ class IcomControl {
1091
1124
  return { raw, state };
1092
1125
  }
1093
1126
  /**
1094
- * Enable or disable internal antenna tuner (CI-V 0x1A/0x01)
1127
+ * Enable or disable internal antenna tuner (CI-V 0x1C/0x01)
1095
1128
  * @param enabled true to enable, false to disable
1096
1129
  */
1097
1130
  async setTunerEnabled(enabled) {
@@ -1100,7 +1133,7 @@ class IcomControl {
1100
1133
  this.sendCiv(IcomRigCommands_1.IcomRigCommands.setTunerEnabled(ctrAddr, rigAddr, enabled));
1101
1134
  }
1102
1135
  /**
1103
- * Start a manual tuning cycle (same as [TUNE] key) (CI-V 0x1A/0x02/0x00)
1136
+ * Start a manual tuning cycle (same as [TUNE] key) (CI-V 0x1C/0x01/0x02)
1104
1137
  */
1105
1138
  async startManualTune() {
1106
1139
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
@@ -1145,14 +1178,25 @@ class IcomControl {
1145
1178
  }
1146
1179
  /** Get microphone gain. Returns 0.0–1.0, or null on timeout. */
1147
1180
  async getMicGain(options) {
1148
- const v = await this.read0x14Level(0x0f, options);
1181
+ const v = await this.read0x14Level(IcomCivSpec_1.CIV.S_LVL_MICGAIN, options);
1149
1182
  if (v === null)
1150
1183
  return null;
1151
1184
  return { raw: Math.round(v * 255), normalized: v };
1152
1185
  }
1153
1186
  /** Set microphone gain. Value 0.0–1.0. */
1154
1187
  setMicGain(value) {
1155
- this.write0x14Level(0x0f, value);
1188
+ this.write0x14Level(IcomCivSpec_1.CIV.S_LVL_MICGAIN, value);
1189
+ }
1190
+ /** Get break-in delay. Returns 0.0–1.0, or null on timeout. */
1191
+ async getBreakInDelay(options) {
1192
+ const v = await this.read0x14Level(IcomCivSpec_1.CIV.S_LVL_BKINDL, options);
1193
+ if (v === null)
1194
+ return null;
1195
+ return { raw: Math.round(v * 255), normalized: v };
1196
+ }
1197
+ /** Set break-in delay. Value 0.0–1.0. */
1198
+ setBreakInDelay(value) {
1199
+ this.write0x14Level(IcomCivSpec_1.CIV.S_LVL_BKINDL, value);
1156
1200
  }
1157
1201
  /** Get noise blanker level. 0.0 = off, >0.0 = on with strength. */
1158
1202
  async getNBLevel(options) {
@@ -1167,14 +1211,14 @@ class IcomControl {
1167
1211
  }
1168
1212
  /** Get noise reduction level. 0.0 = off, >0.0 = on with strength. */
1169
1213
  async getNRLevel(options) {
1170
- const v = await this.read0x14Level(0x13, options);
1214
+ const v = await this.read0x14Level(IcomCivSpec_1.CIV.S_LVL_NR, options);
1171
1215
  if (v === null)
1172
1216
  return null;
1173
1217
  return { raw: Math.round(v * 255), normalized: v };
1174
1218
  }
1175
1219
  /** Set noise reduction level. Value 0.0 (off) – 1.0. */
1176
1220
  setNRLevel(value) {
1177
- this.write0x14Level(0x13, value);
1221
+ this.write0x14Level(IcomCivSpec_1.CIV.S_LVL_NR, value);
1178
1222
  }
1179
1223
  /**
1180
1224
  * Read squelch status (noise/signal gate state)
@@ -1191,7 +1235,7 @@ class IcomControl {
1191
1235
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1192
1236
  const rigAddr = this.civ.civAddress & 0xff;
1193
1237
  const req = IcomRigCommands_1.IcomRigCommands.getSquelchStatus(ctrAddr, rigAddr);
1194
- const resp = await this.waitForCivFrame((frame) => IcomControl.isMeterReply(frame, 0x01, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1238
+ const resp = await this.waitForCivFrame('meter:0x15:0x01', (frame) => IcomControl.isMeterReply(frame, 0x01, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1195
1239
  const raw = IcomControl.extractMeterData(resp);
1196
1240
  if (raw === null)
1197
1241
  return null;
@@ -1215,7 +1259,7 @@ class IcomControl {
1215
1259
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1216
1260
  const rigAddr = this.civ.civAddress & 0xff;
1217
1261
  const req = IcomRigCommands_1.IcomRigCommands.getAudioSquelch(ctrAddr, rigAddr);
1218
- const resp = await this.waitForCivFrame((frame) => IcomControl.isMeterReply(frame, 0x05, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1262
+ const resp = await this.waitForCivFrame('meter:0x15:0x05', (frame) => IcomControl.isMeterReply(frame, 0x05, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1219
1263
  const raw = IcomControl.extractMeterData(resp);
1220
1264
  if (raw === null)
1221
1265
  return null;
@@ -1239,7 +1283,7 @@ class IcomControl {
1239
1283
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1240
1284
  const rigAddr = this.civ.civAddress & 0xff;
1241
1285
  const req = IcomRigCommands_1.IcomRigCommands.getOvfStatus(ctrAddr, rigAddr);
1242
- const resp = await this.waitForCivFrame((frame) => IcomControl.isMeterReply(frame, 0x07, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1286
+ const resp = await this.waitForCivFrame('meter:0x15:0x07', (frame) => IcomControl.isMeterReply(frame, 0x07, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1243
1287
  const raw = IcomControl.extractMeterData(resp);
1244
1288
  if (raw === null)
1245
1289
  return null;
@@ -1263,13 +1307,16 @@ class IcomControl {
1263
1307
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1264
1308
  const rigAddr = this.civ.civAddress & 0xff;
1265
1309
  const req = IcomRigCommands_1.IcomRigCommands.getPowerLevel(ctrAddr, rigAddr);
1266
- const resp = await this.waitForCivFrame((frame) => IcomControl.isMeterReply(frame, 0x11, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1310
+ const resp = await this.waitForCivFrame('meter:0x15:0x11', (frame) => IcomControl.isMeterReply(frame, 0x11, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1267
1311
  const raw = IcomControl.extractMeterData(resp);
1268
1312
  if (raw === null)
1269
1313
  return null;
1314
+ const watts = (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.rfPowerWatts);
1315
+ const maxWatts = this.activeProfile.calibrations.rfPowerWatts[this.activeProfile.calibrations.rfPowerWatts.length - 1]?.value ?? 100;
1270
1316
  return {
1271
1317
  raw,
1272
- percent: (0, IcomConstants_1.rawToPowerPercent)(raw)
1318
+ percent: maxWatts > 0 ? Math.min(100, (watts / maxWatts) * 100) : 0,
1319
+ watts
1273
1320
  };
1274
1321
  }
1275
1322
  /**
@@ -1287,13 +1334,15 @@ class IcomControl {
1287
1334
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1288
1335
  const rigAddr = this.civ.civAddress & 0xff;
1289
1336
  const req = IcomRigCommands_1.IcomRigCommands.getCompLevel(ctrAddr, rigAddr);
1290
- const resp = await this.waitForCivFrame((frame) => IcomControl.isMeterReply(frame, 0x14, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1337
+ const resp = await this.waitForCivFrame('meter:0x15:0x14', (frame) => IcomControl.isMeterReply(frame, 0x14, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1291
1338
  const raw = IcomControl.extractMeterData(resp);
1292
1339
  if (raw === null)
1293
1340
  return null;
1341
+ const db = (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.compDb);
1294
1342
  return {
1295
1343
  raw,
1296
- percent: (raw / 255) * 100
1344
+ percent: (db / 30) * 100,
1345
+ db
1297
1346
  };
1298
1347
  }
1299
1348
  /**
@@ -1311,13 +1360,13 @@ class IcomControl {
1311
1360
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1312
1361
  const rigAddr = this.civ.civAddress & 0xff;
1313
1362
  const req = IcomRigCommands_1.IcomRigCommands.getVoltage(ctrAddr, rigAddr);
1314
- const resp = await this.waitForCivFrame((frame) => IcomControl.isMeterReply(frame, 0x15, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1363
+ const resp = await this.waitForCivFrame('meter:0x15:0x15', (frame) => IcomControl.isMeterReply(frame, 0x15, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1315
1364
  const raw = IcomControl.extractMeterData(resp);
1316
1365
  if (raw === null)
1317
1366
  return null;
1318
1367
  return {
1319
1368
  raw,
1320
- volts: (0, IcomConstants_1.rawToVoltage)(raw)
1369
+ volts: (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.voltage)
1321
1370
  };
1322
1371
  }
1323
1372
  /**
@@ -1335,13 +1384,13 @@ class IcomControl {
1335
1384
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1336
1385
  const rigAddr = this.civ.civAddress & 0xff;
1337
1386
  const req = IcomRigCommands_1.IcomRigCommands.getCurrent(ctrAddr, rigAddr);
1338
- const resp = await this.waitForCivFrame((frame) => IcomControl.isMeterReply(frame, 0x16, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1387
+ const resp = await this.waitForCivFrame('meter:0x15:0x16', (frame) => IcomControl.isMeterReply(frame, 0x16, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1339
1388
  const raw = IcomControl.extractMeterData(resp);
1340
1389
  if (raw === null)
1341
1390
  return null;
1342
1391
  return {
1343
1392
  raw,
1344
- amps: (0, IcomConstants_1.rawToCurrent)(raw)
1393
+ amps: (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.current)
1345
1394
  };
1346
1395
  }
1347
1396
  static isReplyOf(frame, cmd, ctrAddr, rigAddr) {
@@ -1357,9 +1406,17 @@ class IcomControl {
1357
1406
  static extractMeterData(frame) {
1358
1407
  if (!frame || frame.length < 9)
1359
1408
  return null;
1360
- // Extract 2-byte BCD data at position 6-7 of the CI-V frame (FE FE [ctr] [rig] 0x15 [sub] [b0] [b1] FD)
1361
- const bcdData = frame.subarray(6, 8);
1362
- return (0, bcd_1.parseTwoByteBcd)(bcdData);
1409
+ // FE FE [ctr] [rig] 0x15 [sub] [bcd_hi] [bcd_lo] FD
1410
+ return (0, IcomCivFrame_1.decodeBcdBE)(frame.subarray(6, 8));
1411
+ }
1412
+ static extractTrailingBcd(frame, byteLength) {
1413
+ if (!frame || frame.length < 6 + byteLength)
1414
+ return null;
1415
+ const end = frame.length - 1;
1416
+ const start = end - byteLength;
1417
+ if (start < 5)
1418
+ return null;
1419
+ return (0, IcomCivFrame_1.decodeBcdBE)(frame.subarray(start, end));
1363
1420
  }
1364
1421
  static matchCommand(frame, cmd, tail) {
1365
1422
  // FE FE ?? ?? cmd ... tail... FD
@@ -1429,34 +1486,23 @@ class IcomControl {
1429
1486
  }, timeoutMs);
1430
1487
  });
1431
1488
  }
1432
- // Parse CI-V reply for command 0x03 (read operating frequency)
1433
- static parseIcomFreqFromReply(frame) {
1434
- // Expect: FE FE [ctr] [rig] 0x03 [bcd0..bcd4] FD
1489
+ static parseFrequencyReply(frame, payloadOffsetAfterCommand, byteLength) {
1435
1490
  if (!(frame && frame.length >= 7))
1436
1491
  return null;
1437
- if (frame[0] !== 0xfe || frame[1] !== 0xfe)
1492
+ if (frame[0] !== 0xfe || frame[1] !== 0xfe || frame[frame.length - 1] !== 0xfd)
1438
1493
  return null;
1439
- if (frame[4] !== 0x03)
1494
+ const start = 5 + payloadOffsetAfterCommand;
1495
+ const maxBytes = frame.length - 1 - start;
1496
+ const len = byteLength ?? (maxBytes >= 6 ? 6 : 5);
1497
+ if (maxBytes < len || len <= 0)
1440
1498
  return null;
1441
- // Some radios may include extra bytes; find 0x03 and read next 5 bytes
1442
- let idx = frame.indexOf(0x03, 5);
1443
- if (idx < 0 || idx + 5 >= frame.length)
1444
- idx = 4; // fallback to standard position
1445
- if (idx + 5 >= frame.length)
1499
+ return (0, IcomCivFrame_1.decodeFrequencyBcdLE)(frame.subarray(start, start + len));
1500
+ }
1501
+ // Parse standard CI-V 0x03 read-frequency replies.
1502
+ static parseIcomFreqFromReply(frame) {
1503
+ if (!IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_RD_FREQ, []))
1446
1504
  return null;
1447
- const d0 = frame[idx + 1];
1448
- const d1 = frame[idx + 2];
1449
- const d2 = frame[idx + 3];
1450
- const d3 = frame[idx + 4];
1451
- const d4 = frame[idx + 5];
1452
- const bcdToInt = (b) => ((b >> 4) & 0x0f) * 10 + (b & 0x0f);
1453
- const v0 = bcdToInt(d0);
1454
- const v1 = bcdToInt(d1);
1455
- const v2 = bcdToInt(d2);
1456
- const v3 = bcdToInt(d3);
1457
- const v4 = bcdToInt(d4);
1458
- const hz = v0 + v1 * 100 + v2 * 10000 + v3 * 1000000 + v4 * 100000000;
1459
- return hz;
1505
+ return IcomControl.parseFrequencyReply(frame, 0);
1460
1506
  }
1461
1507
  sendAudioFloat32(samples, addLeadingBuffer = false) {
1462
1508
  this.audio.enqueueFloat32(samples, addLeadingBuffer);
@@ -1591,11 +1637,15 @@ class IcomControl {
1591
1637
  audioName: IcomPackets_1.RadioCapPacket.getAudioName(cap),
1592
1638
  supportTX: IcomPackets_1.RadioCapPacket.getSupportTX(cap)
1593
1639
  };
1594
- if (info.civAddress != null)
1640
+ if (info.civAddress != null) {
1595
1641
  this.civ.civAddress = info.civAddress;
1642
+ this.resolveActiveProfile({ civAddress: info.civAddress });
1643
+ info.modelId = this.activeProfile.modelId;
1644
+ info.profileName = this.activeProfile.profileName;
1645
+ }
1596
1646
  if (info.supportTX != null)
1597
1647
  this.civ.supportTX = info.supportTX;
1598
- (0, debug_1.dbgV)('CAP <= civAddr=', info.civAddress, 'audioName=', info.audioName, 'supportTX=', info.supportTX);
1648
+ (0, debug_1.dbgV)('CAP <= civAddr=', info.civAddress, 'audioName=', info.audioName, 'supportTX=', info.supportTX, 'profile=', info.modelId);
1599
1649
  this.ev.emit('capabilities', info);
1600
1650
  }
1601
1651
  break;
@@ -1607,7 +1657,8 @@ class IcomControl {
1607
1657
  const busy = IcomPackets_1.ConnInfoPacket.getBusy(buf);
1608
1658
  this.macAddress = IcomPackets_1.ConnInfoPacket.getMacAddress(buf);
1609
1659
  this.rigName = IcomPackets_1.ConnInfoPacket.getRigName(buf);
1610
- (0, debug_1.dbg)('CONNINFO <= busy=', busy, 'rigName=', this.rigName);
1660
+ this.resolveActiveProfile({ rigName: this.rigName });
1661
+ (0, debug_1.dbg)('CONNINFO <= busy=', busy, 'rigName=', this.rigName, 'profile=', this.activeProfile.modelId);
1611
1662
  if (busy) {
1612
1663
  (0, debug_1.dbg)('CONNINFO busy=true detected - likely reconnecting while rig still has old session');
1613
1664
  (0, debug_1.dbg)('Sending ConnInfo reply anyway to allow STATUS packet delivery');
@@ -1773,26 +1824,14 @@ class IcomControl {
1773
1824
  // Continue loop in case multiple frames are in buffer
1774
1825
  }
1775
1826
  }
1776
- // Wait for single CI-V frame that matches predicate (fed by civFrame event)
1777
- async waitForCivFrame(predicate, timeoutMs, onSend) {
1778
- return new Promise((resolve) => {
1779
- let done = false;
1780
- const onFrame = (frame) => {
1781
- if (!done && predicate(frame)) {
1782
- done = true;
1783
- this.ev.off('civFrame', onFrame);
1784
- resolve(frame);
1785
- }
1786
- };
1787
- this.ev.on('civFrame', onFrame);
1788
- if (onSend)
1789
- onSend();
1790
- setTimeout(() => {
1791
- if (!done) {
1792
- this.ev.off('civFrame', onFrame);
1793
- resolve(null);
1794
- }
1795
- }, timeoutMs);
1827
+ // Wait for a CI-V reply by response key. Same-key queries are deduplicated.
1828
+ async waitForCivFrame(key, predicate, timeoutMs, onSend) {
1829
+ return this.civRequestManager.query({
1830
+ key,
1831
+ predicate,
1832
+ timeoutMs,
1833
+ send: () => { if (onSend)
1834
+ onSend(); },
1796
1835
  });
1797
1836
  }
1798
1837
  // Strict meter reply matcher: FE FE [ctr|00] [rig] 0x15 [sub] ... FD
@@ -1840,7 +1879,7 @@ class IcomControl {
1840
1879
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1841
1880
  const rigAddr = this.civ.civAddress & 0xff;
1842
1881
  const req = IcomRigCommands_1.IcomRigCommands.get0x14Level(ctrAddr, rigAddr, subcmd);
1843
- const resp = await this.waitForCivFrame((frame) => IcomControl.is0x14DataReply(frame, subcmd, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1882
+ const resp = await this.waitForCivFrame(`level:0x14:0x${subcmd.toString(16)}`, (frame) => IcomControl.is0x14DataReply(frame, subcmd, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1844
1883
  if (!resp || resp.length < 9)
1845
1884
  return null;
1846
1885
  const raw = (0, bcd_1.parseTwoByteBcd)(resp.subarray(6, 8));