icom-wlan-node 0.1.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/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/core/IcomPackets.d.ts +104 -0
- package/dist/core/IcomPackets.js +333 -0
- package/dist/core/Session.d.ts +43 -0
- package/dist/core/Session.js +105 -0
- package/dist/demo.d.ts +12 -0
- package/dist/demo.js +329 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +42 -0
- package/dist/rig/IcomAudio.d.ts +27 -0
- package/dist/rig/IcomAudio.js +143 -0
- package/dist/rig/IcomCiv.d.ts +14 -0
- package/dist/rig/IcomCiv.js +44 -0
- package/dist/rig/IcomConstants.d.ts +84 -0
- package/dist/rig/IcomConstants.js +112 -0
- package/dist/rig/IcomControl.d.ts +155 -0
- package/dist/rig/IcomControl.js +912 -0
- package/dist/rig/IcomRigCommands.d.ts +17 -0
- package/dist/rig/IcomRigCommands.js +73 -0
- package/dist/transport/UdpClient.d.ts +14 -0
- package/dist/transport/UdpClient.js +47 -0
- package/dist/types.d.ts +160 -0
- package/dist/types.js +2 -0
- package/dist/utils/bcd.d.ts +36 -0
- package/dist/utils/bcd.js +59 -0
- package/dist/utils/codec.d.ts +22 -0
- package/dist/utils/codec.js +56 -0
- package/dist/utils/debug.d.ts +4 -0
- package/dist/utils/debug.js +15 -0
- package/package.json +31 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AUDIO_RATE = exports.IcomAudio = void 0;
|
|
4
|
+
const IcomPackets_1 = require("../core/IcomPackets");
|
|
5
|
+
class IcomAudio {
|
|
6
|
+
constructor(sess) {
|
|
7
|
+
this.sess = sess;
|
|
8
|
+
this.sendSeq = 0;
|
|
9
|
+
this.sendSeqForTiming = 0; // Separate counter for drift compensation
|
|
10
|
+
this.isPttOn = false;
|
|
11
|
+
this.queue = []; // Public for PTT control to clear queue
|
|
12
|
+
this.volume = 1.0; // 0.0 ~ 2.0
|
|
13
|
+
this.running = false;
|
|
14
|
+
this.startTime = 0; // Absolute start time for drift compensation
|
|
15
|
+
this.FRAME_INTERVAL_MS = 20;
|
|
16
|
+
this.frameCount = 0;
|
|
17
|
+
this.debugInterval = 0;
|
|
18
|
+
this.debugIntervalStart = 0;
|
|
19
|
+
}
|
|
20
|
+
start() {
|
|
21
|
+
// Start continuous audio transmission with drift compensation
|
|
22
|
+
if (this.running)
|
|
23
|
+
return;
|
|
24
|
+
this.running = true;
|
|
25
|
+
this.startTime = performance.now();
|
|
26
|
+
this.sendSeqForTiming = 0;
|
|
27
|
+
this.frameCount = 0;
|
|
28
|
+
this.debugInterval = 0;
|
|
29
|
+
this.debugIntervalStart = 0;
|
|
30
|
+
this.scheduleSend();
|
|
31
|
+
}
|
|
32
|
+
scheduleSend() {
|
|
33
|
+
if (!this.running)
|
|
34
|
+
return;
|
|
35
|
+
const now = performance.now();
|
|
36
|
+
// Calculate ideal send time for next frame (with drift compensation)
|
|
37
|
+
const nextFrameIndex = this.sendSeqForTiming + 1;
|
|
38
|
+
const idealTime = this.startTime + (nextFrameIndex * this.FRAME_INTERVAL_MS);
|
|
39
|
+
const timeUntilSend = idealTime - now;
|
|
40
|
+
if (timeUntilSend <= 0) {
|
|
41
|
+
// We're at or past send time! Send immediately
|
|
42
|
+
this.sendFrame();
|
|
43
|
+
this.sendSeqForTiming++;
|
|
44
|
+
// Schedule next check immediately (tight loop for precision)
|
|
45
|
+
this.txTimer = setImmediate(() => this.scheduleSend());
|
|
46
|
+
}
|
|
47
|
+
else if (timeUntilSend <= 0.5) {
|
|
48
|
+
// Very close (within 0.5ms), tight loop with setImmediate
|
|
49
|
+
this.txTimer = setImmediate(() => this.scheduleSend());
|
|
50
|
+
}
|
|
51
|
+
else if (timeUntilSend <= 3) {
|
|
52
|
+
// Close (1-3ms), use minimal setTimeout
|
|
53
|
+
setTimeout(() => this.scheduleSend(), 0);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// Further away, wait conservatively then tight loop
|
|
57
|
+
const waitTime = Math.max(1, Math.floor(timeUntilSend - 2));
|
|
58
|
+
setTimeout(() => this.scheduleSend(), waitTime);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
sendFrame() {
|
|
62
|
+
let frame;
|
|
63
|
+
const hasData = this.queue.length > 0;
|
|
64
|
+
if (hasData) {
|
|
65
|
+
// Send audio from queue when available
|
|
66
|
+
frame = this.queue.shift();
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Send silence when queue is empty
|
|
70
|
+
frame = new Int16Array(IcomPackets_1.TX_BUFFER_SIZE);
|
|
71
|
+
}
|
|
72
|
+
const buf = Buffer.alloc(IcomPackets_1.TX_BUFFER_SIZE * 2);
|
|
73
|
+
for (let i = 0; i < IcomPackets_1.TX_BUFFER_SIZE; i++)
|
|
74
|
+
buf.writeInt16LE(frame[i] ?? 0, i * 2);
|
|
75
|
+
const pkt = IcomPackets_1.AudioPacket.getTxAudioPacket(buf, 0, this.sess.localId, this.sess.remoteId, this.sendSeq);
|
|
76
|
+
this.sendSeq = (this.sendSeq + 1) & 0xffff;
|
|
77
|
+
this.sess.sendTracked(pkt);
|
|
78
|
+
// Debug: log timing info every 50 frames (~1 second)
|
|
79
|
+
this.frameCount++;
|
|
80
|
+
if (this.frameCount % 50 === 0 && hasData) {
|
|
81
|
+
const now = performance.now();
|
|
82
|
+
if (this.debugIntervalStart > 0) {
|
|
83
|
+
const elapsed = now - this.debugIntervalStart;
|
|
84
|
+
const avgInterval = elapsed / 50;
|
|
85
|
+
const drift = elapsed - (50 * this.FRAME_INTERVAL_MS);
|
|
86
|
+
console.log(`[AudioTiming] Avg: ${avgInterval.toFixed(2)}ms (target: ${this.FRAME_INTERVAL_MS}ms), drift: ${drift.toFixed(2)}ms, queue: ${this.queue.length}`);
|
|
87
|
+
}
|
|
88
|
+
this.debugIntervalStart = now;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
stop() {
|
|
92
|
+
// Stop continuous audio transmission (only on disconnect)
|
|
93
|
+
this.running = false;
|
|
94
|
+
if (this.txTimer) {
|
|
95
|
+
clearImmediate(this.txTimer);
|
|
96
|
+
this.txTimer = undefined;
|
|
97
|
+
}
|
|
98
|
+
this.queue.length = 0;
|
|
99
|
+
this.isPttOn = false;
|
|
100
|
+
this.frameCount = 0;
|
|
101
|
+
this.debugInterval = 0;
|
|
102
|
+
}
|
|
103
|
+
// Add leading silence frames (like Java's front buffer)
|
|
104
|
+
addLeadingSilence(frameCount = 3) {
|
|
105
|
+
const silence = new Int16Array(IcomPackets_1.TX_BUFFER_SIZE);
|
|
106
|
+
for (let i = 0; i < frameCount; i++) {
|
|
107
|
+
this.queue.push(silence);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Add trailing silence frames (like Java's tail buffer)
|
|
111
|
+
addTrailingSilence(frameCount = 5) {
|
|
112
|
+
const silence = new Int16Array(IcomPackets_1.TX_BUFFER_SIZE);
|
|
113
|
+
for (let i = 0; i < frameCount; i++) {
|
|
114
|
+
this.queue.push(silence);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Push Float32 samples; they will be converted to 16-bit and sliced into 20ms frames
|
|
118
|
+
enqueueFloat32(samples, addLeadingBuffer = false) {
|
|
119
|
+
// Add leading silence buffer if requested (used at PTT start)
|
|
120
|
+
if (addLeadingBuffer && this.queue.length === 0) {
|
|
121
|
+
this.addLeadingSilence(3);
|
|
122
|
+
}
|
|
123
|
+
const out = new Int16Array(samples.length);
|
|
124
|
+
for (let i = 0; i < samples.length; i++) {
|
|
125
|
+
let x = Math.max(-1, Math.min(1, samples[i]));
|
|
126
|
+
out[i] = (x * this.volume * 32767) | 0;
|
|
127
|
+
}
|
|
128
|
+
this.enqueuePcm16(out);
|
|
129
|
+
}
|
|
130
|
+
enqueuePcm16(samples) {
|
|
131
|
+
// slice into TX_BUFFER_SIZE frames
|
|
132
|
+
for (let i = 0; i < samples.length; i += IcomPackets_1.TX_BUFFER_SIZE) {
|
|
133
|
+
const slice = samples.subarray(i, Math.min(samples.length, i + IcomPackets_1.TX_BUFFER_SIZE));
|
|
134
|
+
// Always create a new buffer to avoid issues with subarray views
|
|
135
|
+
const frame = new Int16Array(IcomPackets_1.TX_BUFFER_SIZE);
|
|
136
|
+
frame.set(slice);
|
|
137
|
+
this.queue.push(frame);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
setVolume(multiplier) { this.volume = Math.max(0, multiplier); }
|
|
141
|
+
}
|
|
142
|
+
exports.IcomAudio = IcomAudio;
|
|
143
|
+
exports.AUDIO_RATE = IcomPackets_1.AUDIO_SAMPLE_RATE;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Session } from '../core/Session';
|
|
2
|
+
export declare class IcomCiv {
|
|
3
|
+
private sess;
|
|
4
|
+
civAddress: number;
|
|
5
|
+
supportTX: boolean;
|
|
6
|
+
private civSeq;
|
|
7
|
+
private idleTimer?;
|
|
8
|
+
private openTimer?;
|
|
9
|
+
constructor(sess: Session);
|
|
10
|
+
start(): void;
|
|
11
|
+
stop(): void;
|
|
12
|
+
sendOpenClose(open: boolean): void;
|
|
13
|
+
sendCivData(data: Buffer): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IcomCiv = void 0;
|
|
4
|
+
const IcomPackets_1 = require("../core/IcomPackets");
|
|
5
|
+
const debug_1 = require("../utils/debug");
|
|
6
|
+
class IcomCiv {
|
|
7
|
+
constructor(sess) {
|
|
8
|
+
this.sess = sess;
|
|
9
|
+
this.civAddress = 0xA4;
|
|
10
|
+
this.supportTX = true;
|
|
11
|
+
this.civSeq = 0;
|
|
12
|
+
}
|
|
13
|
+
start() {
|
|
14
|
+
this.stop();
|
|
15
|
+
// keep-alive open/close
|
|
16
|
+
this.openTimer = setInterval(() => {
|
|
17
|
+
if (Date.now() - this.sess.lastReceivedTime > 2000) {
|
|
18
|
+
this.sendOpenClose(true);
|
|
19
|
+
}
|
|
20
|
+
}, 500);
|
|
21
|
+
}
|
|
22
|
+
stop() {
|
|
23
|
+
if (this.openTimer)
|
|
24
|
+
clearInterval(this.openTimer);
|
|
25
|
+
if (this.idleTimer)
|
|
26
|
+
clearInterval(this.idleTimer);
|
|
27
|
+
this.openTimer = undefined;
|
|
28
|
+
this.idleTimer = undefined;
|
|
29
|
+
}
|
|
30
|
+
sendOpenClose(open) {
|
|
31
|
+
const magic = open ? 0x04 : 0x00;
|
|
32
|
+
const pkt = IcomPackets_1.OpenClosePacket.toBytes(0, this.sess.localId, this.sess.remoteId, this.civSeq, magic);
|
|
33
|
+
this.civSeq = (this.civSeq + 1) & 0xffff;
|
|
34
|
+
(0, debug_1.dbg)(`CIV -> OpenClose ${open ? 'OPEN' : 'CLOSE'} seq=${this.civSeq - 1}`);
|
|
35
|
+
this.sess.sendTracked(pkt);
|
|
36
|
+
}
|
|
37
|
+
sendCivData(data) {
|
|
38
|
+
const pkt = IcomPackets_1.CivPacket.setCivData(0, this.sess.localId, this.sess.remoteId, this.civSeq, data);
|
|
39
|
+
this.civSeq = (this.civSeq + 1) & 0xffff;
|
|
40
|
+
(0, debug_1.dbg)(`CIV -> data len=${data.length} seq=${this.civSeq - 1}`);
|
|
41
|
+
this.sess.sendTracked(pkt);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.IcomCiv = IcomCiv;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ICOM radio constants and mappings
|
|
3
|
+
* Based on FT8CN's IcomRigConstant.java
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Operating modes supported by ICOM radios
|
|
7
|
+
* LSB:0, USB:1, AM:2, CW:3, RTTY:4, FM:5, WFM:6, CW_R:7, RTTY_R:8, DV:17
|
|
8
|
+
*/
|
|
9
|
+
export declare const MODE_MAP: {
|
|
10
|
+
readonly LSB: 0;
|
|
11
|
+
readonly USB: 1;
|
|
12
|
+
readonly AM: 2;
|
|
13
|
+
readonly CW: 3;
|
|
14
|
+
readonly RTTY: 4;
|
|
15
|
+
readonly FM: 5;
|
|
16
|
+
readonly WFM: 6;
|
|
17
|
+
readonly CW_R: 7;
|
|
18
|
+
readonly RTTY_R: 8;
|
|
19
|
+
readonly DV: 23;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Connector data routing modes
|
|
23
|
+
* Based on ICOM's connector data mode settings
|
|
24
|
+
*/
|
|
25
|
+
export declare const CONNECTOR_MODE_MAP: {
|
|
26
|
+
readonly MIC: 0;
|
|
27
|
+
readonly ACC: 1;
|
|
28
|
+
readonly USB: 2;
|
|
29
|
+
readonly WLAN: 3;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Default controller address (typically 0xE0 for PC/controller)
|
|
33
|
+
*/
|
|
34
|
+
export declare const DEFAULT_CONTROLLER_ADDR = 224;
|
|
35
|
+
/**
|
|
36
|
+
* Helper function to get mode code from string
|
|
37
|
+
*/
|
|
38
|
+
export declare function getModeCode(mode: keyof typeof MODE_MAP): number;
|
|
39
|
+
/**
|
|
40
|
+
* Helper function to get connector mode code from string
|
|
41
|
+
*/
|
|
42
|
+
export declare function getConnectorModeCode(mode: keyof typeof CONNECTOR_MODE_MAP): number;
|
|
43
|
+
/**
|
|
44
|
+
* Helper function to get mode string from code
|
|
45
|
+
*/
|
|
46
|
+
export declare function getModeString(code: number): string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Helper function to get connector mode string from code
|
|
49
|
+
*/
|
|
50
|
+
export declare function getConnectorModeString(code: number): string | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* ICOM filter code to string mapping
|
|
53
|
+
* Common rigs use 0x01(FIL1), 0x02(FIL2), 0x03(FIL3)
|
|
54
|
+
*/
|
|
55
|
+
export declare function getFilterString(code: number | undefined): string | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Meter alert thresholds
|
|
58
|
+
* Based on FT8CN's IcomRigConstant.java
|
|
59
|
+
*/
|
|
60
|
+
export declare const METER_THRESHOLDS: {
|
|
61
|
+
/**
|
|
62
|
+
* SWR alert threshold (raw value)
|
|
63
|
+
* Alert when SWR ≥ 1.2 (raw value ≥ 120)
|
|
64
|
+
*/
|
|
65
|
+
readonly SWR_ALERT: 120;
|
|
66
|
+
/**
|
|
67
|
+
* ALC maximum alert threshold (raw value)
|
|
68
|
+
* Alert when ALC > 120
|
|
69
|
+
*/
|
|
70
|
+
readonly ALC_ALERT_MAX: 120;
|
|
71
|
+
/**
|
|
72
|
+
* Maximum ALC value for percentage calculation
|
|
73
|
+
*/
|
|
74
|
+
readonly ALC_MAX: 255;
|
|
75
|
+
/**
|
|
76
|
+
* Maximum WLAN level value for percentage calculation
|
|
77
|
+
*/
|
|
78
|
+
readonly WLAN_LEVEL_MAX: 255;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Period for meter polling during TX (milliseconds)
|
|
82
|
+
* Matches FT8CN's IComPacketTypes.METER_TIMER_PERIOD_MS
|
|
83
|
+
*/
|
|
84
|
+
export declare const METER_TIMER_PERIOD_MS = 500;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ICOM radio constants and mappings
|
|
4
|
+
* Based on FT8CN's IcomRigConstant.java
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.METER_TIMER_PERIOD_MS = exports.METER_THRESHOLDS = exports.DEFAULT_CONTROLLER_ADDR = exports.CONNECTOR_MODE_MAP = exports.MODE_MAP = void 0;
|
|
8
|
+
exports.getModeCode = getModeCode;
|
|
9
|
+
exports.getConnectorModeCode = getConnectorModeCode;
|
|
10
|
+
exports.getModeString = getModeString;
|
|
11
|
+
exports.getConnectorModeString = getConnectorModeString;
|
|
12
|
+
exports.getFilterString = getFilterString;
|
|
13
|
+
/**
|
|
14
|
+
* Operating modes supported by ICOM radios
|
|
15
|
+
* LSB:0, USB:1, AM:2, CW:3, RTTY:4, FM:5, WFM:6, CW_R:7, RTTY_R:8, DV:17
|
|
16
|
+
*/
|
|
17
|
+
exports.MODE_MAP = {
|
|
18
|
+
LSB: 0x00, // Lower Side Band (下边带)
|
|
19
|
+
USB: 0x01, // Upper Side Band (上边带)
|
|
20
|
+
AM: 0x02, // Amplitude Modulation (调幅)
|
|
21
|
+
CW: 0x03, // Continuous Wave (连续波/莫尔斯码)
|
|
22
|
+
RTTY: 0x04, // Radio Teletype (频移键控)
|
|
23
|
+
FM: 0x05, // Frequency Modulation (调频)
|
|
24
|
+
WFM: 0x06, // Wide FM (宽带调频)
|
|
25
|
+
CW_R: 0x07, // CW Reverse (反向连续波)
|
|
26
|
+
RTTY_R: 0x08, // RTTY Reverse (反向频移键控)
|
|
27
|
+
DV: 0x17 // Digital Voice (数字语音, decimal 23)
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Connector data routing modes
|
|
31
|
+
* Based on ICOM's connector data mode settings
|
|
32
|
+
*/
|
|
33
|
+
exports.CONNECTOR_MODE_MAP = {
|
|
34
|
+
MIC: 0x00, // Microphone input
|
|
35
|
+
ACC: 0x01, // ACC (Accessory) port
|
|
36
|
+
USB: 0x02, // USB audio
|
|
37
|
+
WLAN: 0x03 // WLAN (network) audio
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Default controller address (typically 0xE0 for PC/controller)
|
|
41
|
+
*/
|
|
42
|
+
exports.DEFAULT_CONTROLLER_ADDR = 0xe0;
|
|
43
|
+
/**
|
|
44
|
+
* Helper function to get mode code from string
|
|
45
|
+
*/
|
|
46
|
+
function getModeCode(mode) {
|
|
47
|
+
return exports.MODE_MAP[mode];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Helper function to get connector mode code from string
|
|
51
|
+
*/
|
|
52
|
+
function getConnectorModeCode(mode) {
|
|
53
|
+
return exports.CONNECTOR_MODE_MAP[mode];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Helper function to get mode string from code
|
|
57
|
+
*/
|
|
58
|
+
function getModeString(code) {
|
|
59
|
+
const entry = Object.entries(exports.MODE_MAP).find(([_, value]) => value === code);
|
|
60
|
+
return entry?.[0];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Helper function to get connector mode string from code
|
|
64
|
+
*/
|
|
65
|
+
function getConnectorModeString(code) {
|
|
66
|
+
const entry = Object.entries(exports.CONNECTOR_MODE_MAP).find(([_, value]) => value === code);
|
|
67
|
+
return entry?.[0];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* ICOM filter code to string mapping
|
|
71
|
+
* Common rigs use 0x01(FIL1), 0x02(FIL2), 0x03(FIL3)
|
|
72
|
+
*/
|
|
73
|
+
function getFilterString(code) {
|
|
74
|
+
if (code === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
if (code === 0x01)
|
|
77
|
+
return 'FIL1';
|
|
78
|
+
if (code === 0x02)
|
|
79
|
+
return 'FIL2';
|
|
80
|
+
if (code === 0x03)
|
|
81
|
+
return 'FIL3';
|
|
82
|
+
return 'FIL' + code;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Meter alert thresholds
|
|
86
|
+
* Based on FT8CN's IcomRigConstant.java
|
|
87
|
+
*/
|
|
88
|
+
exports.METER_THRESHOLDS = {
|
|
89
|
+
/**
|
|
90
|
+
* SWR alert threshold (raw value)
|
|
91
|
+
* Alert when SWR ≥ 1.2 (raw value ≥ 120)
|
|
92
|
+
*/
|
|
93
|
+
SWR_ALERT: 120,
|
|
94
|
+
/**
|
|
95
|
+
* ALC maximum alert threshold (raw value)
|
|
96
|
+
* Alert when ALC > 120
|
|
97
|
+
*/
|
|
98
|
+
ALC_ALERT_MAX: 120,
|
|
99
|
+
/**
|
|
100
|
+
* Maximum ALC value for percentage calculation
|
|
101
|
+
*/
|
|
102
|
+
ALC_MAX: 255,
|
|
103
|
+
/**
|
|
104
|
+
* Maximum WLAN level value for percentage calculation
|
|
105
|
+
*/
|
|
106
|
+
WLAN_LEVEL_MAX: 255
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Period for meter polling during TX (milliseconds)
|
|
110
|
+
* Matches FT8CN's IComPacketTypes.METER_TIMER_PERIOD_MS
|
|
111
|
+
*/
|
|
112
|
+
exports.METER_TIMER_PERIOD_MS = 500;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { IcomRigOptions, RigEventEmitter, IcomMode, ConnectorDataMode, SetModeOptions, QueryOptions, SwrReading, AlcReading, WlanLevelReading, LevelMeterReading } from '../types';
|
|
2
|
+
import { IcomCiv } from './IcomCiv';
|
|
3
|
+
import { IcomAudio } from './IcomAudio';
|
|
4
|
+
export declare class IcomControl {
|
|
5
|
+
private ev;
|
|
6
|
+
private sess;
|
|
7
|
+
private civSess;
|
|
8
|
+
private audioSess;
|
|
9
|
+
civ: IcomCiv;
|
|
10
|
+
audio: IcomAudio;
|
|
11
|
+
private options;
|
|
12
|
+
private rigName;
|
|
13
|
+
private macAddress;
|
|
14
|
+
private tokenTimer?;
|
|
15
|
+
private civAssembleBuf;
|
|
16
|
+
private meterTimer?;
|
|
17
|
+
private loginReady;
|
|
18
|
+
private civReady;
|
|
19
|
+
private audioReady;
|
|
20
|
+
private resolveLoginReady;
|
|
21
|
+
private resolveCivReady;
|
|
22
|
+
private resolveAudioReady;
|
|
23
|
+
constructor(options: IcomRigOptions);
|
|
24
|
+
get events(): RigEventEmitter;
|
|
25
|
+
connect(): Promise<void>;
|
|
26
|
+
disconnect(): Promise<void>;
|
|
27
|
+
sendCiv(data: Buffer): void;
|
|
28
|
+
/**
|
|
29
|
+
* Set PTT (Push-To-Talk) state
|
|
30
|
+
* @param on - true to key transmitter, false to unkey
|
|
31
|
+
*/
|
|
32
|
+
setPtt(on: boolean): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Set operating frequency
|
|
35
|
+
* @param hz - Frequency in Hz
|
|
36
|
+
*/
|
|
37
|
+
setFrequency(hz: number): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Set operating mode
|
|
40
|
+
* @param mode - Operating mode (LSB, USB, AM, CW, RTTY, FM, WFM, CW_R, RTTY_R, DV)
|
|
41
|
+
* @param options - Mode options (dataMode for digital modes like USB-D)
|
|
42
|
+
* @example
|
|
43
|
+
* // Set USB mode
|
|
44
|
+
* await rig.setMode('USB');
|
|
45
|
+
* // Set USB-D (data mode) for FT8
|
|
46
|
+
* await rig.setMode('USB', { dataMode: true });
|
|
47
|
+
*/
|
|
48
|
+
setMode(mode: IcomMode | number, options?: SetModeOptions): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Read current operating frequency
|
|
51
|
+
* @param options - Query options (timeout in ms, default 3000)
|
|
52
|
+
* @returns Frequency in Hz, or null if timeout/error
|
|
53
|
+
* @example
|
|
54
|
+
* const hz = await rig.readOperatingFrequency({ timeout: 5000 });
|
|
55
|
+
* console.log(`Frequency: ${hz} Hz`);
|
|
56
|
+
*/
|
|
57
|
+
readOperatingFrequency(options?: QueryOptions): Promise<number | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Read current operating mode and filter
|
|
60
|
+
* @returns { mode: number, filter?: number } or null
|
|
61
|
+
*/
|
|
62
|
+
readOperatingMode(options?: QueryOptions): Promise<{
|
|
63
|
+
mode: number;
|
|
64
|
+
filter?: number;
|
|
65
|
+
modeName?: string;
|
|
66
|
+
filterName?: string;
|
|
67
|
+
} | null>;
|
|
68
|
+
/**
|
|
69
|
+
* Read current transmit frequency (when TX)
|
|
70
|
+
*/
|
|
71
|
+
readTransmitFrequency(options?: QueryOptions): Promise<number | null>;
|
|
72
|
+
/**
|
|
73
|
+
* Read transceiver state (TX/RX) via 0x1A 0x00 0x48
|
|
74
|
+
* Note: Java comments mark this as not recommended; use with caution.
|
|
75
|
+
*/
|
|
76
|
+
readTransceiverState(options?: QueryOptions): Promise<'TX' | 'RX' | 'UNKNOWN' | null>;
|
|
77
|
+
/**
|
|
78
|
+
* Read band edge data (0x02). Format may vary by rig; returns raw data bytes after command.
|
|
79
|
+
*/
|
|
80
|
+
readBandEdges(options?: QueryOptions): Promise<Buffer | null>;
|
|
81
|
+
/**
|
|
82
|
+
* Read SWR (Standing Wave Ratio) meter
|
|
83
|
+
* @param options - Query options (timeout in ms, default 3000)
|
|
84
|
+
* @returns SWR reading with raw value, calculated SWR, and alert status
|
|
85
|
+
* @example
|
|
86
|
+
* const swr = await rig.readSWR({ timeout: 2000 });
|
|
87
|
+
* if (swr) {
|
|
88
|
+
* console.log(`SWR: ${swr.swr.toFixed(2)} ${swr.alert ? '⚠️ HIGH' : '✓'}`);
|
|
89
|
+
* }
|
|
90
|
+
*/
|
|
91
|
+
readSWR(options?: QueryOptions): Promise<SwrReading | null>;
|
|
92
|
+
/**
|
|
93
|
+
* Read ALC (Automatic Level Control) meter
|
|
94
|
+
* @param options - Query options (timeout in ms, default 3000)
|
|
95
|
+
* @returns ALC reading with raw value, percent, and alert status
|
|
96
|
+
* @example
|
|
97
|
+
* const alc = await rig.readALC({ timeout: 2000 });
|
|
98
|
+
* if (alc) {
|
|
99
|
+
* console.log(`ALC: ${alc.percent.toFixed(1)}% ${alc.alert ? '⚠️ HIGH' : '✓'}`);
|
|
100
|
+
* }
|
|
101
|
+
*/
|
|
102
|
+
readALC(options?: QueryOptions): Promise<AlcReading | null>;
|
|
103
|
+
/**
|
|
104
|
+
* Get WLAN connector audio level setting
|
|
105
|
+
* @param options - Query options (timeout in ms, default 3000)
|
|
106
|
+
* @returns WLAN level reading with raw value and percent
|
|
107
|
+
* @example
|
|
108
|
+
* const level = await rig.getConnectorWLanLevel({ timeout: 2000 });
|
|
109
|
+
* if (level) {
|
|
110
|
+
* console.log(`WLAN Level: ${level.percent.toFixed(1)}%`);
|
|
111
|
+
* }
|
|
112
|
+
*/
|
|
113
|
+
getConnectorWLanLevel(options?: QueryOptions): Promise<WlanLevelReading | null>;
|
|
114
|
+
/**
|
|
115
|
+
* Read generic level meter (CI-V 0x15/0x02), raw 0-255.
|
|
116
|
+
* Many rigs return two bytes and the low byte is the level.
|
|
117
|
+
*/
|
|
118
|
+
getLevelMeter(options?: QueryOptions): Promise<LevelMeterReading | null>;
|
|
119
|
+
/**
|
|
120
|
+
* Set WLAN connector audio level
|
|
121
|
+
* @param level - Audio level (0-255)
|
|
122
|
+
*/
|
|
123
|
+
setConnectorWLanLevel(level: number): Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Set connector data routing mode
|
|
126
|
+
* @param mode - Data routing mode (MIC, ACC, USB, WLAN)
|
|
127
|
+
* @example
|
|
128
|
+
* // Route audio to WLAN
|
|
129
|
+
* await rig.setConnectorDataMode('WLAN');
|
|
130
|
+
*/
|
|
131
|
+
setConnectorDataMode(mode: ConnectorDataMode | number): Promise<void>;
|
|
132
|
+
private static isReplyOf;
|
|
133
|
+
/**
|
|
134
|
+
* Extract meter data from CI-V response frame
|
|
135
|
+
* CI-V format: FE FE [ctr] [rig] [cmd] [subcmd] [data0] [data1] FD
|
|
136
|
+
* @param frame - CI-V response buffer
|
|
137
|
+
* @returns Parsed BCD integer value, or null if invalid
|
|
138
|
+
*/
|
|
139
|
+
private static extractMeterData;
|
|
140
|
+
private static matchCommand;
|
|
141
|
+
private static matchCommandFrame;
|
|
142
|
+
private waitForCiv;
|
|
143
|
+
static parseIcomFreqFromReply(frame: Buffer): number | null;
|
|
144
|
+
sendAudioFloat32(samples: Float32Array, addLeadingBuffer?: boolean): void;
|
|
145
|
+
sendAudioPcm16(samples: Int16Array): void;
|
|
146
|
+
private onData;
|
|
147
|
+
private onCivData;
|
|
148
|
+
private processCivPayload;
|
|
149
|
+
private waitForCivFrame;
|
|
150
|
+
private static isMeterReply;
|
|
151
|
+
private startMeterPolling;
|
|
152
|
+
private stopMeterPolling;
|
|
153
|
+
private onAudioData;
|
|
154
|
+
private sendConnectionRequest;
|
|
155
|
+
}
|