icom-wlan-node 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -106,37 +106,190 @@ await rig.setPtt(false);
106
106
  - `civFrame(Buffer)` — one complete CI‑V frame (FE FE ... FD)
107
107
  - `audio({ pcm16: Buffer })` — audio frames
108
108
  - `error(Error)` — UDP errors
109
+ - `connectionLost(ConnectionLostInfo)` — session timeout detected
110
+ - `connectionRestored(ConnectionRestoredInfo)` — reconnected successfully
111
+ - `reconnectAttempting(ReconnectAttemptInfo)` — reconnect attempt started
112
+ - `reconnectFailed(ReconnectFailedInfo)` — reconnect attempt failed
109
113
  - Methods
110
- - `connect()` / `disconnect()` — connects control + CIV + audio sub‑sessions; resolves when all ready
111
- - `sendCiv(buf: Buffer)` — send a raw CI‑V frame
112
- - `setPtt(on: boolean)` — key/unkey; also manages TX meter polling and audio tailing
113
- - `sendAudioFloat32(samples: Float32Array, addLeadingBuffer?: boolean)` / `sendAudioPcm16(samples: Int16Array)`
114
+ - **Connection**: `connect()` / `disconnect()` — connects control + CIV + audio sub‑sessions; resolves when all ready
115
+ - **Raw CI‑V**: `sendCiv(buf: Buffer)` — send a raw CI‑V frame
116
+ - **Audio TX**: `setPtt(on: boolean)`, `sendAudioFloat32()`, `sendAudioPcm16()`
117
+ - **Rig Control**: `setFrequency()`, `setMode()`, `setConnectorDataMode()`, `setConnectorWLanLevel()`
118
+ - **Rig Query**: `readOperatingFrequency()`, `readOperatingMode()`, `readTransmitFrequency()`, `readTransceiverState()`, `readBandEdges()`
119
+ - **Meters (RX)**: `readSquelchStatus()`, `readAudioSquelch()`, `readOvfStatus()`, `getLevelMeter()`
120
+ - **Meters (TX)**: `readSWR()`, `readALC()`, `readPowerLevel()`, `readCompLevel()`
121
+ - **Power Supply**: `readVoltage()`, `readCurrent()`
122
+ - **Audio Config**: `getConnectorWLanLevel()`
123
+ - **Connection Monitoring**: `getConnectionPhase()`, `getConnectionMetrics()`, `getConnectionState()`, `isAnySessionDisconnected()`, `configureMonitoring()`
124
+
125
+ ### Connection Management & Auto-Reconnect
126
+
127
+ The library features a robust state machine for connection lifecycle management with automatic reconnection support.
128
+
129
+ #### Connection State Machine
130
+
131
+ ```ts
132
+ ConnectionPhase: IDLE → CONNECTING → CONNECTED → DISCONNECTING
133
+ ↓ ↓
134
+ RECONNECTING ←────────────┘
135
+ ```
136
+
137
+ #### Basic Usage
138
+
139
+ ```ts
140
+ // Connect (idempotent - safe to call multiple times)
141
+ await rig.connect();
142
+
143
+ // Query connection phase
144
+ const phase = rig.getConnectionPhase(); // 'IDLE' | 'CONNECTING' | 'CONNECTED' | ...
145
+
146
+ // Get detailed metrics
147
+ const metrics = rig.getConnectionMetrics();
148
+ console.log(metrics.phase); // Current phase
149
+ console.log(metrics.uptime); // Milliseconds since connected
150
+ console.log(metrics.sessions); // Per-session states {control, civ, audio}
151
+
152
+ // Disconnect (also idempotent)
153
+ await rig.disconnect();
154
+ ```
155
+
156
+ #### Connection Monitoring Events
157
+
158
+ ```ts
159
+ // Connection lost (any session timeout)
160
+ rig.events.on('connectionLost', (info) => {
161
+ console.error(`Lost: ${info.sessionType}, idle: ${info.timeSinceLastData}ms`);
162
+ });
163
+
164
+ // Connection restored after reconnect
165
+ rig.events.on('connectionRestored', (info) => {
166
+ console.log(`Restored after ${info.downtime}ms downtime`);
167
+ });
168
+
169
+ // Reconnect attempt started
170
+ rig.events.on('reconnectAttempting', (info) => {
171
+ console.log(`Reconnect attempt #${info.attemptNumber}, delay: ${info.delay}ms`);
172
+ });
173
+
174
+ // Reconnect attempt failed
175
+ rig.events.on('reconnectFailed', (info) => {
176
+ console.error(`Attempt #${info.attemptNumber} failed: ${info.error}`);
177
+ if (!info.willRetry) console.error('Giving up - max retries reached');
178
+ });
179
+ ```
180
+
181
+ #### Auto-Reconnect Configuration
182
+
183
+ ```ts
184
+ rig.configureMonitoring({
185
+ timeout: 8000, // Session timeout: 8s (default: 5s)
186
+ checkInterval: 1000, // Check every 1s (default: 1s)
187
+ autoReconnect: true, // Enable auto-reconnect (default: false)
188
+ maxReconnectAttempts: 10, // Max retries (default: undefined = infinite)
189
+ reconnectBaseDelay: 2000, // Base delay: 2s (default: 2s)
190
+ reconnectMaxDelay: 30000 // Max delay: 30s (default: 30s, uses exponential backoff)
191
+ });
192
+ ```
193
+
194
+ **Exponential Backoff**: Delays are `baseDelay × 2^(attempt-1)`, capped at `maxDelay`.
195
+ Example: 2s → 4s → 8s → 16s → 30s (capped) → 30s ...
196
+
197
+ #### Error Handling
198
+
199
+ **Common Errors**:
200
+
201
+ ```ts
202
+ try {
203
+ await rig.connect();
204
+ } catch (err) {
205
+ if (err.message.includes('timeout')) {
206
+ // Connection timeout (no response from radio)
207
+ } else if (err.message.includes('Login failed')) {
208
+ // Authentication error (check userName/password)
209
+ } else if (err.message.includes('Radio reported connected=false')) {
210
+ // Radio rejected connection (may be busy with another client)
211
+ } else if (err.message.includes('Cannot connect while disconnecting')) {
212
+ // Invalid state transition (wait for disconnect to complete)
213
+ }
214
+ }
215
+
216
+ // Listen for UDP errors
217
+ rig.events.on('error', (err) => {
218
+ console.error('UDP error:', err.message);
219
+ // Network issues, invalid packets, etc.
220
+ });
221
+ ```
222
+
223
+ **Connection States to Handle**:
224
+ - **CONNECTING**: Wait or show "connecting..." UI
225
+ - **CONNECTED**: Normal operation
226
+ - **RECONNECTING**: Show "reconnecting..." UI, disable TX
227
+ - **DISCONNECTING**: Cleanup in progress
228
+ - **IDLE**: Not connected
114
229
 
115
230
  ### High‑Level API
116
231
 
117
232
  The library exposes common CI‑V operations as friendly methods. Addresses are handled internally (`ctrAddr=0xe0`, `rigAddr` discovered via capabilities).
118
233
 
119
- - `setFrequency(hz: number)`
120
- - `setMode(mode: IcomMode | number, { dataMode?: boolean })`
121
- - `setPtt(on: boolean)`
234
+ #### Rig Control
235
+
236
+ - `setFrequency(hz: number)` — Set operating frequency in Hz
237
+ - `setMode(mode: IcomMode | number, options?: { dataMode?: boolean })` — Set mode (supports string or numeric code)
238
+ - `setPtt(on: boolean)` — Key/unkey transmitter
239
+
240
+ **Supported Modes** (IcomMode string constants):
241
+ - `'LSB'`, `'USB'`, `'AM'`, `'CW'`, `'RTTY'`, `'FM'`, `'WFM'`, `'CW_R'`, `'RTTY_R'`, `'DV'`
242
+ - Or use numeric codes: `0x00` (LSB), `0x01` (USB), `0x02` (AM), etc.
243
+
244
+ #### Rig Query
245
+
122
246
  - `readOperatingFrequency(options?: QueryOptions) => Promise<number|null>`
123
247
  - `readOperatingMode(options?: QueryOptions) => Promise<{ mode: number; filter?: number; modeName?: string; filterName?: string } | null>`
124
248
  - `readTransmitFrequency(options?: QueryOptions) => Promise<number|null>`
125
249
  - `readTransceiverState(options?: QueryOptions) => Promise<'TX' | 'RX' | 'UNKNOWN' | null>`
126
250
  - `readBandEdges(options?: QueryOptions) => Promise<Buffer|null>`
127
- - `readSWR(options?: QueryOptions) => Promise<{ raw: number; swr: number; alert: boolean } | null>`
128
- - `readALC(options?: QueryOptions) => Promise<{ raw: number; percent: number; alert: boolean } | null>`
129
- - `getConnectorWLanLevel(options?: QueryOptions) => Promise<{ raw: number; percent: number } | null>`
130
- - `getLevelMeter(options?: QueryOptions) => Promise<{ raw: number; percent: number } | null>`
131
- - `setConnectorWLanLevel(level: number)`
132
- - `setConnectorDataMode(mode: ConnectorDataMode | number)`
133
251
 
134
- Examples:
252
+ #### Meters & Levels
253
+
254
+ **Reception Meters** (available anytime):
255
+ - `readSquelchStatus(options?: QueryOptions) => Promise<{ raw: number; isOpen: boolean } | null>` — Squelch gate state (CI-V 0x15/0x01)
256
+ - `readAudioSquelch(options?: QueryOptions) => Promise<{ raw: number; isOpen: boolean } | null>` — Audio squelch state (CI-V 0x15/0x05)
257
+ - `readOvfStatus(options?: QueryOptions) => Promise<{ raw: number; isOverload: boolean } | null>` — ADC overload detection (CI-V 0x15/0x07)
258
+ - `getLevelMeter(options?: QueryOptions) => Promise<{ raw: number; percent: number } | null>` — S-meter level (CI-V 0x15/0x02)
259
+
260
+ **Transmission Meters** (require PTT on):
261
+ - `readSWR(options?: QueryOptions) => Promise<{ raw: number; swr: number; alert: boolean } | null>` — SWR meter (CI-V 0x15/0x12)
262
+ - `readALC(options?: QueryOptions) => Promise<{ raw: number; percent: number; alert: boolean } | null>` — ALC meter (CI-V 0x15/0x13)
263
+ - `readPowerLevel(options?: QueryOptions) => Promise<{ raw: number; percent: number } | null>` — Output power level (CI-V 0x15/0x11)
264
+ - `readCompLevel(options?: QueryOptions) => Promise<{ raw: number; percent: number } | null>` — Voice compression level (CI-V 0x15/0x14)
265
+
266
+ **Power Supply Monitoring**:
267
+ - `readVoltage(options?: QueryOptions) => Promise<{ raw: number; volts: number } | null>` — Supply voltage (CI-V 0x15/0x15)
268
+ - `readCurrent(options?: QueryOptions) => Promise<{ raw: number; amps: number } | null>` — Supply current draw (CI-V 0x15/0x16)
269
+
270
+ **Audio Configuration**:
271
+ - `getConnectorWLanLevel(options?: QueryOptions) => Promise<{ raw: number; percent: number } | null>` — Get WLAN audio level (CI-V 0x1A/0x05/0x01/0x17)
272
+ - `setConnectorWLanLevel(level: number)` — Set WLAN audio level (0-255)
273
+
274
+ #### Connector Settings
275
+
276
+ - `setConnectorDataMode(mode: ConnectorDataMode | number)` — Set data routing mode (supports string or numeric)
277
+
278
+ **Supported Connector Modes** (ConnectorDataMode string constants):
279
+ - `'MIC'` (0x00), `'ACC'` (0x01), `'USB'` (0x02), `'WLAN'` (0x03)
280
+
281
+ #### Examples
135
282
 
136
283
  ```ts
137
- // Set frequency and mode (USB-D)
284
+ // Set frequency and mode using string constants
138
285
  await rig.setFrequency(14074000);
139
- await rig.setMode(0x01, { dataMode: true }); // USB=0x01, data mode
286
+ await rig.setMode('USB', { dataMode: true }); // USB-D for FT8
287
+
288
+ // Or use numeric codes
289
+ await rig.setMode(0x01, { dataMode: true }); // USB=0x01
290
+
291
+ // Set LSB mode
292
+ await rig.setMode('LSB');
140
293
 
141
294
  // Query current frequency (Hz)
142
295
  const hz = await rig.readOperatingFrequency({ timeout: 3000 });
@@ -152,17 +305,75 @@ for (let n = 0; n < 10; n++) {
152
305
  }
153
306
  await rig.setPtt(false);
154
307
 
155
- // Read meters and connector settings
308
+ // Read reception meters (available anytime)
309
+ const squelch = await rig.readSquelchStatus({ timeout: 2000 });
310
+ if (squelch) {
311
+ console.log(`Squelch: ${squelch.isOpen ? 'OPEN' : 'CLOSED'}`);
312
+ }
313
+
314
+ const audioSq = await rig.readAudioSquelch({ timeout: 2000 });
315
+ if (audioSq) {
316
+ console.log(`Audio Squelch: ${audioSq.isOpen ? 'OPEN' : 'CLOSED'}`);
317
+ }
318
+
319
+ const ovf = await rig.readOvfStatus({ timeout: 2000 });
320
+ if (ovf) {
321
+ console.log(`ADC: ${ovf.isOverload ? '⚠️ OVERLOAD' : '✓ OK'}`);
322
+ }
323
+
324
+ const sMeter = await rig.getLevelMeter({ timeout: 2000 });
325
+ if (sMeter) {
326
+ console.log(`S-Meter: ${sMeter.percent.toFixed(1)}%`);
327
+ }
328
+
329
+ // Read power supply monitoring
330
+ const voltage = await rig.readVoltage({ timeout: 2000 });
331
+ if (voltage) {
332
+ console.log(`Voltage: ${voltage.volts.toFixed(2)}V`);
333
+ }
334
+
335
+ const current = await rig.readCurrent({ timeout: 2000 });
336
+ if (current) {
337
+ console.log(`Current: ${current.amps.toFixed(2)}A`);
338
+ }
339
+
340
+ // Read transmission meters (requires PTT on)
341
+ await rig.setPtt(true);
342
+ await new Promise(r => setTimeout(r, 200)); // Wait for meters to stabilize
343
+
156
344
  const swr = await rig.readSWR({ timeout: 2000 });
345
+ if (swr) {
346
+ console.log(`SWR: ${swr.swr.toFixed(2)} ${swr.alert ? '⚠️ HIGH' : '✓'}`);
347
+ }
348
+
157
349
  const alc = await rig.readALC({ timeout: 2000 });
158
- const wlanLevel = await rig.getConnectorWLanLevel({ timeout: 2000 });
159
- const level = await rig.getLevelMeter({ timeout: 1500 });
160
- await rig.setConnectorWLanLevel(0x0120);
161
- await rig.setConnectorDataMode(0x01); // e.g., DATA
350
+ if (alc) {
351
+ console.log(`ALC: ${alc.percent.toFixed(1)}% ${alc.alert ? '⚠️ HIGH' : '✓'}`);
352
+ }
353
+
354
+ const power = await rig.readPowerLevel({ timeout: 2000 });
355
+ if (power) {
356
+ console.log(`Power: ${power.percent.toFixed(1)}%`);
357
+ }
358
+
359
+ const comp = await rig.readCompLevel({ timeout: 2000 });
360
+ if (comp) {
361
+ console.log(`COMP: ${comp.percent.toFixed(1)}%`);
362
+ }
162
363
 
163
- if (level) {
164
- console.log(`Generic Level Meter: raw=${level.raw} (${level.percent.toFixed(1)}%)`);
364
+ await rig.setPtt(false);
365
+
366
+ // Configure WLAN connector
367
+ const wlanLevel = await rig.getConnectorWLanLevel({ timeout: 2000 });
368
+ if (wlanLevel) {
369
+ console.log(`WLAN Level: ${wlanLevel.percent.toFixed(1)}%`);
165
370
  }
371
+
372
+ // Set connector to WLAN mode using string constant
373
+ await rig.setConnectorDataMode('WLAN');
374
+ // Or numeric: await rig.setConnectorDataMode(0x03);
375
+
376
+ await rig.setConnectorWLanLevel(120); // Set WLAN audio level
166
377
  ```
167
378
 
168
379
  ## Design Notes
@@ -1,4 +1,5 @@
1
1
  import { UdpClient } from '../transport/UdpClient';
2
+ import { SessionType } from '../types';
2
3
  export interface SessionOptions {
3
4
  ip: string;
4
5
  port: number;
@@ -19,10 +20,12 @@ export declare class Session {
19
20
  lastSentTime: number;
20
21
  lastReceivedTime: number;
21
22
  private address;
23
+ private handlers;
22
24
  private txHistory;
23
25
  private areYouThereTimer?;
24
26
  private pingTimer?;
25
27
  private destroyed;
28
+ sessionType?: SessionType;
26
29
  constructor(address: SessionOptions, handlers: SessionHandlers);
27
30
  open(): void;
28
31
  close(): void;
@@ -40,4 +43,10 @@ export declare class Session {
40
43
  stopPing(): void;
41
44
  stopTimers(): void;
42
45
  setRemote(ip: string, port: number): void;
46
+ /**
47
+ * Reset session state to initial values
48
+ * Call this before reconnecting to ensure clean state
49
+ * (especially important after radio restart)
50
+ */
51
+ resetState(): void;
43
52
  }
@@ -19,21 +19,24 @@ class Session {
19
19
  this.txHistory = new Map();
20
20
  this.destroyed = false;
21
21
  this.address = address;
22
+ this.handlers = handlers;
22
23
  this.udp.on('data', (rinfo, data) => {
23
24
  if (this.destroyed)
24
25
  return;
25
26
  this.lastReceivedTime = Date.now();
26
27
  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'}`);
28
+ const type = 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=${type >= 0 ? '0x' + type.toString(16) : 'n/a'}`);
30
30
  }
31
31
  catch { }
32
32
  handlers.onData(data);
33
33
  });
34
34
  this.udp.on('error', handlers.onSendError);
35
35
  }
36
- open() { this.udp.open(); }
36
+ open() {
37
+ this.destroyed = false; // Reset destroyed flag to allow sending data after reconnection
38
+ this.udp.open();
39
+ }
37
40
  close() {
38
41
  this.stopTimers();
39
42
  this.destroyed = true;
@@ -78,8 +81,9 @@ class Session {
78
81
  } }
79
82
  startAreYouThere() {
80
83
  this.stopAreYouThere();
84
+ (0, debug_1.dbg)(`Starting AreYouThere timer for ${this.address.ip}:${this.address.port}`);
81
85
  this.areYouThereTimer = setInterval(() => {
82
- (0, debug_1.dbgV)(`AYT -> ${this.address.ip}:${this.address.port} localId=${this.localId}`);
86
+ (0, debug_1.dbg)(`AYT -> ${this.address.ip}:${this.address.port} localId=${this.localId}`);
83
87
  this.sendUntracked(IcomPackets_1.ControlPacket.toBytes(IcomPackets_1.Cmd.ARE_YOU_THERE, 0, this.localId, 0));
84
88
  }, 500);
85
89
  }
@@ -101,5 +105,30 @@ class Session {
101
105
  setRemote(ip, port) {
102
106
  this.address = { ip, port };
103
107
  }
108
+ /**
109
+ * Reset session state to initial values
110
+ * Call this before reconnecting to ensure clean state
111
+ * (especially important after radio restart)
112
+ */
113
+ resetState() {
114
+ // Reset destroyed flag to ensure session is usable after state reset
115
+ this.destroyed = false;
116
+ // Generate new IDs
117
+ this.localId = Date.now() >>> 0;
118
+ this.remoteId = 0;
119
+ // Reset sequence numbers
120
+ this.trackedSeq = 1;
121
+ this.pingSeq = 0;
122
+ this.innerSeq = 0x30;
123
+ // Reset tokens
124
+ this.rigToken = 0;
125
+ this.localToken = (Date.now() & 0xffff) >>> 0;
126
+ // Reset timestamps
127
+ this.lastSentTime = Date.now();
128
+ this.lastReceivedTime = Date.now();
129
+ // Clear history
130
+ this.txHistory.clear();
131
+ (0, debug_1.dbgV)(`Session state reset: localId=${this.localId}, localToken=${this.localToken}`);
132
+ }
104
133
  }
105
134
  exports.Session = Session;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from './types';
2
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';
3
+ export { MODE_MAP, CONNECTOR_MODE_MAP, DEFAULT_CONTROLLER_ADDR, METER_THRESHOLDS, METER_CALIBRATION, getModeCode, getConnectorModeCode, getModeString, getConnectorModeString, getFilterString, rawToPowerPercent, rawToVoltage, rawToCurrent } from './rig/IcomConstants';
4
4
  export { parseTwoByteBcd, intToTwoByteBcd } from './utils/bcd';
5
5
  export { IcomRigCommands } from './rig/IcomRigCommands';
6
6
  export { AUDIO_RATE } from './rig/IcomAudio';
package/dist/index.js CHANGED
@@ -14,8 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
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
17
+ exports.AUDIO_RATE = exports.IcomRigCommands = exports.intToTwoByteBcd = exports.parseTwoByteBcd = exports.rawToCurrent = exports.rawToVoltage = exports.rawToPowerPercent = exports.getFilterString = exports.getConnectorModeString = exports.getModeString = exports.getConnectorModeCode = exports.getModeCode = exports.METER_CALIBRATION = exports.METER_THRESHOLDS = exports.DEFAULT_CONTROLLER_ADDR = exports.CONNECTOR_MODE_MAP = exports.MODE_MAP = exports.IcomControl = void 0;
18
+ // Export types (includes ConnectionPhase, ConnectionMetrics, etc.)
19
19
  __exportStar(require("./types"), exports);
20
20
  // Export main class
21
21
  var IcomControl_1 = require("./rig/IcomControl");
@@ -26,11 +26,15 @@ Object.defineProperty(exports, "MODE_MAP", { enumerable: true, get: function ()
26
26
  Object.defineProperty(exports, "CONNECTOR_MODE_MAP", { enumerable: true, get: function () { return IcomConstants_1.CONNECTOR_MODE_MAP; } });
27
27
  Object.defineProperty(exports, "DEFAULT_CONTROLLER_ADDR", { enumerable: true, get: function () { return IcomConstants_1.DEFAULT_CONTROLLER_ADDR; } });
28
28
  Object.defineProperty(exports, "METER_THRESHOLDS", { enumerable: true, get: function () { return IcomConstants_1.METER_THRESHOLDS; } });
29
+ Object.defineProperty(exports, "METER_CALIBRATION", { enumerable: true, get: function () { return IcomConstants_1.METER_CALIBRATION; } });
29
30
  Object.defineProperty(exports, "getModeCode", { enumerable: true, get: function () { return IcomConstants_1.getModeCode; } });
30
31
  Object.defineProperty(exports, "getConnectorModeCode", { enumerable: true, get: function () { return IcomConstants_1.getConnectorModeCode; } });
31
32
  Object.defineProperty(exports, "getModeString", { enumerable: true, get: function () { return IcomConstants_1.getModeString; } });
32
33
  Object.defineProperty(exports, "getConnectorModeString", { enumerable: true, get: function () { return IcomConstants_1.getConnectorModeString; } });
33
34
  Object.defineProperty(exports, "getFilterString", { enumerable: true, get: function () { return IcomConstants_1.getFilterString; } });
35
+ Object.defineProperty(exports, "rawToPowerPercent", { enumerable: true, get: function () { return IcomConstants_1.rawToPowerPercent; } });
36
+ Object.defineProperty(exports, "rawToVoltage", { enumerable: true, get: function () { return IcomConstants_1.rawToVoltage; } });
37
+ Object.defineProperty(exports, "rawToCurrent", { enumerable: true, get: function () { return IcomConstants_1.rawToCurrent; } });
34
38
  // Export BCD utilities
35
39
  var bcd_1 = require("./utils/bcd");
36
40
  Object.defineProperty(exports, "parseTwoByteBcd", { enumerable: true, get: function () { return bcd_1.parseTwoByteBcd; } });
@@ -12,9 +12,14 @@ class IcomCiv {
12
12
  }
13
13
  start() {
14
14
  this.stop();
15
- // keep-alive open/close
15
+ // CIV session watchdog: Keep-alive mechanism to maintain CIV connection
16
+ // Matches FT8CN's startCivDataTimer() implementation:
17
+ // - Checks every 500ms if CIV session is receiving data
18
+ // - If no data received for >2000ms, sends OpenClose(true) to re-establish connection
19
+ // - This prevents silent disconnection where radio stops responding without error
16
20
  this.openTimer = setInterval(() => {
17
21
  if (Date.now() - this.sess.lastReceivedTime > 2000) {
22
+ (0, debug_1.dbg)('CIV watchdog: No data for >2s, sending OpenClose to keep alive');
18
23
  this.sendOpenClose(true);
19
24
  }
20
25
  }, 500);
@@ -82,3 +82,75 @@ export declare const METER_THRESHOLDS: {
82
82
  * Matches FT8CN's IComPacketTypes.METER_TIMER_PERIOD_MS
83
83
  */
84
84
  export declare const METER_TIMER_PERIOD_MS = 500;
85
+ /**
86
+ * Meter calibration constants for physical unit conversion
87
+ * Based on IC-705 official CI-V reference manual
88
+ */
89
+ export declare const METER_CALIBRATION: {
90
+ /**
91
+ * Power level (CI-V 0x15/0x11) calibration points
92
+ * Used for converting raw BCD to percentage
93
+ */
94
+ readonly POWER: {
95
+ /** 50% power reference point */
96
+ readonly HALF: {
97
+ readonly raw: 323;
98
+ readonly percent: 50;
99
+ };
100
+ /** 100% power reference point */
101
+ readonly FULL: {
102
+ readonly raw: 531;
103
+ readonly percent: 100;
104
+ };
105
+ };
106
+ /**
107
+ * Voltage (CI-V 0x15/0x15) calibration points
108
+ * Used for converting raw BCD to volts
109
+ */
110
+ readonly VOLTAGE: {
111
+ /** Low voltage reference: 5V */
112
+ readonly LOW: {
113
+ readonly raw: 117;
114
+ readonly volts: 5;
115
+ };
116
+ /** High voltage reference: 16V */
117
+ readonly HIGH: {
118
+ readonly raw: 577;
119
+ readonly volts: 16;
120
+ };
121
+ };
122
+ /**
123
+ * Current (CI-V 0x15/0x16) calibration points
124
+ * Used for converting raw BCD to amperes
125
+ */
126
+ readonly CURRENT: {
127
+ /** Low current reference: 2A */
128
+ readonly LOW: {
129
+ readonly raw: 289;
130
+ readonly amps: 2;
131
+ };
132
+ /** High current reference: 4A */
133
+ readonly HIGH: {
134
+ readonly raw: 577;
135
+ readonly amps: 4;
136
+ };
137
+ };
138
+ };
139
+ /**
140
+ * Convert raw power level to percentage
141
+ * @param raw - Raw BCD value (0-255)
142
+ * @returns Power percentage (0-100%)
143
+ */
144
+ export declare function rawToPowerPercent(raw: number): number;
145
+ /**
146
+ * Convert raw voltage reading to volts
147
+ * @param raw - Raw BCD value (0-255)
148
+ * @returns Voltage in volts
149
+ */
150
+ export declare function rawToVoltage(raw: number): number;
151
+ /**
152
+ * Convert raw current reading to amperes
153
+ * @param raw - Raw BCD value (0-255)
154
+ * @returns Current in amperes
155
+ */
156
+ export declare function rawToCurrent(raw: number): number;
@@ -4,12 +4,15 @@
4
4
  * Based on FT8CN's IcomRigConstant.java
5
5
  */
6
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;
7
+ exports.METER_CALIBRATION = exports.METER_TIMER_PERIOD_MS = exports.METER_THRESHOLDS = exports.DEFAULT_CONTROLLER_ADDR = exports.CONNECTOR_MODE_MAP = exports.MODE_MAP = void 0;
8
8
  exports.getModeCode = getModeCode;
9
9
  exports.getConnectorModeCode = getConnectorModeCode;
10
10
  exports.getModeString = getModeString;
11
11
  exports.getConnectorModeString = getConnectorModeString;
12
12
  exports.getFilterString = getFilterString;
13
+ exports.rawToPowerPercent = rawToPowerPercent;
14
+ exports.rawToVoltage = rawToVoltage;
15
+ exports.rawToCurrent = rawToCurrent;
13
16
  /**
14
17
  * Operating modes supported by ICOM radios
15
18
  * LSB:0, USB:1, AM:2, CW:3, RTTY:4, FM:5, WFM:6, CW_R:7, RTTY_R:8, DV:17
@@ -110,3 +113,81 @@ exports.METER_THRESHOLDS = {
110
113
  * Matches FT8CN's IComPacketTypes.METER_TIMER_PERIOD_MS
111
114
  */
112
115
  exports.METER_TIMER_PERIOD_MS = 500;
116
+ /**
117
+ * Meter calibration constants for physical unit conversion
118
+ * Based on IC-705 official CI-V reference manual
119
+ */
120
+ exports.METER_CALIBRATION = {
121
+ /**
122
+ * Power level (CI-V 0x15/0x11) calibration points
123
+ * Used for converting raw BCD to percentage
124
+ */
125
+ POWER: {
126
+ /** 50% power reference point */
127
+ HALF: { raw: 0x0143, percent: 50 },
128
+ /** 100% power reference point */
129
+ FULL: { raw: 0x0213, percent: 100 }
130
+ },
131
+ /**
132
+ * Voltage (CI-V 0x15/0x15) calibration points
133
+ * Used for converting raw BCD to volts
134
+ */
135
+ VOLTAGE: {
136
+ /** Low voltage reference: 5V */
137
+ LOW: { raw: 0x0075, volts: 5.0 },
138
+ /** High voltage reference: 16V */
139
+ HIGH: { raw: 0x0241, volts: 16.0 }
140
+ },
141
+ /**
142
+ * Current (CI-V 0x15/0x16) calibration points
143
+ * Used for converting raw BCD to amperes
144
+ */
145
+ CURRENT: {
146
+ /** Low current reference: 2A */
147
+ LOW: { raw: 0x0121, amps: 2.0 },
148
+ /** High current reference: 4A */
149
+ HIGH: { raw: 0x0241, amps: 4.0 }
150
+ }
151
+ };
152
+ /**
153
+ * Linear interpolation helper for meter value conversion
154
+ * @param raw - Raw BCD value
155
+ * @param x1 - Lower calibration point (raw value)
156
+ * @param y1 - Lower calibration point (physical value)
157
+ * @param x2 - Upper calibration point (raw value)
158
+ * @param y2 - Upper calibration point (physical value)
159
+ * @returns Interpolated physical value
160
+ */
161
+ function linearInterpolate(raw, x1, y1, x2, y2) {
162
+ // Clamp to range
163
+ if (raw <= x1)
164
+ return y1;
165
+ if (raw >= x2)
166
+ return y2;
167
+ // Linear interpolation: y = y1 + (raw - x1) * (y2 - y1) / (x2 - x1)
168
+ return y1 + ((raw - x1) * (y2 - y1)) / (x2 - x1);
169
+ }
170
+ /**
171
+ * Convert raw power level to percentage
172
+ * @param raw - Raw BCD value (0-255)
173
+ * @returns Power percentage (0-100%)
174
+ */
175
+ function rawToPowerPercent(raw) {
176
+ return linearInterpolate(raw, exports.METER_CALIBRATION.POWER.HALF.raw, exports.METER_CALIBRATION.POWER.HALF.percent, exports.METER_CALIBRATION.POWER.FULL.raw, exports.METER_CALIBRATION.POWER.FULL.percent);
177
+ }
178
+ /**
179
+ * Convert raw voltage reading to volts
180
+ * @param raw - Raw BCD value (0-255)
181
+ * @returns Voltage in volts
182
+ */
183
+ function rawToVoltage(raw) {
184
+ return linearInterpolate(raw, exports.METER_CALIBRATION.VOLTAGE.LOW.raw, exports.METER_CALIBRATION.VOLTAGE.LOW.volts, exports.METER_CALIBRATION.VOLTAGE.HIGH.raw, exports.METER_CALIBRATION.VOLTAGE.HIGH.volts);
185
+ }
186
+ /**
187
+ * Convert raw current reading to amperes
188
+ * @param raw - Raw BCD value (0-255)
189
+ * @returns Current in amperes
190
+ */
191
+ function rawToCurrent(raw) {
192
+ return linearInterpolate(raw, exports.METER_CALIBRATION.CURRENT.LOW.raw, exports.METER_CALIBRATION.CURRENT.LOW.amps, exports.METER_CALIBRATION.CURRENT.HIGH.raw, exports.METER_CALIBRATION.CURRENT.HIGH.amps);
193
+ }