icom-wlan-node 0.5.1 → 0.6.0
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 +62 -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/IcomCivSpec.d.ts +54 -0
- package/dist/rig/IcomCivSpec.js +57 -0
- package/dist/rig/IcomControl.d.ts +20 -7
- package/dist/rig/IcomControl.js +188 -139
- 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,10 @@ 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");
|
|
53
55
|
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
56
|
function modeCodeToName(mode) {
|
|
74
57
|
switch (mode) {
|
|
75
58
|
case 0: return 'center';
|
|
@@ -92,6 +75,8 @@ class IcomControl {
|
|
|
92
75
|
this.rigName = '';
|
|
93
76
|
this.macAddress = Buffer.alloc(6);
|
|
94
77
|
this.civAssembleBuf = Buffer.alloc(0); // CIV stream reassembler
|
|
78
|
+
this.activeProfile = (0, IcomProfiles_1.getProfileByModel)('generic-modern-icom');
|
|
79
|
+
this.lastFilter = 1;
|
|
95
80
|
// Connection state machine (replaces old fragmented state flags)
|
|
96
81
|
this.connectionSession = {
|
|
97
82
|
phase: types_1.ConnectionPhase.IDLE,
|
|
@@ -109,7 +94,8 @@ class IcomControl {
|
|
|
109
94
|
reconnectBaseDelay: 2000,
|
|
110
95
|
reconnectMaxDelay: 30000
|
|
111
96
|
};
|
|
112
|
-
this.options = options;
|
|
97
|
+
this.options = { ...options, model: options.model ?? 'auto' };
|
|
98
|
+
this.activeProfile = (0, IcomProfiles_1.resolveIcomProfile)({ requestedModel: this.options.model });
|
|
113
99
|
// Setup control session
|
|
114
100
|
this.sess = new Session_1.Session({ ip: options.control.ip, port: options.control.port }, {
|
|
115
101
|
onData: (data) => this.onData(data),
|
|
@@ -137,6 +123,25 @@ class IcomControl {
|
|
|
137
123
|
this.scope.on('scopeFrame', (frame) => this.ev.emit('scopeFrame', frame));
|
|
138
124
|
}
|
|
139
125
|
get events() { return this.ev; }
|
|
126
|
+
get profile() { return this.activeProfile; }
|
|
127
|
+
resolveActiveProfile(context = {}) {
|
|
128
|
+
const next = (0, IcomProfiles_1.resolveIcomProfile)({
|
|
129
|
+
requestedModel: this.options.model,
|
|
130
|
+
rigName: context.rigName ?? this.rigName,
|
|
131
|
+
civAddress: context.civAddress ?? this.civ.civAddress,
|
|
132
|
+
});
|
|
133
|
+
if (this.options.model && this.options.model !== 'auto' && context.civAddress !== undefined && next.defaultCivAddress !== (context.civAddress & 0xff)) {
|
|
134
|
+
(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)}`);
|
|
135
|
+
}
|
|
136
|
+
if (next.modelId !== this.activeProfile.modelId) {
|
|
137
|
+
(0, debug_1.dbg)(`ICOM profile selected: ${next.profileName}`);
|
|
138
|
+
}
|
|
139
|
+
this.activeProfile = next;
|
|
140
|
+
this.lastFilter = next.defaultFilter;
|
|
141
|
+
}
|
|
142
|
+
getProfileModelId() {
|
|
143
|
+
return this.activeProfile.modelId;
|
|
144
|
+
}
|
|
140
145
|
// ============================================================================
|
|
141
146
|
// State Machine Management
|
|
142
147
|
// ============================================================================
|
|
@@ -575,10 +580,10 @@ class IcomControl {
|
|
|
575
580
|
if (!resp || resp.length < 13) {
|
|
576
581
|
return null;
|
|
577
582
|
}
|
|
578
|
-
const
|
|
583
|
+
const encodedSpanHz = (0, IcomScopeParser_1.parseIcomBcdFreqLE)(resp.subarray(7, 12));
|
|
579
584
|
return {
|
|
580
585
|
receiver,
|
|
581
|
-
spanHz,
|
|
586
|
+
spanHz: encodedSpanHz * 2,
|
|
582
587
|
};
|
|
583
588
|
}
|
|
584
589
|
async setScopeSpan(spanHz, options) {
|
|
@@ -628,7 +633,9 @@ class IcomControl {
|
|
|
628
633
|
}
|
|
629
634
|
async setScopeEdge(edgeSlot, options) {
|
|
630
635
|
const receiver = options?.receiver ?? 0;
|
|
631
|
-
const
|
|
636
|
+
const slots = this.activeProfile.scopeEdgeSlots;
|
|
637
|
+
const maxSlot = slots.length ? Math.max(...slots) : 4;
|
|
638
|
+
const safeEdgeSlot = Math.max(1, Math.min(maxSlot, Math.trunc(edgeSlot)));
|
|
632
639
|
const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
|
|
633
640
|
const rigAddr = this.civ.civAddress & 0xff;
|
|
634
641
|
this.sendCiv(IcomScopeCommands_1.IcomScopeCommands.setScopeEdge(ctrAddr, rigAddr, safeEdgeSlot, receiver));
|
|
@@ -670,14 +677,14 @@ class IcomControl {
|
|
|
670
677
|
if (!targetFrequency) {
|
|
671
678
|
throw new Error('Unable to resolve scope frequency range without operating frequency');
|
|
672
679
|
}
|
|
673
|
-
const matched =
|
|
680
|
+
const matched = this.activeProfile.scopeRanges.find((range) => targetFrequency >= range.lowHz && targetFrequency < range.highHz);
|
|
674
681
|
if (!matched) {
|
|
675
682
|
throw new Error(`No scope frequency range matches ${targetFrequency} Hz`);
|
|
676
683
|
}
|
|
677
684
|
return matched.rangeId;
|
|
678
685
|
}
|
|
679
686
|
getScopeSupportedEdgeSlots() {
|
|
680
|
-
return [...
|
|
687
|
+
return [...this.activeProfile.scopeEdgeSlots];
|
|
681
688
|
}
|
|
682
689
|
async getSpectrumDisplayState(options) {
|
|
683
690
|
const receiver = options?.receiver ?? 0;
|
|
@@ -706,8 +713,8 @@ class IcomControl {
|
|
|
706
713
|
supportedModes: ['center', 'fixed', 'scroll-center', 'scroll-fixed'],
|
|
707
714
|
supportedSpans: [...DEFAULT_SCOPE_SPANS_HZ],
|
|
708
715
|
supportedEdgeSlots: this.getScopeSupportedEdgeSlots(),
|
|
709
|
-
supportsFixedEdges:
|
|
710
|
-
supportsEdgeSlotSelection:
|
|
716
|
+
supportsFixedEdges: this.activeProfile.scopeRanges.length > 0,
|
|
717
|
+
supportsEdgeSlotSelection: this.activeProfile.scopeEdgeSlots.length > 0,
|
|
711
718
|
};
|
|
712
719
|
}
|
|
713
720
|
async configureSpectrumDisplay(config = {}) {
|
|
@@ -800,7 +807,11 @@ class IcomControl {
|
|
|
800
807
|
async setFrequency(hz) {
|
|
801
808
|
const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
|
|
802
809
|
const rigAddr = this.civ.civAddress & 0xff;
|
|
803
|
-
this.
|
|
810
|
+
const bcdBytes = this.activeProfile.frequencyBcdBytes(hz);
|
|
811
|
+
const frame = this.activeProfile.supportsX25X26
|
|
812
|
+
? IcomRigCommands_1.IcomRigCommands.setSelectedFrequency(ctrAddr, rigAddr, hz, bcdBytes, 0)
|
|
813
|
+
: IcomRigCommands_1.IcomRigCommands.setFrequency(ctrAddr, rigAddr, hz, bcdBytes);
|
|
814
|
+
this.sendCiv(frame);
|
|
804
815
|
}
|
|
805
816
|
/**
|
|
806
817
|
* Set operating mode
|
|
@@ -816,11 +827,16 @@ class IcomControl {
|
|
|
816
827
|
const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
|
|
817
828
|
const rigAddr = this.civ.civAddress & 0xff;
|
|
818
829
|
const modeCode = typeof mode === 'string' ? (0, IcomConstants_1.getModeCode)(mode) : mode;
|
|
819
|
-
|
|
820
|
-
|
|
830
|
+
const filter = options?.filter ?? this.lastFilter ?? this.activeProfile.defaultFilter;
|
|
831
|
+
if (this.activeProfile.supportsX25X26 && this.activeProfile.modeWithFilter) {
|
|
832
|
+
this.sendCiv(IcomRigCommands_1.IcomRigCommands.setSelectedMode(ctrAddr, rigAddr, modeCode, !!options?.dataMode, filter, 0));
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if (options?.dataMode && this.activeProfile.dataModeSupported) {
|
|
836
|
+
this.sendCiv(IcomRigCommands_1.IcomRigCommands.setOperationDataMode(ctrAddr, rigAddr, modeCode, filter));
|
|
821
837
|
}
|
|
822
838
|
else {
|
|
823
|
-
this.sendCiv(IcomRigCommands_1.IcomRigCommands.setMode(ctrAddr, rigAddr, modeCode));
|
|
839
|
+
this.sendCiv(IcomRigCommands_1.IcomRigCommands.setMode(ctrAddr, rigAddr, modeCode, filter));
|
|
824
840
|
}
|
|
825
841
|
}
|
|
826
842
|
/**
|
|
@@ -835,12 +851,16 @@ class IcomControl {
|
|
|
835
851
|
const timeoutMs = options?.timeout ?? 3000;
|
|
836
852
|
const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
|
|
837
853
|
const rigAddr = this.civ.civAddress & 0xff;
|
|
838
|
-
const
|
|
839
|
-
const
|
|
854
|
+
const useX25 = this.activeProfile.supportsX25X26;
|
|
855
|
+
const req = useX25
|
|
856
|
+
? IcomRigCommands_1.IcomRigCommands.readSelectedFrequency(ctrAddr, rigAddr, 0)
|
|
857
|
+
: IcomRigCommands_1.IcomRigCommands.readOperatingFrequency(ctrAddr, rigAddr);
|
|
858
|
+
const resp = await this.waitForCivFrame((frame) => useX25
|
|
859
|
+
? IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_SEND_SEL_FREQ, [0x00], ctrAddr, rigAddr)
|
|
860
|
+
: IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_RD_FREQ, [], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
|
|
840
861
|
if (!resp)
|
|
841
862
|
return null;
|
|
842
|
-
|
|
843
|
-
return freq;
|
|
863
|
+
return IcomControl.parseFrequencyReply(resp, useX25 ? 1 : 0);
|
|
844
864
|
}
|
|
845
865
|
/**
|
|
846
866
|
* Read current operating mode and filter
|
|
@@ -850,78 +870,56 @@ class IcomControl {
|
|
|
850
870
|
const timeoutMs = options?.timeout ?? 3000;
|
|
851
871
|
const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
|
|
852
872
|
const rigAddr = this.civ.civAddress & 0xff;
|
|
853
|
-
const
|
|
854
|
-
const
|
|
873
|
+
const useX26 = this.activeProfile.supportsX25X26 && this.activeProfile.modeWithFilter;
|
|
874
|
+
const req = useX26 ? IcomRigCommands_1.IcomRigCommands.readSelectedMode(ctrAddr, rigAddr, 0) : IcomRigCommands_1.IcomRigCommands.readOperatingMode(ctrAddr, rigAddr);
|
|
875
|
+
const resp = await this.waitForCivFrame((frame) => useX26
|
|
876
|
+
? IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_SEND_SEL_MODE, [0x00], ctrAddr, rigAddr)
|
|
877
|
+
: IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_RD_MODE, [], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
|
|
855
878
|
if (!resp)
|
|
856
879
|
return null;
|
|
857
|
-
|
|
858
|
-
const
|
|
859
|
-
const filter = resp.length > 6 ? resp[6] : undefined;
|
|
880
|
+
const mode = useX26 ? resp[6] : resp[5];
|
|
881
|
+
const dataMode = useX26 ? resp[7] !== 0x00 : undefined;
|
|
882
|
+
const filter = useX26 ? resp[8] : (resp.length > 6 ? resp[6] : undefined);
|
|
860
883
|
if (mode === undefined)
|
|
861
884
|
return null;
|
|
862
|
-
|
|
885
|
+
if (filter === 1 || filter === 2 || filter === 3)
|
|
886
|
+
this.lastFilter = filter;
|
|
863
887
|
const { getModeString, getFilterString } = await Promise.resolve().then(() => __importStar(require('./IcomConstants')));
|
|
864
888
|
const modeName = getModeString(mode);
|
|
865
889
|
const filterName = getFilterString(filter);
|
|
866
|
-
return { mode, filter, modeName, filterName };
|
|
890
|
+
return { mode, filter, modeName, filterName, dataMode };
|
|
867
891
|
}
|
|
868
892
|
/**
|
|
869
893
|
* Read current transmit frequency (when TX)
|
|
870
894
|
*/
|
|
871
895
|
async readTransmitFrequency(options) {
|
|
896
|
+
if (!this.activeProfile.supportsX1C03TxFreq)
|
|
897
|
+
return null;
|
|
872
898
|
const timeoutMs = options?.timeout ?? 3000;
|
|
873
899
|
const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
|
|
874
900
|
const rigAddr = this.civ.civAddress & 0xff;
|
|
875
901
|
const req = IcomRigCommands_1.IcomRigCommands.readTransmitFrequency(ctrAddr, rigAddr);
|
|
876
|
-
const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame,
|
|
902
|
+
const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_CTL_PTT, [IcomCivSpec_1.CIV.S_RD_TX_FREQ], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
|
|
877
903
|
if (!resp)
|
|
878
904
|
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;
|
|
905
|
+
return IcomControl.parseFrequencyReply(resp, 1);
|
|
902
906
|
}
|
|
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) {
|
|
907
|
+
async readPtt(options) {
|
|
908
908
|
const timeoutMs = options?.timeout ?? 3000;
|
|
909
909
|
const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
|
|
910
910
|
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)
|
|
911
|
+
const req = IcomRigCommands_1.IcomRigCommands.readPTT(ctrAddr, rigAddr);
|
|
912
|
+
const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_CTL_PTT, [IcomCivSpec_1.CIV.S_PTT], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
|
|
913
|
+
if (!resp || resp.length < 7)
|
|
914
|
+
return null;
|
|
915
|
+
return resp[6] !== 0x00;
|
|
916
|
+
}
|
|
917
|
+
/** Read transceiver state (TX/RX) using standard Hamlib-aligned PTT status. */
|
|
918
|
+
async readTransceiverState(options) {
|
|
919
|
+
const ptt = await this.readPtt(options);
|
|
920
|
+
if (ptt === null)
|
|
914
921
|
return null;
|
|
915
|
-
|
|
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';
|
|
922
|
+
return ptt ? 'TX' : 'RX';
|
|
925
923
|
}
|
|
926
924
|
/**
|
|
927
925
|
* Read band edge data (0x02). Format may vary by rig; returns raw data bytes after command.
|
|
@@ -958,8 +956,8 @@ class IcomControl {
|
|
|
958
956
|
return null;
|
|
959
957
|
return {
|
|
960
958
|
raw,
|
|
961
|
-
swr: raw
|
|
962
|
-
alert: raw >=
|
|
959
|
+
swr: (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.swr),
|
|
960
|
+
alert: (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.swr) >= 3.0
|
|
963
961
|
};
|
|
964
962
|
}
|
|
965
963
|
/**
|
|
@@ -983,8 +981,8 @@ class IcomControl {
|
|
|
983
981
|
return null;
|
|
984
982
|
return {
|
|
985
983
|
raw,
|
|
986
|
-
percent: (raw
|
|
987
|
-
alert: raw >
|
|
984
|
+
percent: (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.alc),
|
|
985
|
+
alert: (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.alc) > 100
|
|
988
986
|
};
|
|
989
987
|
}
|
|
990
988
|
/**
|
|
@@ -998,12 +996,15 @@ class IcomControl {
|
|
|
998
996
|
* }
|
|
999
997
|
*/
|
|
1000
998
|
async getConnectorWLanLevel(options) {
|
|
999
|
+
const ext = this.activeProfile.vendorExtensions.connectorWlanLevel;
|
|
1000
|
+
if (!ext)
|
|
1001
|
+
return null;
|
|
1001
1002
|
const timeoutMs = options?.timeout ?? 3000;
|
|
1002
1003
|
const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
|
|
1003
1004
|
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.
|
|
1005
|
+
const req = IcomRigCommands_1.IcomRigCommands.getConnectorWLanLevel(ctrAddr, rigAddr, ext.subext);
|
|
1006
|
+
const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, ext.command, [ext.subcmd, ...ext.subext], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
|
|
1007
|
+
const raw = IcomControl.extractTrailingBcd(resp, ext.dataBytes);
|
|
1007
1008
|
if (raw === null)
|
|
1008
1009
|
return null;
|
|
1009
1010
|
return {
|
|
@@ -1040,18 +1041,44 @@ class IcomControl {
|
|
|
1040
1041
|
if (data.length === 0)
|
|
1041
1042
|
return null;
|
|
1042
1043
|
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');
|
|
1044
|
+
return (0, smeter_1.rawToSMeter)(raw, this.activeProfile.calibrations.sMeterModel);
|
|
1046
1045
|
}
|
|
1047
1046
|
/**
|
|
1048
1047
|
* Set WLAN connector audio level
|
|
1049
1048
|
* @param level - Audio level (0-255)
|
|
1050
1049
|
*/
|
|
1051
1050
|
async setConnectorWLanLevel(level) {
|
|
1051
|
+
const ext = this.activeProfile.vendorExtensions.connectorWlanLevel;
|
|
1052
|
+
if (!ext) {
|
|
1053
|
+
throw new errors_1.UnsupportedCommandError({ modelId: this.getProfileModelId(), commandName: 'setConnectorWLanLevel', civCommand: '0x1a/0x05', reason: 'No vendor WLAN level extension for active profile' });
|
|
1054
|
+
}
|
|
1055
|
+
const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
|
|
1056
|
+
const rigAddr = this.civ.civAddress & 0xff;
|
|
1057
|
+
this.sendCiv(IcomRigCommands_1.IcomRigCommands.setConnectorWLanLevel(ctrAddr, rigAddr, level, ext.subext));
|
|
1058
|
+
}
|
|
1059
|
+
async getUsbAfLevel(options) {
|
|
1060
|
+
const ext = this.activeProfile.extParams.usbAfLevel;
|
|
1061
|
+
if (!ext)
|
|
1062
|
+
return null;
|
|
1063
|
+
const timeoutMs = options?.timeout ?? 3000;
|
|
1064
|
+
const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
|
|
1065
|
+
const rigAddr = this.civ.civAddress & 0xff;
|
|
1066
|
+
const req = IcomRigCommands_1.IcomRigCommands.getUsbAfLevel(ctrAddr, rigAddr, ext.subext);
|
|
1067
|
+
const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, ext.command, [ext.subcmd, ...ext.subext], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
|
|
1068
|
+
const raw = IcomControl.extractTrailingBcd(resp, ext.dataBytes);
|
|
1069
|
+
if (raw === null)
|
|
1070
|
+
return null;
|
|
1071
|
+
return { raw, percent: (raw / 255) * 100 };
|
|
1072
|
+
}
|
|
1073
|
+
async setUsbAfLevel(level) {
|
|
1074
|
+
const ext = this.activeProfile.extParams.usbAfLevel;
|
|
1075
|
+
if (!ext) {
|
|
1076
|
+
throw new errors_1.UnsupportedCommandError({ modelId: this.getProfileModelId(), commandName: 'setUsbAfLevel', civCommand: '0x1a/0x05', reason: 'No USB AF level extension for active profile' });
|
|
1077
|
+
}
|
|
1052
1078
|
const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
|
|
1053
1079
|
const rigAddr = this.civ.civAddress & 0xff;
|
|
1054
|
-
|
|
1080
|
+
const raw = Math.max(0, Math.min(255, Math.round(level)));
|
|
1081
|
+
this.sendCiv(IcomRigCommands_1.IcomRigCommands.setUsbAfLevel(ctrAddr, rigAddr, raw, ext.subext));
|
|
1055
1082
|
}
|
|
1056
1083
|
/**
|
|
1057
1084
|
* Set connector data routing mode
|
|
@@ -1061,10 +1088,14 @@ class IcomControl {
|
|
|
1061
1088
|
* await rig.setConnectorDataMode('WLAN');
|
|
1062
1089
|
*/
|
|
1063
1090
|
async setConnectorDataMode(mode) {
|
|
1091
|
+
const ext = this.activeProfile.vendorExtensions.connectorDataMode;
|
|
1092
|
+
if (!ext) {
|
|
1093
|
+
throw new errors_1.UnsupportedCommandError({ modelId: this.getProfileModelId(), commandName: 'setConnectorDataMode', civCommand: '0x1a/0x05', reason: 'No vendor connector data-mode extension for active profile' });
|
|
1094
|
+
}
|
|
1064
1095
|
const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
|
|
1065
1096
|
const rigAddr = this.civ.civAddress & 0xff;
|
|
1066
1097
|
const modeCode = typeof mode === 'string' ? (0, IcomConstants_1.getConnectorModeCode)(mode) : mode;
|
|
1067
|
-
this.sendCiv(IcomRigCommands_1.IcomRigCommands.setConnectorDataMode(ctrAddr, rigAddr, modeCode));
|
|
1098
|
+
this.sendCiv(IcomRigCommands_1.IcomRigCommands.setConnectorDataMode(ctrAddr, rigAddr, modeCode, ext.subext));
|
|
1068
1099
|
}
|
|
1069
1100
|
/**
|
|
1070
1101
|
* ==============================
|
|
@@ -1072,7 +1103,7 @@ class IcomControl {
|
|
|
1072
1103
|
* ==============================
|
|
1073
1104
|
*/
|
|
1074
1105
|
/**
|
|
1075
|
-
* Read antenna tuner status (CI-V
|
|
1106
|
+
* Read antenna tuner status (CI-V 0x1C/0x01)
|
|
1076
1107
|
* 00=OFF, 01=ON, 02=TUNING
|
|
1077
1108
|
*/
|
|
1078
1109
|
async readTunerStatus(options) {
|
|
@@ -1080,10 +1111,10 @@ class IcomControl {
|
|
|
1080
1111
|
const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
|
|
1081
1112
|
const rigAddr = this.civ.civAddress & 0xff;
|
|
1082
1113
|
const req = IcomRigCommands_1.IcomRigCommands.getTunerStatus(ctrAddr, rigAddr);
|
|
1083
|
-
const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame,
|
|
1114
|
+
const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_CTL_PTT, [IcomCivSpec_1.CIV.S_ANT_TUN], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
|
|
1084
1115
|
if (!resp)
|
|
1085
1116
|
return null;
|
|
1086
|
-
// Expect FE FE [ctr] [rig]
|
|
1117
|
+
// Expect FE FE [ctr] [rig] 0x1C 0x01 [status] FD
|
|
1087
1118
|
const raw = resp.length > 6 ? (resp[6] & 0xff) : undefined;
|
|
1088
1119
|
if (raw === undefined)
|
|
1089
1120
|
return null;
|
|
@@ -1091,7 +1122,7 @@ class IcomControl {
|
|
|
1091
1122
|
return { raw, state };
|
|
1092
1123
|
}
|
|
1093
1124
|
/**
|
|
1094
|
-
* Enable or disable internal antenna tuner (CI-V
|
|
1125
|
+
* Enable or disable internal antenna tuner (CI-V 0x1C/0x01)
|
|
1095
1126
|
* @param enabled true to enable, false to disable
|
|
1096
1127
|
*/
|
|
1097
1128
|
async setTunerEnabled(enabled) {
|
|
@@ -1100,7 +1131,7 @@ class IcomControl {
|
|
|
1100
1131
|
this.sendCiv(IcomRigCommands_1.IcomRigCommands.setTunerEnabled(ctrAddr, rigAddr, enabled));
|
|
1101
1132
|
}
|
|
1102
1133
|
/**
|
|
1103
|
-
* Start a manual tuning cycle (same as [TUNE] key) (CI-V
|
|
1134
|
+
* Start a manual tuning cycle (same as [TUNE] key) (CI-V 0x1C/0x01/0x02)
|
|
1104
1135
|
*/
|
|
1105
1136
|
async startManualTune() {
|
|
1106
1137
|
const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
|
|
@@ -1145,14 +1176,25 @@ class IcomControl {
|
|
|
1145
1176
|
}
|
|
1146
1177
|
/** Get microphone gain. Returns 0.0–1.0, or null on timeout. */
|
|
1147
1178
|
async getMicGain(options) {
|
|
1148
|
-
const v = await this.read0x14Level(
|
|
1179
|
+
const v = await this.read0x14Level(IcomCivSpec_1.CIV.S_LVL_MICGAIN, options);
|
|
1149
1180
|
if (v === null)
|
|
1150
1181
|
return null;
|
|
1151
1182
|
return { raw: Math.round(v * 255), normalized: v };
|
|
1152
1183
|
}
|
|
1153
1184
|
/** Set microphone gain. Value 0.0–1.0. */
|
|
1154
1185
|
setMicGain(value) {
|
|
1155
|
-
this.write0x14Level(
|
|
1186
|
+
this.write0x14Level(IcomCivSpec_1.CIV.S_LVL_MICGAIN, value);
|
|
1187
|
+
}
|
|
1188
|
+
/** Get break-in delay. Returns 0.0–1.0, or null on timeout. */
|
|
1189
|
+
async getBreakInDelay(options) {
|
|
1190
|
+
const v = await this.read0x14Level(IcomCivSpec_1.CIV.S_LVL_BKINDL, options);
|
|
1191
|
+
if (v === null)
|
|
1192
|
+
return null;
|
|
1193
|
+
return { raw: Math.round(v * 255), normalized: v };
|
|
1194
|
+
}
|
|
1195
|
+
/** Set break-in delay. Value 0.0–1.0. */
|
|
1196
|
+
setBreakInDelay(value) {
|
|
1197
|
+
this.write0x14Level(IcomCivSpec_1.CIV.S_LVL_BKINDL, value);
|
|
1156
1198
|
}
|
|
1157
1199
|
/** Get noise blanker level. 0.0 = off, >0.0 = on with strength. */
|
|
1158
1200
|
async getNBLevel(options) {
|
|
@@ -1167,14 +1209,14 @@ class IcomControl {
|
|
|
1167
1209
|
}
|
|
1168
1210
|
/** Get noise reduction level. 0.0 = off, >0.0 = on with strength. */
|
|
1169
1211
|
async getNRLevel(options) {
|
|
1170
|
-
const v = await this.read0x14Level(
|
|
1212
|
+
const v = await this.read0x14Level(IcomCivSpec_1.CIV.S_LVL_NR, options);
|
|
1171
1213
|
if (v === null)
|
|
1172
1214
|
return null;
|
|
1173
1215
|
return { raw: Math.round(v * 255), normalized: v };
|
|
1174
1216
|
}
|
|
1175
1217
|
/** Set noise reduction level. Value 0.0 (off) – 1.0. */
|
|
1176
1218
|
setNRLevel(value) {
|
|
1177
|
-
this.write0x14Level(
|
|
1219
|
+
this.write0x14Level(IcomCivSpec_1.CIV.S_LVL_NR, value);
|
|
1178
1220
|
}
|
|
1179
1221
|
/**
|
|
1180
1222
|
* Read squelch status (noise/signal gate state)
|
|
@@ -1267,9 +1309,12 @@ class IcomControl {
|
|
|
1267
1309
|
const raw = IcomControl.extractMeterData(resp);
|
|
1268
1310
|
if (raw === null)
|
|
1269
1311
|
return null;
|
|
1312
|
+
const watts = (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.rfPowerWatts);
|
|
1313
|
+
const maxWatts = this.activeProfile.calibrations.rfPowerWatts[this.activeProfile.calibrations.rfPowerWatts.length - 1]?.value ?? 100;
|
|
1270
1314
|
return {
|
|
1271
1315
|
raw,
|
|
1272
|
-
percent:
|
|
1316
|
+
percent: maxWatts > 0 ? Math.min(100, (watts / maxWatts) * 100) : 0,
|
|
1317
|
+
watts
|
|
1273
1318
|
};
|
|
1274
1319
|
}
|
|
1275
1320
|
/**
|
|
@@ -1291,9 +1336,11 @@ class IcomControl {
|
|
|
1291
1336
|
const raw = IcomControl.extractMeterData(resp);
|
|
1292
1337
|
if (raw === null)
|
|
1293
1338
|
return null;
|
|
1339
|
+
const db = (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.compDb);
|
|
1294
1340
|
return {
|
|
1295
1341
|
raw,
|
|
1296
|
-
percent: (
|
|
1342
|
+
percent: (db / 30) * 100,
|
|
1343
|
+
db
|
|
1297
1344
|
};
|
|
1298
1345
|
}
|
|
1299
1346
|
/**
|
|
@@ -1317,7 +1364,7 @@ class IcomControl {
|
|
|
1317
1364
|
return null;
|
|
1318
1365
|
return {
|
|
1319
1366
|
raw,
|
|
1320
|
-
volts: (0,
|
|
1367
|
+
volts: (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.voltage)
|
|
1321
1368
|
};
|
|
1322
1369
|
}
|
|
1323
1370
|
/**
|
|
@@ -1341,7 +1388,7 @@ class IcomControl {
|
|
|
1341
1388
|
return null;
|
|
1342
1389
|
return {
|
|
1343
1390
|
raw,
|
|
1344
|
-
amps: (0,
|
|
1391
|
+
amps: (0, IcomProfiles_1.interpolateCalibration)(raw, this.activeProfile.calibrations.current)
|
|
1345
1392
|
};
|
|
1346
1393
|
}
|
|
1347
1394
|
static isReplyOf(frame, cmd, ctrAddr, rigAddr) {
|
|
@@ -1357,9 +1404,17 @@ class IcomControl {
|
|
|
1357
1404
|
static extractMeterData(frame) {
|
|
1358
1405
|
if (!frame || frame.length < 9)
|
|
1359
1406
|
return null;
|
|
1360
|
-
//
|
|
1361
|
-
|
|
1362
|
-
|
|
1407
|
+
// FE FE [ctr] [rig] 0x15 [sub] [bcd_hi] [bcd_lo] FD
|
|
1408
|
+
return (0, IcomCivFrame_1.decodeBcdBE)(frame.subarray(6, 8));
|
|
1409
|
+
}
|
|
1410
|
+
static extractTrailingBcd(frame, byteLength) {
|
|
1411
|
+
if (!frame || frame.length < 6 + byteLength)
|
|
1412
|
+
return null;
|
|
1413
|
+
const end = frame.length - 1;
|
|
1414
|
+
const start = end - byteLength;
|
|
1415
|
+
if (start < 5)
|
|
1416
|
+
return null;
|
|
1417
|
+
return (0, IcomCivFrame_1.decodeBcdBE)(frame.subarray(start, end));
|
|
1363
1418
|
}
|
|
1364
1419
|
static matchCommand(frame, cmd, tail) {
|
|
1365
1420
|
// FE FE ?? ?? cmd ... tail... FD
|
|
@@ -1429,34 +1484,23 @@ class IcomControl {
|
|
|
1429
1484
|
}, timeoutMs);
|
|
1430
1485
|
});
|
|
1431
1486
|
}
|
|
1432
|
-
|
|
1433
|
-
static parseIcomFreqFromReply(frame) {
|
|
1434
|
-
// Expect: FE FE [ctr] [rig] 0x03 [bcd0..bcd4] FD
|
|
1487
|
+
static parseFrequencyReply(frame, payloadOffsetAfterCommand, byteLength) {
|
|
1435
1488
|
if (!(frame && frame.length >= 7))
|
|
1436
1489
|
return null;
|
|
1437
|
-
if (frame[0] !== 0xfe || frame[1] !== 0xfe)
|
|
1490
|
+
if (frame[0] !== 0xfe || frame[1] !== 0xfe || frame[frame.length - 1] !== 0xfd)
|
|
1438
1491
|
return null;
|
|
1439
|
-
|
|
1492
|
+
const start = 5 + payloadOffsetAfterCommand;
|
|
1493
|
+
const maxBytes = frame.length - 1 - start;
|
|
1494
|
+
const len = byteLength ?? (maxBytes >= 6 ? 6 : 5);
|
|
1495
|
+
if (maxBytes < len || len <= 0)
|
|
1440
1496
|
return null;
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
if (
|
|
1497
|
+
return (0, IcomCivFrame_1.decodeFrequencyBcdLE)(frame.subarray(start, start + len));
|
|
1498
|
+
}
|
|
1499
|
+
// Parse standard CI-V 0x03 read-frequency replies.
|
|
1500
|
+
static parseIcomFreqFromReply(frame) {
|
|
1501
|
+
if (!IcomControl.matchCommandFrame(frame, IcomCivSpec_1.CIV.C_RD_FREQ, []))
|
|
1446
1502
|
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;
|
|
1503
|
+
return IcomControl.parseFrequencyReply(frame, 0);
|
|
1460
1504
|
}
|
|
1461
1505
|
sendAudioFloat32(samples, addLeadingBuffer = false) {
|
|
1462
1506
|
this.audio.enqueueFloat32(samples, addLeadingBuffer);
|
|
@@ -1591,11 +1635,15 @@ class IcomControl {
|
|
|
1591
1635
|
audioName: IcomPackets_1.RadioCapPacket.getAudioName(cap),
|
|
1592
1636
|
supportTX: IcomPackets_1.RadioCapPacket.getSupportTX(cap)
|
|
1593
1637
|
};
|
|
1594
|
-
if (info.civAddress != null)
|
|
1638
|
+
if (info.civAddress != null) {
|
|
1595
1639
|
this.civ.civAddress = info.civAddress;
|
|
1640
|
+
this.resolveActiveProfile({ civAddress: info.civAddress });
|
|
1641
|
+
info.modelId = this.activeProfile.modelId;
|
|
1642
|
+
info.profileName = this.activeProfile.profileName;
|
|
1643
|
+
}
|
|
1596
1644
|
if (info.supportTX != null)
|
|
1597
1645
|
this.civ.supportTX = info.supportTX;
|
|
1598
|
-
(0, debug_1.dbgV)('CAP <= civAddr=', info.civAddress, 'audioName=', info.audioName, 'supportTX=', info.supportTX);
|
|
1646
|
+
(0, debug_1.dbgV)('CAP <= civAddr=', info.civAddress, 'audioName=', info.audioName, 'supportTX=', info.supportTX, 'profile=', info.modelId);
|
|
1599
1647
|
this.ev.emit('capabilities', info);
|
|
1600
1648
|
}
|
|
1601
1649
|
break;
|
|
@@ -1607,7 +1655,8 @@ class IcomControl {
|
|
|
1607
1655
|
const busy = IcomPackets_1.ConnInfoPacket.getBusy(buf);
|
|
1608
1656
|
this.macAddress = IcomPackets_1.ConnInfoPacket.getMacAddress(buf);
|
|
1609
1657
|
this.rigName = IcomPackets_1.ConnInfoPacket.getRigName(buf);
|
|
1610
|
-
|
|
1658
|
+
this.resolveActiveProfile({ rigName: this.rigName });
|
|
1659
|
+
(0, debug_1.dbg)('CONNINFO <= busy=', busy, 'rigName=', this.rigName, 'profile=', this.activeProfile.modelId);
|
|
1611
1660
|
if (busy) {
|
|
1612
1661
|
(0, debug_1.dbg)('CONNINFO busy=true detected - likely reconnecting while rig still has old session');
|
|
1613
1662
|
(0, debug_1.dbg)('Sending ConnInfo reply anyway to allow STATUS packet delivery');
|