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.
- package/README.md +83 -22
- package/dist/index.d.ts +4 -1
- package/dist/index.js +15 -1
- package/dist/rig/IcomCivFrame.d.ts +13 -0
- package/dist/rig/IcomCivFrame.js +72 -0
- package/dist/rig/IcomCivRequestManager.d.ts +22 -0
- package/dist/rig/IcomCivRequestManager.js +74 -0
- package/dist/rig/IcomCivSpec.d.ts +54 -0
- package/dist/rig/IcomCivSpec.js +57 -0
- package/dist/rig/IcomControl.d.ts +21 -7
- package/dist/rig/IcomControl.js +214 -175
- package/dist/rig/IcomProfiles.d.ts +61 -0
- package/dist/rig/IcomProfiles.js +255 -0
- package/dist/rig/IcomRigCommands.d.ts +13 -15
- package/dist/rig/IcomRigCommands.js +66 -86
- package/dist/scope/IcomScopeCommands.js +27 -54
- package/dist/types.d.ts +19 -8
- package/dist/utils/errors.d.ts +13 -0
- package/dist/utils/errors.js +17 -1
- package/package.json +2 -2
package/dist/rig/IcomControl.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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 =
|
|
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 [...
|
|
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:
|
|
710
|
-
supportsEdgeSlotSelection:
|
|
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.
|
|
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
|
-
|
|
820
|
-
|
|
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
|
|
839
|
-
const
|
|
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
|
-
|
|
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
|
|
854
|
-
const
|
|
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
|
-
|
|
858
|
-
const
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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.
|
|
912
|
-
const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame,
|
|
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
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
if (
|
|
921
|
-
return
|
|
922
|
-
|
|
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
|
|
962
|
-
alert: raw >=
|
|
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
|
|
987
|
-
alert: raw >
|
|
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,
|
|
1006
|
-
const raw = IcomControl.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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]
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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: (
|
|
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,
|
|
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,
|
|
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
|
-
//
|
|
1361
|
-
|
|
1362
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1777
|
-
async waitForCivFrame(predicate, timeoutMs, onSend) {
|
|
1778
|
-
return
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
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));
|