icom-wlan-node 0.2.7 → 0.3.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.
@@ -1,4 +1,4 @@
1
- import { IcomRigOptions, RigEventEmitter, IcomMode, ConnectorDataMode, SetModeOptions, QueryOptions, SwrReading, AlcReading, WlanLevelReading, LevelMeterReading, SquelchStatusReading, AudioSquelchReading, OvfStatusReading, PowerLevelReading, CompLevelReading, VoltageReading, CurrentReading, ConnectionState, ConnectionMonitorConfig, ConnectionPhase, ConnectionMetrics, DisconnectReason, DisconnectOptions, TunerStatusReading } from '../types';
1
+ import { IcomRigOptions, RigEventEmitter, IcomMode, ConnectorDataMode, SetModeOptions, QueryOptions, SwrReading, AlcReading, WlanLevelReading, LevelMeterReading, SquelchStatusReading, AudioSquelchReading, OvfStatusReading, PowerLevelReading, CompLevelReading, VoltageReading, CurrentReading, ConnectionState, ConnectionMonitorConfig, ConnectionPhase, ConnectionMetrics, DisconnectReason, DisconnectOptions, TunerStatusReading, LevelReading } from '../types';
2
2
  import { IcomCiv } from './IcomCiv';
3
3
  import { IcomAudio } from './IcomAudio';
4
4
  export declare class IcomControl {
@@ -209,6 +209,30 @@ export declare class IcomControl {
209
209
  * Start a manual tuning cycle (same as [TUNE] key) (CI-V 0x1A/0x02/0x00)
210
210
  */
211
211
  startManualTune(): Promise<void>;
212
+ /** Get AF (audio output) gain. Returns 0.0–1.0, or null on timeout. */
213
+ getAFGain(options?: QueryOptions): Promise<LevelReading | null>;
214
+ /** Set AF (audio output) gain. Value 0.0–1.0. */
215
+ setAFGain(value: number): void;
216
+ /** Get squelch level. Returns 0.0–1.0, or null on timeout. */
217
+ getSQL(options?: QueryOptions): Promise<LevelReading | null>;
218
+ /** Set squelch level. Value 0.0–1.0. */
219
+ setSQL(value: number): void;
220
+ /** Get RF transmit power. Returns 0.0–1.0, or null on timeout. */
221
+ getRFPower(options?: QueryOptions): Promise<LevelReading | null>;
222
+ /** Set RF transmit power. Value 0.0–1.0. */
223
+ setRFPower(value: number): void;
224
+ /** Get microphone gain. Returns 0.0–1.0, or null on timeout. */
225
+ getMicGain(options?: QueryOptions): Promise<LevelReading | null>;
226
+ /** Set microphone gain. Value 0.0–1.0. */
227
+ setMicGain(value: number): void;
228
+ /** Get noise blanker level. 0.0 = off, >0.0 = on with strength. */
229
+ getNBLevel(options?: QueryOptions): Promise<LevelReading | null>;
230
+ /** Set noise blanker level. Value 0.0 (off) – 1.0. */
231
+ setNBLevel(value: number): void;
232
+ /** Get noise reduction level. 0.0 = off, >0.0 = on with strength. */
233
+ getNRLevel(options?: QueryOptions): Promise<LevelReading | null>;
234
+ /** Set noise reduction level. Value 0.0 (off) – 1.0. */
235
+ setNRLevel(value: number): void;
212
236
  /**
213
237
  * Read squelch status (noise/signal gate state)
214
238
  * @param options - Query options (timeout in ms, default 3000)
@@ -305,6 +329,17 @@ export declare class IcomControl {
305
329
  private processCivPayload;
306
330
  private waitForCivFrame;
307
331
  private static isMeterReply;
332
+ private static is0x14DataReply;
333
+ /**
334
+ * Read a 0x14 level value from the radio.
335
+ * Returns normalized value 0.0-1.0, or null on timeout/error.
336
+ */
337
+ private read0x14Level;
338
+ /**
339
+ * Write a 0x14 level value to the radio.
340
+ * @param value - Normalized value 0.0-1.0
341
+ */
342
+ private write0x14Level;
308
343
  private startMeterPolling;
309
344
  private stopMeterPolling;
310
345
  private onAudioData;
@@ -852,6 +852,75 @@ class IcomControl {
852
852
  const rigAddr = this.civ.civAddress & 0xff;
853
853
  this.sendCiv(IcomRigCommands_1.IcomRigCommands.startManualTune(ctrAddr, rigAddr));
854
854
  }
855
+ // ============================================================================
856
+ // 0x14 Level API — AF Gain, SQL, RF Power, MIC Gain, NB Level, NR Level
857
+ // ============================================================================
858
+ /** Get AF (audio output) gain. Returns 0.0–1.0, or null on timeout. */
859
+ async getAFGain(options) {
860
+ const v = await this.read0x14Level(0x01, options);
861
+ if (v === null)
862
+ return null;
863
+ return { raw: Math.round(v * 255), normalized: v };
864
+ }
865
+ /** Set AF (audio output) gain. Value 0.0–1.0. */
866
+ setAFGain(value) {
867
+ this.write0x14Level(0x01, value);
868
+ }
869
+ /** Get squelch level. Returns 0.0–1.0, or null on timeout. */
870
+ async getSQL(options) {
871
+ const v = await this.read0x14Level(0x03, options);
872
+ if (v === null)
873
+ return null;
874
+ return { raw: Math.round(v * 255), normalized: v };
875
+ }
876
+ /** Set squelch level. Value 0.0–1.0. */
877
+ setSQL(value) {
878
+ this.write0x14Level(0x03, value);
879
+ }
880
+ /** Get RF transmit power. Returns 0.0–1.0, or null on timeout. */
881
+ async getRFPower(options) {
882
+ const v = await this.read0x14Level(0x0a, options);
883
+ if (v === null)
884
+ return null;
885
+ return { raw: Math.round(v * 255), normalized: v };
886
+ }
887
+ /** Set RF transmit power. Value 0.0–1.0. */
888
+ setRFPower(value) {
889
+ this.write0x14Level(0x0a, value);
890
+ }
891
+ /** Get microphone gain. Returns 0.0–1.0, or null on timeout. */
892
+ async getMicGain(options) {
893
+ const v = await this.read0x14Level(0x0f, options);
894
+ if (v === null)
895
+ return null;
896
+ return { raw: Math.round(v * 255), normalized: v };
897
+ }
898
+ /** Set microphone gain. Value 0.0–1.0. */
899
+ setMicGain(value) {
900
+ this.write0x14Level(0x0f, value);
901
+ }
902
+ /** Get noise blanker level. 0.0 = off, >0.0 = on with strength. */
903
+ async getNBLevel(options) {
904
+ const v = await this.read0x14Level(0x12, options);
905
+ if (v === null)
906
+ return null;
907
+ return { raw: Math.round(v * 255), normalized: v };
908
+ }
909
+ /** Set noise blanker level. Value 0.0 (off) – 1.0. */
910
+ setNBLevel(value) {
911
+ this.write0x14Level(0x12, value);
912
+ }
913
+ /** Get noise reduction level. 0.0 = off, >0.0 = on with strength. */
914
+ async getNRLevel(options) {
915
+ const v = await this.read0x14Level(0x13, options);
916
+ if (v === null)
917
+ return null;
918
+ return { raw: Math.round(v * 255), normalized: v };
919
+ }
920
+ /** Set noise reduction level. Value 0.0 (off) – 1.0. */
921
+ setNRLevel(value) {
922
+ this.write0x14Level(0x13, value);
923
+ }
855
924
  /**
856
925
  * Read squelch status (noise/signal gate state)
857
926
  * @param options - Query options (timeout in ms, default 3000)
@@ -1488,6 +1557,50 @@ class IcomControl {
1488
1557
  return false;
1489
1558
  return true;
1490
1559
  }
1560
+ // Strict 0x14 data reply matcher: FE FE [ctr|00] [rig] 0x14 [sub] [bcd_hi] [bcd_lo] FD
1561
+ static is0x14DataReply(frame, subcmd, ctrAddr, rigAddr) {
1562
+ if (!(frame && frame.length >= 9))
1563
+ return false;
1564
+ if (frame[0] !== 0xfe || frame[1] !== 0xfe)
1565
+ return false;
1566
+ const addrCtrOk = frame[2] === (ctrAddr & 0xff) || frame[2] === 0x00;
1567
+ const addrRigOk = frame[3] === (rigAddr & 0xff);
1568
+ if (!addrCtrOk || !addrRigOk)
1569
+ return false;
1570
+ if (frame[4] !== 0x14)
1571
+ return false;
1572
+ if (frame[5] !== (subcmd & 0xff))
1573
+ return false;
1574
+ if (frame[frame.length - 1] !== 0xfd)
1575
+ return false;
1576
+ return true;
1577
+ }
1578
+ /**
1579
+ * Read a 0x14 level value from the radio.
1580
+ * Returns normalized value 0.0-1.0, or null on timeout/error.
1581
+ */
1582
+ async read0x14Level(subcmd, options) {
1583
+ const timeoutMs = options?.timeout ?? 3000;
1584
+ const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1585
+ const rigAddr = this.civ.civAddress & 0xff;
1586
+ const req = IcomRigCommands_1.IcomRigCommands.get0x14Level(ctrAddr, rigAddr, subcmd);
1587
+ const resp = await this.waitForCivFrame((frame) => IcomControl.is0x14DataReply(frame, subcmd, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
1588
+ if (!resp || resp.length < 9)
1589
+ return null;
1590
+ const raw = (0, bcd_1.parseTwoByteBcd)(resp.subarray(6, 8));
1591
+ return raw / 255;
1592
+ }
1593
+ /**
1594
+ * Write a 0x14 level value to the radio.
1595
+ * @param value - Normalized value 0.0-1.0
1596
+ */
1597
+ write0x14Level(subcmd, value) {
1598
+ const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1599
+ const rigAddr = this.civ.civAddress & 0xff;
1600
+ const raw = Math.max(0, Math.min(255, Math.round(value * 255)));
1601
+ const bcd = (0, bcd_1.intToTwoByteBcd)(raw);
1602
+ this.sendCiv(IcomRigCommands_1.IcomRigCommands.set0x14Level(ctrAddr, rigAddr, subcmd, bcd[0], bcd[1]));
1603
+ }
1491
1604
  // Start meter polling like Java (every 500ms when PTT is on)
1492
1605
  startMeterPolling() {
1493
1606
  this.stopMeterPolling();
@@ -21,6 +21,17 @@ export declare const IcomRigCommands: {
21
21
  getCompLevel(ctrAddr: number, rigAddr: number): Buffer;
22
22
  getVoltage(ctrAddr: number, rigAddr: number): Buffer;
23
23
  getCurrent(ctrAddr: number, rigAddr: number): Buffer;
24
+ /**
25
+ * Build a 0x14 level read query.
26
+ * Send this to request the current value; the radio responds with the same
27
+ * command byte + subcmd + 2-byte BCD data.
28
+ */
29
+ get0x14Level(ctrAddr: number, rigAddr: number, subcmd: number): Buffer;
30
+ /**
31
+ * Build a 0x14 level write command.
32
+ * @param rawValue - Integer 0-255 (use intToTwoByteBcd to encode)
33
+ */
34
+ set0x14Level(ctrAddr: number, rigAddr: number, subcmd: number, bcdHi: number, bcdLo: number): Buffer;
24
35
  getTunerStatus(ctrAddr: number, rigAddr: number): Buffer;
25
36
  setTunerEnabled(ctrAddr: number, rigAddr: number, on: boolean): Buffer;
26
37
  startManualTune(ctrAddr: number, rigAddr: number): Buffer;
@@ -99,6 +99,26 @@ exports.IcomRigCommands = {
99
99
  return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x15, 0x16, 0xfd]);
100
100
  },
101
101
  // =====================
102
+ // 0x14 Level Commands (read/write)
103
+ // =====================
104
+ /**
105
+ * Build a 0x14 level read query.
106
+ * Send this to request the current value; the radio responds with the same
107
+ * command byte + subcmd + 2-byte BCD data.
108
+ */
109
+ get0x14Level(ctrAddr, rigAddr, subcmd) {
110
+ // FE FE [rig] [ctr] 0x14 [subcmd] FD
111
+ return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x14, subcmd & 0xff, 0xfd]);
112
+ },
113
+ /**
114
+ * Build a 0x14 level write command.
115
+ * @param rawValue - Integer 0-255 (use intToTwoByteBcd to encode)
116
+ */
117
+ set0x14Level(ctrAddr, rigAddr, subcmd, bcdHi, bcdLo) {
118
+ // FE FE [rig] [ctr] 0x14 [subcmd] [bcd_hi] [bcd_lo] FD
119
+ return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x14, subcmd & 0xff, bcdHi & 0xff, bcdLo & 0xff, 0xfd]);
120
+ },
121
+ // =====================
102
122
  // Antenna Tuner (ATU)
103
123
  // =====================
104
124
  getTunerStatus(ctrAddr, rigAddr) {
package/dist/types.d.ts CHANGED
@@ -96,6 +96,16 @@ export interface MeterReading {
96
96
  */
97
97
  success: boolean;
98
98
  }
99
+ /**
100
+ * Result of a 0x14 level read operation.
101
+ * Covers AF Gain, SQL Level, RF Power, MIC Gain, NB Level, NR Level.
102
+ */
103
+ export interface LevelReading {
104
+ /** Raw BCD-decoded integer value (0–255) */
105
+ raw: number;
106
+ /** Normalized value in range 0.0–1.0 */
107
+ normalized: number;
108
+ }
99
109
  /**
100
110
  * SWR (Standing Wave Ratio) meter reading
101
111
  * Represents antenna impedance matching quality
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icom-wlan-node",
3
- "version": "0.2.7",
3
+ "version": "0.3.0",
4
4
  "description": "Icom WLAN (CI‑V, audio) protocol implementation for Node.js/TypeScript.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,7 +9,8 @@
9
9
  "clean": "rimraf dist",
10
10
  "test": "jest",
11
11
  "prepublishOnly": "npm run build",
12
- "lint": "eslint ."
12
+ "lint": "eslint .",
13
+ "diagnose": "tsx scripts/diagnose.ts"
13
14
  },
14
15
  "keywords": ["icom", "civ", "cat", "udp", "hamradio", "wlan", "audio"],
15
16
  "author": "boybook",
@@ -22,10 +23,12 @@
22
23
  "devDependencies": {
23
24
  "@types/jest": "^29.5.12",
24
25
  "@types/node": "^20.10.6",
26
+ "commander": "^12.0.0",
25
27
  "eslint": "^8.57.0",
26
28
  "jest": "^29.7.0",
27
29
  "rimraf": "^5.0.5",
28
30
  "ts-jest": "^29.1.1",
31
+ "tsx": "^4.7.0",
29
32
  "typescript": "^5.4.5"
30
33
  }
31
34
  }
package/dist/demo.d.ts DELETED
@@ -1,12 +0,0 @@
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 DELETED
@@ -1,329 +0,0 @@
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
- });