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,43 @@
|
|
|
1
|
+
import { UdpClient } from '../transport/UdpClient';
|
|
2
|
+
export interface SessionOptions {
|
|
3
|
+
ip: string;
|
|
4
|
+
port: number;
|
|
5
|
+
}
|
|
6
|
+
export interface SessionHandlers {
|
|
7
|
+
onData: (data: Buffer) => void;
|
|
8
|
+
onSendError: (err: Error) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare class Session {
|
|
11
|
+
readonly udp: UdpClient;
|
|
12
|
+
localId: number;
|
|
13
|
+
remoteId: number;
|
|
14
|
+
trackedSeq: number;
|
|
15
|
+
pingSeq: number;
|
|
16
|
+
innerSeq: number;
|
|
17
|
+
rigToken: number;
|
|
18
|
+
localToken: number;
|
|
19
|
+
lastSentTime: number;
|
|
20
|
+
lastReceivedTime: number;
|
|
21
|
+
private address;
|
|
22
|
+
private txHistory;
|
|
23
|
+
private areYouThereTimer?;
|
|
24
|
+
private pingTimer?;
|
|
25
|
+
private destroyed;
|
|
26
|
+
constructor(address: SessionOptions, handlers: SessionHandlers);
|
|
27
|
+
open(): void;
|
|
28
|
+
close(): void;
|
|
29
|
+
get localPort(): number;
|
|
30
|
+
sendRaw(buf: Buffer): void;
|
|
31
|
+
sendUntracked(buf: Buffer): void;
|
|
32
|
+
sendTracked(buf: Buffer): void;
|
|
33
|
+
retransmit(seq: number): void;
|
|
34
|
+
private idleTimer?;
|
|
35
|
+
startIdle(): void;
|
|
36
|
+
stopIdle(): void;
|
|
37
|
+
startAreYouThere(): void;
|
|
38
|
+
stopAreYouThere(): void;
|
|
39
|
+
startPing(): void;
|
|
40
|
+
stopPing(): void;
|
|
41
|
+
stopTimers(): void;
|
|
42
|
+
setRemote(ip: string, port: number): void;
|
|
43
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Session = void 0;
|
|
4
|
+
const UdpClient_1 = require("../transport/UdpClient");
|
|
5
|
+
const debug_1 = require("../utils/debug");
|
|
6
|
+
const IcomPackets_1 = require("./IcomPackets");
|
|
7
|
+
class Session {
|
|
8
|
+
constructor(address, handlers) {
|
|
9
|
+
this.udp = new UdpClient_1.UdpClient();
|
|
10
|
+
this.localId = Date.now() >>> 0;
|
|
11
|
+
this.remoteId = 0;
|
|
12
|
+
this.trackedSeq = 1; // 0x03 uses seq=0, 0x06 uses seq=1
|
|
13
|
+
this.pingSeq = 0;
|
|
14
|
+
this.innerSeq = 0x30;
|
|
15
|
+
this.rigToken = 0;
|
|
16
|
+
this.localToken = (Date.now() & 0xffff) >>> 0; // short semantics
|
|
17
|
+
this.lastSentTime = Date.now();
|
|
18
|
+
this.lastReceivedTime = Date.now();
|
|
19
|
+
this.txHistory = new Map();
|
|
20
|
+
this.destroyed = false;
|
|
21
|
+
this.address = address;
|
|
22
|
+
this.udp.on('data', (rinfo, data) => {
|
|
23
|
+
if (this.destroyed)
|
|
24
|
+
return;
|
|
25
|
+
this.lastReceivedTime = Date.now();
|
|
26
|
+
try {
|
|
27
|
+
// Peek type directly to avoid late requires during teardown
|
|
28
|
+
const t = data.length >= 6 ? data.readUInt16LE(4) : -1;
|
|
29
|
+
(0, debug_1.dbgV)(`RX port=${this.localPort} from ${rinfo.address}:${rinfo.port} len=${data.length} type=${t >= 0 ? '0x' + t.toString(16) : 'n/a'}`);
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
handlers.onData(data);
|
|
33
|
+
});
|
|
34
|
+
this.udp.on('error', handlers.onSendError);
|
|
35
|
+
}
|
|
36
|
+
open() { this.udp.open(); }
|
|
37
|
+
close() {
|
|
38
|
+
this.stopTimers();
|
|
39
|
+
this.destroyed = true;
|
|
40
|
+
this.udp.close();
|
|
41
|
+
}
|
|
42
|
+
get localPort() { return this.udp.localPort; }
|
|
43
|
+
sendRaw(buf) {
|
|
44
|
+
if (this.destroyed)
|
|
45
|
+
return;
|
|
46
|
+
try {
|
|
47
|
+
this.udp.send(buf, this.address.ip, this.address.port);
|
|
48
|
+
this.lastSentTime = Date.now();
|
|
49
|
+
}
|
|
50
|
+
catch (e) { /* bubble via event */ }
|
|
51
|
+
}
|
|
52
|
+
sendUntracked(buf) { this.sendRaw(buf); }
|
|
53
|
+
sendTracked(buf) {
|
|
54
|
+
const pkt = Buffer.from(buf);
|
|
55
|
+
IcomPackets_1.ControlPacket.setSeq(pkt, this.trackedSeq);
|
|
56
|
+
this.sendRaw(pkt);
|
|
57
|
+
this.txHistory.set(this.trackedSeq, pkt);
|
|
58
|
+
this.trackedSeq = (this.trackedSeq + 1) & 0xffff;
|
|
59
|
+
}
|
|
60
|
+
retransmit(seq) {
|
|
61
|
+
const pkt = this.txHistory.get(seq);
|
|
62
|
+
if (pkt)
|
|
63
|
+
this.sendRaw(pkt);
|
|
64
|
+
else
|
|
65
|
+
this.sendUntracked(IcomPackets_1.ControlPacket.toBytes(IcomPackets_1.Cmd.NULL, seq, this.localId, this.remoteId));
|
|
66
|
+
}
|
|
67
|
+
startIdle() {
|
|
68
|
+
this.stopIdle();
|
|
69
|
+
this.idleTimer = setInterval(() => {
|
|
70
|
+
if (Date.now() - this.lastSentTime > 200) {
|
|
71
|
+
this.sendTracked(IcomPackets_1.ControlPacket.toBytes(IcomPackets_1.Cmd.NULL, 0, this.localId, this.remoteId));
|
|
72
|
+
}
|
|
73
|
+
}, 100);
|
|
74
|
+
}
|
|
75
|
+
stopIdle() { if (this.idleTimer) {
|
|
76
|
+
clearInterval(this.idleTimer);
|
|
77
|
+
this.idleTimer = undefined;
|
|
78
|
+
} }
|
|
79
|
+
startAreYouThere() {
|
|
80
|
+
this.stopAreYouThere();
|
|
81
|
+
this.areYouThereTimer = setInterval(() => {
|
|
82
|
+
(0, debug_1.dbgV)(`AYT -> ${this.address.ip}:${this.address.port} localId=${this.localId}`);
|
|
83
|
+
this.sendUntracked(IcomPackets_1.ControlPacket.toBytes(IcomPackets_1.Cmd.ARE_YOU_THERE, 0, this.localId, 0));
|
|
84
|
+
}, 500);
|
|
85
|
+
}
|
|
86
|
+
stopAreYouThere() { if (this.areYouThereTimer) {
|
|
87
|
+
clearInterval(this.areYouThereTimer);
|
|
88
|
+
this.areYouThereTimer = undefined;
|
|
89
|
+
} }
|
|
90
|
+
startPing() {
|
|
91
|
+
this.stopPing();
|
|
92
|
+
this.pingTimer = setInterval(() => {
|
|
93
|
+
this.sendUntracked(IcomPackets_1.PingPacket.buildPing(this.localId, this.remoteId, this.pingSeq));
|
|
94
|
+
}, 500);
|
|
95
|
+
}
|
|
96
|
+
stopPing() { if (this.pingTimer) {
|
|
97
|
+
clearInterval(this.pingTimer);
|
|
98
|
+
this.pingTimer = undefined;
|
|
99
|
+
} }
|
|
100
|
+
stopTimers() { this.stopAreYouThere(); this.stopPing(); this.stopIdle(); }
|
|
101
|
+
setRemote(ip, port) {
|
|
102
|
+
this.address = { ip, port };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.Session = Session;
|
package/dist/demo.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test against a real Icom WLAN radio.
|
|
3
|
+
*
|
|
4
|
+
* This test is skipped unless these env vars are present:
|
|
5
|
+
* - ICOM_IP: radio IP
|
|
6
|
+
* - ICOM_PORT: radio control UDP port (e.g., 50001)
|
|
7
|
+
* - ICOM_USER: username
|
|
8
|
+
* - ICOM_PASS: password
|
|
9
|
+
* Optional:
|
|
10
|
+
* - ICOM_TEST_PTT=true to exercise PTT and short audio TX (be careful: this keys TX!)
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
package/dist/demo.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Integration test against a real Icom WLAN radio.
|
|
4
|
+
*
|
|
5
|
+
* This test is skipped unless these env vars are present:
|
|
6
|
+
* - ICOM_IP: radio IP
|
|
7
|
+
* - ICOM_PORT: radio control UDP port (e.g., 50001)
|
|
8
|
+
* - ICOM_USER: username
|
|
9
|
+
* - ICOM_PASS: password
|
|
10
|
+
* Optional:
|
|
11
|
+
* - ICOM_TEST_PTT=true to exercise PTT and short audio TX (be careful: this keys TX!)
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
const _1 = require(".");
|
|
15
|
+
const IcomRigCommands_1 = require("./rig/IcomRigCommands");
|
|
16
|
+
const codec_1 = require("./utils/codec");
|
|
17
|
+
async function test() {
|
|
18
|
+
const ip = process.env.ICOM_IP;
|
|
19
|
+
const port = parseInt(process.env.ICOM_PORT, 10);
|
|
20
|
+
const user = process.env.ICOM_USER;
|
|
21
|
+
const pass = process.env.ICOM_PASS;
|
|
22
|
+
const testPTT = process.env.ICOM_TEST_PTT === 'true';
|
|
23
|
+
const t0 = Date.now();
|
|
24
|
+
const stamp = () => `+${(Date.now() - t0).toString().padStart(4)}ms`;
|
|
25
|
+
const rig = new _1.IcomControl({ control: { ip, port }, userName: user, password: pass });
|
|
26
|
+
let gotLogin = false;
|
|
27
|
+
let gotStatus = false;
|
|
28
|
+
let gotCap = false;
|
|
29
|
+
let civCount = 0;
|
|
30
|
+
let audioCount = 0;
|
|
31
|
+
const wait = (cond, ms = 30000) => new Promise((resolve, reject) => {
|
|
32
|
+
const start = Date.now();
|
|
33
|
+
const t = setInterval(() => {
|
|
34
|
+
if (cond()) {
|
|
35
|
+
clearInterval(t);
|
|
36
|
+
resolve();
|
|
37
|
+
}
|
|
38
|
+
else if (Date.now() - start > ms) {
|
|
39
|
+
clearInterval(t);
|
|
40
|
+
reject(new Error('timeout'));
|
|
41
|
+
}
|
|
42
|
+
}, 100);
|
|
43
|
+
});
|
|
44
|
+
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
45
|
+
const onLogin = (res) => {
|
|
46
|
+
console.log(stamp(), 'login event ok=', res.ok, 'error?', res.errorCode, 'conn=', res.connection);
|
|
47
|
+
gotLogin = res.ok;
|
|
48
|
+
};
|
|
49
|
+
const onStatus = (s) => {
|
|
50
|
+
console.log(stamp(), 'status civPort=', s.civPort, 'audioPort=', s.audioPort, 'authOK=', s.authOK, 'connected=', s.connected);
|
|
51
|
+
gotStatus = true;
|
|
52
|
+
};
|
|
53
|
+
const onCap = (c) => {
|
|
54
|
+
console.log(stamp(), 'capabilities civAddr=', c.civAddress, 'audioName=', c.audioName, 'supportTX=', c.supportTX);
|
|
55
|
+
gotCap = true;
|
|
56
|
+
};
|
|
57
|
+
const onCiv = (frame) => {
|
|
58
|
+
civCount++;
|
|
59
|
+
if (civCount <= 3)
|
|
60
|
+
console.log(stamp(), `CIV[${civCount}]`, (0, codec_1.hex)(frame.subarray(0, Math.min(16, frame.length))));
|
|
61
|
+
};
|
|
62
|
+
const onAudio = (p) => {
|
|
63
|
+
audioCount++;
|
|
64
|
+
if (audioCount % 10 === 0)
|
|
65
|
+
console.log(stamp(), `AUDIO[${audioCount}] len=`, p.pcm16.length);
|
|
66
|
+
};
|
|
67
|
+
rig.events.on('login', onLogin);
|
|
68
|
+
rig.events.on('status', onStatus);
|
|
69
|
+
rig.events.on('capabilities', onCap);
|
|
70
|
+
rig.events.on('civ', onCiv);
|
|
71
|
+
rig.events.on('audio', onAudio);
|
|
72
|
+
console.log(stamp(), 'connecting to', ip, port);
|
|
73
|
+
await rig.connect();
|
|
74
|
+
// Wait for login + status
|
|
75
|
+
console.log(stamp(), 'waiting login+status ...');
|
|
76
|
+
await wait(() => gotLogin && gotStatus, 20000);
|
|
77
|
+
console.log(stamp(), 'login+status OK');
|
|
78
|
+
// Expect at least capabilities soon
|
|
79
|
+
console.log(stamp(), 'waiting capabilities (0xA8) ...');
|
|
80
|
+
await wait(() => gotCap, 8000).catch(() => { console.log(stamp(), 'capabilities timeout (tolerated)'); });
|
|
81
|
+
// Ensure data mode and connector routing favor WLAN before CIV queries
|
|
82
|
+
console.log(stamp(), 'setMode USB-D (data) and route data to WLAN');
|
|
83
|
+
await rig.setMode('USB', { dataMode: true });
|
|
84
|
+
await rig.setConnectorDataMode('WLAN');
|
|
85
|
+
// Issue a CIV read operating frequency; expect some CIV traffic
|
|
86
|
+
const rigAddr = rig.civ.civAddress & 0xff;
|
|
87
|
+
const ctrAddr = 0xe0;
|
|
88
|
+
const readFreq = IcomRigCommands_1.IcomRigCommands.readOperatingFrequency(ctrAddr, rigAddr);
|
|
89
|
+
const civBefore = civCount;
|
|
90
|
+
rig.sendCiv(readFreq);
|
|
91
|
+
console.log(stamp(), 'sent CIV read frequency, waiting CIV traffic ...');
|
|
92
|
+
await wait(() => civCount > civBefore, 6000).catch(() => { });
|
|
93
|
+
// High-level: query frequency, set mode to USB-D (data), adjust frequency slightly and revert
|
|
94
|
+
const curHz = await rig.readOperatingFrequency({ timeout: 8000 });
|
|
95
|
+
if (curHz) {
|
|
96
|
+
console.log(stamp(), 'readOperatingFrequency =', curHz);
|
|
97
|
+
// Set data mode USB-D
|
|
98
|
+
console.log(stamp(), 'setMode USB-D');
|
|
99
|
+
await rig.setMode('USB', { dataMode: true });
|
|
100
|
+
// Route connector data to WLAN (best effort)
|
|
101
|
+
console.log(stamp(), 'setConnectorDataMode WLAN');
|
|
102
|
+
await rig.setConnectorDataMode('WLAN');
|
|
103
|
+
// Nudge frequency by +50 Hz then revert
|
|
104
|
+
const newHz = curHz + 50;
|
|
105
|
+
console.log(stamp(), 'setFrequency to', newHz);
|
|
106
|
+
await rig.setFrequency(newHz);
|
|
107
|
+
const backHz = await rig.readOperatingFrequency({ timeout: 4000 });
|
|
108
|
+
console.log(stamp(), 'verify freq after set =', backHz);
|
|
109
|
+
console.log(stamp(), 'revert frequency to', curHz);
|
|
110
|
+
await rig.setFrequency(curHz);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
console.log(stamp(), 'readOperatingFrequency returned null (tolerated)');
|
|
114
|
+
}
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// API Demo: Demonstrate all available APIs and print their results
|
|
117
|
+
// ============================================================================
|
|
118
|
+
console.log('\n' + '='.repeat(80));
|
|
119
|
+
console.log(stamp(), '🎯 API DEMONSTRATION - Testing all available methods');
|
|
120
|
+
console.log('='.repeat(80) + '\n');
|
|
121
|
+
// 1. Mode APIs
|
|
122
|
+
console.log(stamp(), '📡 Testing Mode APIs:');
|
|
123
|
+
console.log(stamp(), ' → Setting mode to LSB (Lower Side Band)');
|
|
124
|
+
await rig.setMode('LSB');
|
|
125
|
+
await sleep(500);
|
|
126
|
+
console.log(stamp(), ' → Setting mode to USB (Upper Side Band)');
|
|
127
|
+
await rig.setMode('USB');
|
|
128
|
+
await sleep(500);
|
|
129
|
+
console.log(stamp(), ' → Setting mode to USB-D (Data mode for FT8)');
|
|
130
|
+
await rig.setMode('USB', { dataMode: true });
|
|
131
|
+
await sleep(500);
|
|
132
|
+
// 2. Frequency APIs
|
|
133
|
+
console.log(stamp(), '\n📻 Testing Frequency APIs:');
|
|
134
|
+
const currentFreq = await rig.readOperatingFrequency({ timeout: 3000 });
|
|
135
|
+
if (currentFreq) {
|
|
136
|
+
console.log(stamp(), ` ✓ Current frequency: ${(currentFreq / 1000000).toFixed(3)} MHz (${currentFreq} Hz)`);
|
|
137
|
+
// Test frequency change
|
|
138
|
+
const testFreq = 14074000; // FT8 on 20m
|
|
139
|
+
console.log(stamp(), ` → Setting frequency to ${(testFreq / 1000000).toFixed(3)} MHz`);
|
|
140
|
+
await rig.setFrequency(testFreq);
|
|
141
|
+
await sleep(500);
|
|
142
|
+
const verifyFreq = await rig.readOperatingFrequency({ timeout: 3000 });
|
|
143
|
+
if (verifyFreq) {
|
|
144
|
+
console.log(stamp(), ` ✓ Verified frequency: ${(verifyFreq / 1000000).toFixed(3)} MHz`);
|
|
145
|
+
}
|
|
146
|
+
// Restore original frequency
|
|
147
|
+
console.log(stamp(), ` → Restoring original frequency: ${(currentFreq / 1000000).toFixed(3)} MHz`);
|
|
148
|
+
await rig.setFrequency(currentFreq);
|
|
149
|
+
await sleep(500);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.log(stamp(), ' ✗ Failed to read current frequency');
|
|
153
|
+
}
|
|
154
|
+
// 3. Connector APIs
|
|
155
|
+
console.log(stamp(), '\n🔌 Testing Connector APIs:');
|
|
156
|
+
console.log(stamp(), ' → Setting connector data mode to WLAN');
|
|
157
|
+
await rig.setConnectorDataMode('WLAN');
|
|
158
|
+
await sleep(500);
|
|
159
|
+
const wlanLevel = await rig.getConnectorWLanLevel({ timeout: 2000 });
|
|
160
|
+
if (wlanLevel) {
|
|
161
|
+
console.log(stamp(), ` ✓ WLAN Level: ${wlanLevel.percent.toFixed(1)}% (raw=${wlanLevel.raw}/255)`);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
console.log(stamp(), ' ℹ WLAN Level: Not available (may not be supported on this radio)');
|
|
165
|
+
}
|
|
166
|
+
// Test setting WLAN level
|
|
167
|
+
console.log(stamp(), ' → Setting WLAN level to 128 (50%)');
|
|
168
|
+
await rig.setConnectorWLanLevel(128);
|
|
169
|
+
await sleep(500);
|
|
170
|
+
const newWlanLevel = await rig.getConnectorWLanLevel({ timeout: 2000 });
|
|
171
|
+
if (newWlanLevel) {
|
|
172
|
+
console.log(stamp(), ` ✓ New WLAN Level: ${newWlanLevel.percent.toFixed(1)}% (raw=${newWlanLevel.raw}/255)`);
|
|
173
|
+
}
|
|
174
|
+
// 4. Summary of RX-mode readings
|
|
175
|
+
console.log(stamp(), '\n📋 Summary of Current Radio State (RX mode):');
|
|
176
|
+
console.log(stamp(), ' ╔═══════════════════════════════════════════════════════╗');
|
|
177
|
+
if (currentFreq) {
|
|
178
|
+
console.log(stamp(), ` ║ Frequency: ${(currentFreq / 1000000).toFixed(3).padEnd(10)} MHz ║`);
|
|
179
|
+
}
|
|
180
|
+
console.log(stamp(), ` ║ Mode: USB-D (Data mode) ║`);
|
|
181
|
+
console.log(stamp(), ` ║ Connector: WLAN ║`);
|
|
182
|
+
if (newWlanLevel) {
|
|
183
|
+
console.log(stamp(), ` ║ WLAN Level: ${newWlanLevel.percent.toFixed(1).padEnd(5)}% ║`);
|
|
184
|
+
}
|
|
185
|
+
console.log(stamp(), ' ╚═══════════════════════════════════════════════════════╝');
|
|
186
|
+
console.log(stamp(), '\n ℹ️ Note: Meter readings (SWR/ALC) require TX mode - see PTT test below');
|
|
187
|
+
console.log('\n' + '='.repeat(80));
|
|
188
|
+
console.log(stamp(), '✅ API DEMONSTRATION COMPLETE (RX mode)');
|
|
189
|
+
console.log('='.repeat(80) + '\n');
|
|
190
|
+
// Expect to receive at least some audio frames if radio is streaming
|
|
191
|
+
console.log(stamp(), 'waiting audio frames ...');
|
|
192
|
+
await wait(() => audioCount > 0, 6000).catch(() => { });
|
|
193
|
+
// Try reading additional CIV info in RX mode
|
|
194
|
+
try {
|
|
195
|
+
const mode = await rig.readOperatingMode({ timeout: 1500 });
|
|
196
|
+
if (mode) {
|
|
197
|
+
const modeStr = mode.modeName ?? `0x${mode.mode.toString(16)}`;
|
|
198
|
+
const filStr = mode.filterName ?? (mode.filter !== undefined ? `FIL${mode.filter}` : '');
|
|
199
|
+
console.log(stamp(), `RX: Operating Mode = ${modeStr}${filStr ? `, ${filStr}` : ''}`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
console.log(stamp(), 'RX: Operating Mode: Not available');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch { }
|
|
206
|
+
try {
|
|
207
|
+
const edges = await rig.readBandEdges({ timeout: 1500 });
|
|
208
|
+
if (edges) {
|
|
209
|
+
console.log(stamp(), `RX: Band edges payload length: ${edges.length} bytes`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
console.log(stamp(), 'RX: Band edges: Not available');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch { }
|
|
216
|
+
// Optional: brief PTT + short audio TX (dangerous; keys TX)
|
|
217
|
+
if (testPTT && gotLogin && gotStatus) {
|
|
218
|
+
console.log('\n' + '='.repeat(80));
|
|
219
|
+
console.log(stamp(), '📡 PTT TEST & METER READINGS (TX MODE)');
|
|
220
|
+
console.log('='.repeat(80) + '\n');
|
|
221
|
+
// Ensure audio routing is set to WLAN before PTT
|
|
222
|
+
console.log(stamp(), '→ Setting connector data mode to WLAN before PTT');
|
|
223
|
+
await rig.setConnectorDataMode('WLAN');
|
|
224
|
+
console.log(stamp(), '→ PTT ON - Starting transmission');
|
|
225
|
+
await rig.setPtt(true);
|
|
226
|
+
// Wait a moment for TX to stabilize
|
|
227
|
+
await sleep(500);
|
|
228
|
+
// Generate all audio frames at once (10 seconds of 1 kHz tone)
|
|
229
|
+
const frames = 500; // 500 * 20ms = 10000ms = 10 seconds
|
|
230
|
+
const samplesPerFrame = 240;
|
|
231
|
+
const totalSamples = frames * samplesPerFrame;
|
|
232
|
+
const allAudio = new Float32Array(totalSamples);
|
|
233
|
+
// Generate 1 kHz tone for entire duration
|
|
234
|
+
for (let i = 0; i < totalSamples; i++) {
|
|
235
|
+
allAudio[i] = Math.sin(2 * Math.PI * 1000 * i / _1.AUDIO_RATE) * 0.2;
|
|
236
|
+
}
|
|
237
|
+
// Add all audio to queue at once with leading silence buffer
|
|
238
|
+
console.log(stamp(), '→ Enqueuing', frames, 'frames of audio (1kHz tone, 10 seconds)');
|
|
239
|
+
rig.sendAudioFloat32(allAudio, true); // true = add leading silence buffer
|
|
240
|
+
// ============================================================================
|
|
241
|
+
// 📊 Testing Meter APIs during TX
|
|
242
|
+
// ============================================================================
|
|
243
|
+
console.log(stamp(), '\n📊 Reading Meters during TX:');
|
|
244
|
+
// Also read TX-related CIV info
|
|
245
|
+
try {
|
|
246
|
+
const txHz = await rig.readTransmitFrequency({ timeout: 1500 });
|
|
247
|
+
if (txHz)
|
|
248
|
+
console.log(stamp(), ` TX Frequency: ${txHz} Hz`);
|
|
249
|
+
else
|
|
250
|
+
console.log(stamp(), ' TX Frequency: Not available');
|
|
251
|
+
}
|
|
252
|
+
catch { }
|
|
253
|
+
try {
|
|
254
|
+
const state = await rig.readTransceiverState({ timeout: 1500 });
|
|
255
|
+
if (state)
|
|
256
|
+
console.log(stamp(), ` Transceiver State: ${state}`);
|
|
257
|
+
else
|
|
258
|
+
console.log(stamp(), ' Transceiver State: Not available');
|
|
259
|
+
}
|
|
260
|
+
catch { }
|
|
261
|
+
// Read meters multiple times during transmission to get stable readings
|
|
262
|
+
const meterReadings = { swr: [], alc: [] };
|
|
263
|
+
for (let i = 0; i < 3; i++) {
|
|
264
|
+
await sleep(1000); // Wait 1 second between readings
|
|
265
|
+
console.log(stamp(), ` → Reading meters (${i + 1}/3)...`);
|
|
266
|
+
const swr = await rig.readSWR({ timeout: 2000 });
|
|
267
|
+
if (swr) {
|
|
268
|
+
meterReadings.swr.push(swr);
|
|
269
|
+
const swrStatus = swr.alert ? '⚠️ ALERT' : '✓ OK';
|
|
270
|
+
console.log(stamp(), ` SWR: ${swr.swr.toFixed(2)} (raw=${swr.raw}) ${swrStatus}`);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
console.log(stamp(), ` SWR: Not available`);
|
|
274
|
+
}
|
|
275
|
+
const alc = await rig.readALC({ timeout: 2000 });
|
|
276
|
+
if (alc) {
|
|
277
|
+
meterReadings.alc.push(alc);
|
|
278
|
+
const alcStatus = alc.alert ? '⚠️ ALERT' : '✓ OK';
|
|
279
|
+
console.log(stamp(), ` ALC: ${alc.percent.toFixed(1)}% (raw=${alc.raw}) ${alcStatus}`);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
console.log(stamp(), ` ALC: Not available`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Calculate and display average readings
|
|
286
|
+
if (meterReadings.swr.length > 0) {
|
|
287
|
+
const avgSwr = meterReadings.swr.reduce((sum, r) => sum + r.swr, 0) / meterReadings.swr.length;
|
|
288
|
+
const hasAlert = meterReadings.swr.some(r => r.alert);
|
|
289
|
+
console.log(stamp(), '\n 📈 Average SWR Reading:');
|
|
290
|
+
console.log(stamp(), ` - Average Value: ${avgSwr.toFixed(2)}`);
|
|
291
|
+
console.log(stamp(), ` - Status: ${hasAlert ? '⚠️ ALERT (High SWR detected!)' : '✓ OK'}`);
|
|
292
|
+
console.log(stamp(), ` - Assessment: ${avgSwr < 1.5 ? 'Excellent antenna match' : avgSwr < 2.0 ? 'Good antenna match' : 'Poor antenna match - check connections'}`);
|
|
293
|
+
}
|
|
294
|
+
if (meterReadings.alc.length > 0) {
|
|
295
|
+
const avgAlc = meterReadings.alc.reduce((sum, r) => sum + r.percent, 0) / meterReadings.alc.length;
|
|
296
|
+
const hasAlert = meterReadings.alc.some(r => r.alert);
|
|
297
|
+
console.log(stamp(), '\n 📈 Average ALC Reading:');
|
|
298
|
+
console.log(stamp(), ` - Average Level: ${avgAlc.toFixed(1)}%`);
|
|
299
|
+
console.log(stamp(), ` - Status: ${hasAlert ? '⚠️ ALERT (Over-driving!)' : '✓ OK'}`);
|
|
300
|
+
console.log(stamp(), ` - Assessment: ${avgAlc < 30 ? 'Low drive - increase audio level' : avgAlc < 70 ? 'Normal operating range' : 'High drive - reduce audio level'}`);
|
|
301
|
+
}
|
|
302
|
+
// Wait for remaining transmission to complete
|
|
303
|
+
const remainingTime = (frames * 21) - 3000; // Already waited 3 seconds for meter readings
|
|
304
|
+
if (remainingTime > 0) {
|
|
305
|
+
console.log(stamp(), `\n→ Waiting for transmission to complete (~${(remainingTime / 1000).toFixed(1)}s remaining)`);
|
|
306
|
+
await sleep(remainingTime);
|
|
307
|
+
}
|
|
308
|
+
await rig.setPtt(false);
|
|
309
|
+
console.log(stamp(), '\n→ PTT OFF - Transmission complete, trailing silence sent');
|
|
310
|
+
console.log('\n' + '='.repeat(80));
|
|
311
|
+
console.log(stamp(), '✅ PTT TEST & METER READINGS COMPLETE');
|
|
312
|
+
console.log('='.repeat(80) + '\n');
|
|
313
|
+
await sleep(1000);
|
|
314
|
+
}
|
|
315
|
+
// Clean up listeners to help jest exit
|
|
316
|
+
rig.events.off('login', onLogin);
|
|
317
|
+
rig.events.off('status', onStatus);
|
|
318
|
+
rig.events.off('capabilities', onCap);
|
|
319
|
+
rig.events.off('civ', onCiv);
|
|
320
|
+
rig.events.off('audio', onAudio);
|
|
321
|
+
console.log(stamp(), 'summary: civ=', civCount, 'audio=', audioCount);
|
|
322
|
+
console.log(stamp(), 'disconnecting...');
|
|
323
|
+
await rig.disconnect();
|
|
324
|
+
console.log(stamp(), 'disconnected');
|
|
325
|
+
}
|
|
326
|
+
test().then().catch(e => {
|
|
327
|
+
console.error('Test failed:', e);
|
|
328
|
+
process.exit(1);
|
|
329
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export { IcomControl } from './rig/IcomControl';
|
|
3
|
+
export { MODE_MAP, CONNECTOR_MODE_MAP, DEFAULT_CONTROLLER_ADDR, METER_THRESHOLDS, getModeCode, getConnectorModeCode, getModeString, getConnectorModeString, getFilterString } from './rig/IcomConstants';
|
|
4
|
+
export { parseTwoByteBcd, intToTwoByteBcd } from './utils/bcd';
|
|
5
|
+
export { IcomRigCommands } from './rig/IcomRigCommands';
|
|
6
|
+
export { AUDIO_RATE } from './rig/IcomAudio';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.AUDIO_RATE = exports.IcomRigCommands = exports.intToTwoByteBcd = exports.parseTwoByteBcd = exports.getFilterString = exports.getConnectorModeString = exports.getModeString = exports.getConnectorModeCode = exports.getModeCode = exports.METER_THRESHOLDS = exports.DEFAULT_CONTROLLER_ADDR = exports.CONNECTOR_MODE_MAP = exports.MODE_MAP = exports.IcomControl = void 0;
|
|
18
|
+
// Export types
|
|
19
|
+
__exportStar(require("./types"), exports);
|
|
20
|
+
// Export main class
|
|
21
|
+
var IcomControl_1 = require("./rig/IcomControl");
|
|
22
|
+
Object.defineProperty(exports, "IcomControl", { enumerable: true, get: function () { return IcomControl_1.IcomControl; } });
|
|
23
|
+
// Export constants and enums
|
|
24
|
+
var IcomConstants_1 = require("./rig/IcomConstants");
|
|
25
|
+
Object.defineProperty(exports, "MODE_MAP", { enumerable: true, get: function () { return IcomConstants_1.MODE_MAP; } });
|
|
26
|
+
Object.defineProperty(exports, "CONNECTOR_MODE_MAP", { enumerable: true, get: function () { return IcomConstants_1.CONNECTOR_MODE_MAP; } });
|
|
27
|
+
Object.defineProperty(exports, "DEFAULT_CONTROLLER_ADDR", { enumerable: true, get: function () { return IcomConstants_1.DEFAULT_CONTROLLER_ADDR; } });
|
|
28
|
+
Object.defineProperty(exports, "METER_THRESHOLDS", { enumerable: true, get: function () { return IcomConstants_1.METER_THRESHOLDS; } });
|
|
29
|
+
Object.defineProperty(exports, "getModeCode", { enumerable: true, get: function () { return IcomConstants_1.getModeCode; } });
|
|
30
|
+
Object.defineProperty(exports, "getConnectorModeCode", { enumerable: true, get: function () { return IcomConstants_1.getConnectorModeCode; } });
|
|
31
|
+
Object.defineProperty(exports, "getModeString", { enumerable: true, get: function () { return IcomConstants_1.getModeString; } });
|
|
32
|
+
Object.defineProperty(exports, "getConnectorModeString", { enumerable: true, get: function () { return IcomConstants_1.getConnectorModeString; } });
|
|
33
|
+
Object.defineProperty(exports, "getFilterString", { enumerable: true, get: function () { return IcomConstants_1.getFilterString; } });
|
|
34
|
+
// Export BCD utilities
|
|
35
|
+
var bcd_1 = require("./utils/bcd");
|
|
36
|
+
Object.defineProperty(exports, "parseTwoByteBcd", { enumerable: true, get: function () { return bcd_1.parseTwoByteBcd; } });
|
|
37
|
+
Object.defineProperty(exports, "intToTwoByteBcd", { enumerable: true, get: function () { return bcd_1.intToTwoByteBcd; } });
|
|
38
|
+
// Export low-level utilities (for advanced users)
|
|
39
|
+
var IcomRigCommands_1 = require("./rig/IcomRigCommands");
|
|
40
|
+
Object.defineProperty(exports, "IcomRigCommands", { enumerable: true, get: function () { return IcomRigCommands_1.IcomRigCommands; } });
|
|
41
|
+
var IcomAudio_1 = require("./rig/IcomAudio");
|
|
42
|
+
Object.defineProperty(exports, "AUDIO_RATE", { enumerable: true, get: function () { return IcomAudio_1.AUDIO_RATE; } });
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Session } from '../core/Session';
|
|
2
|
+
export declare class IcomAudio {
|
|
3
|
+
private sess;
|
|
4
|
+
private sendSeq;
|
|
5
|
+
private sendSeqForTiming;
|
|
6
|
+
isPttOn: boolean;
|
|
7
|
+
private txTimer?;
|
|
8
|
+
queue: Int16Array[];
|
|
9
|
+
private volume;
|
|
10
|
+
private running;
|
|
11
|
+
private startTime;
|
|
12
|
+
private readonly FRAME_INTERVAL_MS;
|
|
13
|
+
private frameCount;
|
|
14
|
+
private debugInterval;
|
|
15
|
+
private debugIntervalStart;
|
|
16
|
+
constructor(sess: Session);
|
|
17
|
+
start(): void;
|
|
18
|
+
private scheduleSend;
|
|
19
|
+
private sendFrame;
|
|
20
|
+
stop(): void;
|
|
21
|
+
private addLeadingSilence;
|
|
22
|
+
private addTrailingSilence;
|
|
23
|
+
enqueueFloat32(samples: Float32Array, addLeadingBuffer?: boolean): void;
|
|
24
|
+
enqueuePcm16(samples: Int16Array): void;
|
|
25
|
+
setVolume(multiplier: number): void;
|
|
26
|
+
}
|
|
27
|
+
export declare const AUDIO_RATE = 12000;
|