icom-wlan-node 0.2.5 → 0.2.7

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
@@ -117,6 +117,7 @@ await rig.setPtt(false);
117
117
  - **Audio TX**: `setPtt(on: boolean)`, `sendAudioFloat32()`, `sendAudioPcm16()`
118
118
  - **Rig Control**: `setFrequency()`, `setMode()`, `setConnectorDataMode()`, `setConnectorWLanLevel()`
119
119
  - **Rig Query**: `readOperatingFrequency()`, `readOperatingMode()`, `readTransmitFrequency()`, `readTransceiverState()`, `readBandEdges()`
120
+ - **Antenna Tuner**: `readTunerStatus()`, `setTunerEnabled()`, `startManualTune()`
120
121
  - **Meters (RX)**: `readSquelchStatus()`, `readAudioSquelch()`, `readOvfStatus()`, `getLevelMeter()`
121
122
  - **Meters (TX)**: `readSWR()`, `readALC()`, `readPowerLevel()`, `readCompLevel()`
122
123
  - **Power Supply**: `readVoltage()`, `readCurrent()`
@@ -262,7 +263,7 @@ The library exposes common CI‑V operations as friendly methods. Addresses are
262
263
  - `readSquelchStatus(options?: QueryOptions) => Promise<{ raw: number; isOpen: boolean } | null>` — Squelch gate state (CI-V 0x15/0x01)
263
264
  - `readAudioSquelch(options?: QueryOptions) => Promise<{ raw: number; isOpen: boolean } | null>` — Audio squelch state (CI-V 0x15/0x05)
264
265
  - `readOvfStatus(options?: QueryOptions) => Promise<{ raw: number; isOverload: boolean } | null>` — ADC overload detection (CI-V 0x15/0x07)
265
- - `getLevelMeter(options?: QueryOptions) => Promise<{ raw: number; percent: number } | null>` — S-meter level (CI-V 0x15/0x02)
266
+ - `getLevelMeter(options?: QueryOptions) => Promise<{ raw: number; percent: number; sUnits: number; dbAboveS9?: number; dBm: number; formatted: string } | null>` — S-meter (signal strength) with physical units (CI-V 0x15/0x02)
266
267
 
267
268
  **Transmission Meters** (require PTT on):
268
269
  - `readSWR(options?: QueryOptions) => Promise<{ raw: number; swr: number; alert: boolean } | null>` — SWR meter (CI-V 0x15/0x12)
@@ -330,7 +331,8 @@ if (ovf) {
330
331
 
331
332
  const sMeter = await rig.getLevelMeter({ timeout: 2000 });
332
333
  if (sMeter) {
333
- console.log(`S-Meter: ${sMeter.percent.toFixed(1)}%`);
334
+ console.log(`S-Meter: ${sMeter.formatted} (${sMeter.sUnits.toFixed(1)} S-units, ${sMeter.dBm.toFixed(1)} dBm)`);
335
+ // Example output: "S-Meter: S9+10dB (9.9 S-units, -63.1 dBm)"
334
336
  }
335
337
 
336
338
  // Read power supply monitoring
@@ -420,3 +422,29 @@ ICOM_IP=192.168.31.253 ICOM_PORT=50001 ICOM_USER=icom ICOM_PASS=icomicom npm tes
420
422
  ## License
421
423
 
422
424
  MIT
425
+ #### Antenna Tuner (ATU)
426
+
427
+ - `readTunerStatus(options?: QueryOptions) => Promise<{ raw: number; state: 'OFF'|'ON'|'TUNING' } | null>` — 读取天调状态(CI‑V 0x1A/0x00)
428
+ - `setTunerEnabled(enabled: boolean) => Promise<void>` — 开启/关闭内置天调(CI‑V 0x1A/0x01 00/01)
429
+ - `startManualTune() => Promise<void>` — 触发一次手动调谐(相当于 [TUNE] 键,CI‑V 0x1A/0x02/0x00)
430
+
431
+ 示例:
432
+
433
+ ```ts
434
+ // 读取天调状态
435
+ const atu = await rig.readTunerStatus({ timeout: 2000 });
436
+ if (atu) console.log('ATU:', atu.state); // OFF / ON / TUNING
437
+
438
+ // 启用内置天调
439
+ await rig.setTunerEnabled(true);
440
+
441
+ // 触发一次手动调谐
442
+ await rig.startManualTune();
443
+
444
+ // 可选:轮询状态直到结束
445
+ let status;
446
+ do {
447
+ await new Promise(r => setTimeout(r, 300));
448
+ status = await rig.readTunerStatus({ timeout: 1000 });
449
+ } while (status && status.state === 'TUNING');
450
+ ```
@@ -135,6 +135,28 @@ export declare const METER_CALIBRATION: {
135
135
  readonly amps: 4;
136
136
  };
137
137
  };
138
+ /**
139
+ * S-meter (CI-V 0x15/0x02) calibration points
140
+ * Per-model calibration for signal strength meter
141
+ */
142
+ readonly SMETER: {
143
+ /**
144
+ * IC-705 S-meter calibration (from official CI-V reference manual)
145
+ * - raw=0 → S0
146
+ * - raw=120 → S9
147
+ * - raw=241 → S9+60dB
148
+ */
149
+ readonly 'IC-705': {
150
+ /** S0 reference point */
151
+ readonly S0: 0;
152
+ /** S9 reference point */
153
+ readonly S9: 120;
154
+ /** S9+60dB reference point */
155
+ readonly S9_PLUS_60DB: 241;
156
+ /** HF standard: S9 ≈ -73dBm (used for dBm estimation) */
157
+ readonly HF_S9_DBM: -73;
158
+ };
159
+ };
138
160
  };
139
161
  /**
140
162
  * Convert raw power level to percentage
@@ -147,6 +147,29 @@ exports.METER_CALIBRATION = {
147
147
  LOW: { raw: 121, amps: 2.0 },
148
148
  /** High current reference: 4A (raw=241) */
149
149
  HIGH: { raw: 241, amps: 4.0 }
150
+ },
151
+ /**
152
+ * S-meter (CI-V 0x15/0x02) calibration points
153
+ * Per-model calibration for signal strength meter
154
+ */
155
+ SMETER: {
156
+ /**
157
+ * IC-705 S-meter calibration (from official CI-V reference manual)
158
+ * - raw=0 → S0
159
+ * - raw=120 → S9
160
+ * - raw=241 → S9+60dB
161
+ */
162
+ 'IC-705': {
163
+ /** S0 reference point */
164
+ S0: 0,
165
+ /** S9 reference point */
166
+ S9: 120,
167
+ /** S9+60dB reference point */
168
+ S9_PLUS_60DB: 241,
169
+ /** HF standard: S9 ≈ -73dBm (used for dBm estimation) */
170
+ HF_S9_DBM: -73
171
+ }
172
+ // Future: Add other ICOM models here
150
173
  }
151
174
  };
152
175
  /**
@@ -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 } 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 } from '../types';
2
2
  import { IcomCiv } from './IcomCiv';
3
3
  import { IcomAudio } from './IcomAudio';
4
4
  export declare class IcomControl {
@@ -160,8 +160,21 @@ export declare class IcomControl {
160
160
  */
161
161
  getConnectorWLanLevel(options?: QueryOptions): Promise<WlanLevelReading | null>;
162
162
  /**
163
- * Read generic level meter (CI-V 0x15/0x02), raw 0-255.
164
- * Many rigs return two bytes and the low byte is the level.
163
+ * Read S-meter (signal strength) level (CI-V 0x15/0x02)
164
+ * Returns complete reading with S-units, dB, and dBm conversion
165
+ *
166
+ * @param options - Query options (timeout)
167
+ * @returns S-meter reading with physical units, or null if timeout
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * const reading = await rig.getLevelMeter();
172
+ * if (reading) {
173
+ * console.log(reading.formatted); // "S9+10dB"
174
+ * console.log(reading.sUnits); // 9.99
175
+ * console.log(reading.dBm); // -63.08
176
+ * }
177
+ * ```
165
178
  */
166
179
  getLevelMeter(options?: QueryOptions): Promise<LevelMeterReading | null>;
167
180
  /**
@@ -177,6 +190,25 @@ export declare class IcomControl {
177
190
  * await rig.setConnectorDataMode('WLAN');
178
191
  */
179
192
  setConnectorDataMode(mode: ConnectorDataMode | number): Promise<void>;
193
+ /**
194
+ * ==============================
195
+ * Antenna Tuner (ATU) Operations
196
+ * ==============================
197
+ */
198
+ /**
199
+ * Read antenna tuner status (CI-V 0x1A/0x00)
200
+ * 00=OFF, 01=ON, 02=TUNING
201
+ */
202
+ readTunerStatus(options?: QueryOptions): Promise<TunerStatusReading | null>;
203
+ /**
204
+ * Enable or disable internal antenna tuner (CI-V 0x1A/0x01)
205
+ * @param enabled true to enable, false to disable
206
+ */
207
+ setTunerEnabled(enabled: boolean): Promise<void>;
208
+ /**
209
+ * Start a manual tuning cycle (same as [TUNE] key) (CI-V 0x1A/0x02/0x00)
210
+ */
211
+ startManualTune(): Promise<void>;
180
212
  /**
181
213
  * Read squelch status (noise/signal gate state)
182
214
  * @param options - Query options (timeout in ms, default 3000)
@@ -45,6 +45,7 @@ const IcomRigCommands_1 = require("./IcomRigCommands");
45
45
  const IcomConstants_1 = require("./IcomConstants");
46
46
  const bcd_1 = require("../utils/bcd");
47
47
  const errors_1 = require("../utils/errors");
48
+ const smeter_1 = require("../utils/smeter");
48
49
  class IcomControl {
49
50
  constructor(options) {
50
51
  this.ev = new events_1.EventEmitter();
@@ -756,8 +757,21 @@ class IcomControl {
756
757
  };
757
758
  }
758
759
  /**
759
- * Read generic level meter (CI-V 0x15/0x02), raw 0-255.
760
- * Many rigs return two bytes and the low byte is the level.
760
+ * Read S-meter (signal strength) level (CI-V 0x15/0x02)
761
+ * Returns complete reading with S-units, dB, and dBm conversion
762
+ *
763
+ * @param options - Query options (timeout)
764
+ * @returns S-meter reading with physical units, or null if timeout
765
+ *
766
+ * @example
767
+ * ```typescript
768
+ * const reading = await rig.getLevelMeter();
769
+ * if (reading) {
770
+ * console.log(reading.formatted); // "S9+10dB"
771
+ * console.log(reading.sUnits); // 9.99
772
+ * console.log(reading.dBm); // -63.08
773
+ * }
774
+ * ```
761
775
  */
762
776
  async getLevelMeter(options) {
763
777
  const timeoutMs = options?.timeout ?? 3000;
@@ -771,10 +785,9 @@ class IcomControl {
771
785
  if (data.length === 0)
772
786
  return null;
773
787
  const raw = data[data.length - 1] & 0xff; // use low byte as 0-255 level
774
- return {
775
- raw,
776
- percent: (raw / 255) * 100
777
- };
788
+ // Convert raw value to S-meter reading with physical units
789
+ // Uses IC-705 calibration by default (can be extended to support other models)
790
+ return (0, smeter_1.rawToSMeter)(raw, 'IC-705');
778
791
  }
779
792
  /**
780
793
  * Set WLAN connector audio level
@@ -798,6 +811,47 @@ class IcomControl {
798
811
  const modeCode = typeof mode === 'string' ? (0, IcomConstants_1.getConnectorModeCode)(mode) : mode;
799
812
  this.sendCiv(IcomRigCommands_1.IcomRigCommands.setConnectorDataMode(ctrAddr, rigAddr, modeCode));
800
813
  }
814
+ /**
815
+ * ==============================
816
+ * Antenna Tuner (ATU) Operations
817
+ * ==============================
818
+ */
819
+ /**
820
+ * Read antenna tuner status (CI-V 0x1A/0x00)
821
+ * 00=OFF, 01=ON, 02=TUNING
822
+ */
823
+ async readTunerStatus(options) {
824
+ const timeoutMs = options?.timeout ?? 3000;
825
+ const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
826
+ const rigAddr = this.civ.civAddress & 0xff;
827
+ const req = IcomRigCommands_1.IcomRigCommands.getTunerStatus(ctrAddr, rigAddr);
828
+ const resp = await this.waitForCivFrame((frame) => IcomControl.matchCommandFrame(frame, 0x1a, [0x00], ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(req));
829
+ if (!resp)
830
+ return null;
831
+ // Expect FE FE [ctr] [rig] 0x1A 0x00 [status] FD
832
+ const raw = resp.length > 6 ? (resp[6] & 0xff) : undefined;
833
+ if (raw === undefined)
834
+ return null;
835
+ const state = raw === 0x00 ? 'OFF' : raw === 0x01 ? 'ON' : raw === 0x02 ? 'TUNING' : 'OFF';
836
+ return { raw, state };
837
+ }
838
+ /**
839
+ * Enable or disable internal antenna tuner (CI-V 0x1A/0x01)
840
+ * @param enabled true to enable, false to disable
841
+ */
842
+ async setTunerEnabled(enabled) {
843
+ const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
844
+ const rigAddr = this.civ.civAddress & 0xff;
845
+ this.sendCiv(IcomRigCommands_1.IcomRigCommands.setTunerEnabled(ctrAddr, rigAddr, enabled));
846
+ }
847
+ /**
848
+ * Start a manual tuning cycle (same as [TUNE] key) (CI-V 0x1A/0x02/0x00)
849
+ */
850
+ async startManualTune() {
851
+ const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
852
+ const rigAddr = this.civ.civAddress & 0xff;
853
+ this.sendCiv(IcomRigCommands_1.IcomRigCommands.startManualTune(ctrAddr, rigAddr));
854
+ }
801
855
  /**
802
856
  * Read squelch status (noise/signal gate state)
803
857
  * @param options - Query options (timeout in ms, default 3000)
@@ -21,4 +21,7 @@ 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
+ getTunerStatus(ctrAddr: number, rigAddr: number): Buffer;
25
+ setTunerEnabled(ctrAddr: number, rigAddr: number, on: boolean): Buffer;
26
+ startManualTune(ctrAddr: number, rigAddr: number): Buffer;
24
27
  };
@@ -97,5 +97,20 @@ exports.IcomRigCommands = {
97
97
  getCurrent(ctrAddr, rigAddr) {
98
98
  // FE FE [rig] [ctr] 0x15 0x16 FD
99
99
  return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x15, 0x16, 0xfd]);
100
+ },
101
+ // =====================
102
+ // Antenna Tuner (ATU)
103
+ // =====================
104
+ getTunerStatus(ctrAddr, rigAddr) {
105
+ // FE FE [rig] [ctr] 0x1A 0x00 FD
106
+ return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x1a, 0x00, 0xfd]);
107
+ },
108
+ setTunerEnabled(ctrAddr, rigAddr, on) {
109
+ // FE FE [rig] [ctr] 0x1A 0x01 [00|01] FD
110
+ return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x1a, 0x01, on ? 0x01 : 0x00, 0xfd]);
111
+ },
112
+ startManualTune(ctrAddr, rigAddr) {
113
+ // FE FE [rig] [ctr] 0x1A 0x02 0x00 FD
114
+ return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x1a, 0x02, 0x00, 0xfd]);
100
115
  }
101
116
  };
package/dist/types.d.ts CHANGED
@@ -153,14 +153,27 @@ export interface WlanLevelReading {
153
153
  percent: number;
154
154
  }
155
155
  /**
156
- * Generic level meter (0-255) reading
157
- * For CI-V 0x15/0x02 experimental level meter
156
+ * S-meter (signal strength) level reading
157
+ * For CI-V 0x15/0x02 command
158
+ *
159
+ * Calibration (IC-705):
160
+ * - raw=0 → S0
161
+ * - raw=120 → S9
162
+ * - raw=241 → S9+60dB
158
163
  */
159
164
  export interface LevelMeterReading {
160
- /** Raw 0-255 value */
165
+ /** Raw 0-255 BCD value */
161
166
  raw: number;
162
167
  /** Percentage (0-100%) */
163
168
  percent: number;
169
+ /** S-unit value (0-9+), supports decimal (e.g., 4.5 = S4.5) */
170
+ sUnits: number;
171
+ /** dB above S9 (only when >S9, e.g., 20 means S9+20dB) */
172
+ dbAboveS9?: number;
173
+ /** Estimated absolute power in dBm (based on HF standard S9 ≈ -73dBm) */
174
+ dBm: number;
175
+ /** Human-readable formatted string (e.g., "S4", "S9+20dB") */
176
+ formatted: string;
164
177
  }
165
178
  /**
166
179
  * Squelch status reading (CI-V 0x15/0x01)
@@ -241,6 +254,20 @@ export interface CurrentReading {
241
254
  /** Current in amperes */
242
255
  amps: number;
243
256
  }
257
+ /**
258
+ * Antenna tuner state (IC-705 CI-V 0x1A/0x00)
259
+ * 00=OFF, 01=ON, 02=TUNING
260
+ */
261
+ export type TunerState = 'OFF' | 'ON' | 'TUNING';
262
+ /**
263
+ * Antenna tuner status reading
264
+ */
265
+ export interface TunerStatusReading {
266
+ /** Raw status code (0x00, 0x01, 0x02) */
267
+ raw: number;
268
+ /** Parsed state name */
269
+ state: TunerState;
270
+ }
244
271
  /**
245
272
  * Connection state enumeration
246
273
  * Represents the current state of a UDP session
@@ -0,0 +1,27 @@
1
+ /**
2
+ * S-meter (signal strength meter) conversion utilities
3
+ * Converts raw BCD values from CI-V 0x15/0x02 to physical S-units and dBm
4
+ */
5
+ import { LevelMeterReading } from '../types';
6
+ /**
7
+ * Convert raw S-meter BCD value to complete reading with S-units, dB, and dBm
8
+ *
9
+ * @param raw - Raw BCD value (0-255) from CI-V 0x15/0x02
10
+ * @param model - Radio model name (default: 'IC-705')
11
+ * @returns Complete LevelMeterReading with all calculated fields
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const reading = rawToSMeter(140, 'IC-705');
16
+ * console.log(reading);
17
+ * // {
18
+ * // raw: 140,
19
+ * // percent: 54.9,
20
+ * // sUnits: 9.99,
21
+ * // dbAboveS9: 9.92,
22
+ * // dBm: -63.08,
23
+ * // formatted: "S9+10dB"
24
+ * // }
25
+ * ```
26
+ */
27
+ export declare function rawToSMeter(raw: number, model?: string): LevelMeterReading;
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ /**
3
+ * S-meter (signal strength meter) conversion utilities
4
+ * Converts raw BCD values from CI-V 0x15/0x02 to physical S-units and dBm
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.rawToSMeter = rawToSMeter;
8
+ const IcomConstants_1 = require("../rig/IcomConstants");
9
+ /**
10
+ * Get S-meter calibration constants for a specific radio model
11
+ * @param model - Radio model name (e.g., 'IC-705')
12
+ * @returns Calibration constants, defaults to IC-705 if model not found
13
+ */
14
+ function getCalibration(model = 'IC-705') {
15
+ const cal = IcomConstants_1.METER_CALIBRATION.SMETER[model];
16
+ if (!cal) {
17
+ // Default to IC-705 if model not found
18
+ console.warn(`S-meter calibration for model '${model}' not found, using IC-705 defaults`);
19
+ return IcomConstants_1.METER_CALIBRATION.SMETER['IC-705'];
20
+ }
21
+ return cal;
22
+ }
23
+ /**
24
+ * Convert raw S-meter value to S-units (0-9+)
25
+ * Uses linear interpolation based on calibration points
26
+ *
27
+ * @param raw - Raw BCD value (0-255)
28
+ * @param cal - Calibration constants
29
+ * @returns S-unit value (0-9+, supports decimals)
30
+ */
31
+ function rawToSUnits(raw, cal) {
32
+ // Clamp to valid range
33
+ if (raw <= cal.S0)
34
+ return 0;
35
+ if (raw >= cal.S9_PLUS_60DB) {
36
+ // Maximum S9+60dB = S9 + 60dB/6dB per S-unit = S9 + 10 S-units = S19
37
+ return 9 + 60 / 6;
38
+ }
39
+ // Linear interpolation
40
+ if (raw <= cal.S9) {
41
+ // S0 to S9: linear from 0 to 9
42
+ return (raw - cal.S0) * 9.0 / (cal.S9 - cal.S0);
43
+ }
44
+ else {
45
+ // Above S9: each S-unit = 6dB
46
+ const dbAboveS9 = (raw - cal.S9) * 60.0 / (cal.S9_PLUS_60DB - cal.S9);
47
+ return 9 + dbAboveS9 / 6.0;
48
+ }
49
+ }
50
+ /**
51
+ * Convert raw S-meter value to dB above S9
52
+ * Only meaningful when raw > S9 threshold
53
+ *
54
+ * @param raw - Raw BCD value (0-255)
55
+ * @param cal - Calibration constants
56
+ * @returns dB above S9, or undefined if below S9
57
+ */
58
+ function rawToDbAboveS9(raw, cal) {
59
+ if (raw <= cal.S9) {
60
+ return undefined; // Below S9, no "dB above S9" concept
61
+ }
62
+ // Linear interpolation: S9 to S9+60dB
63
+ const dbAboveS9 = (raw - cal.S9) * 60.0 / (cal.S9_PLUS_60DB - cal.S9);
64
+ return Math.max(0, dbAboveS9); // Clamp to non-negative
65
+ }
66
+ /**
67
+ * Estimate absolute power in dBm
68
+ * Based on HF standard: S9 ≈ -73dBm
69
+ * NOTE: This is an estimation and may vary by band, filter, and device settings
70
+ *
71
+ * @param sUnits - S-unit value
72
+ * @param cal - Calibration constants
73
+ * @returns Estimated power in dBm
74
+ */
75
+ function estimateDpm(sUnits, cal) {
76
+ // Each S-unit below S9 = 6dB
77
+ // Each dB above S9 = 1dB
78
+ const dbRelativeToS9 = (sUnits - 9) * 6.0;
79
+ return cal.HF_S9_DBM + dbRelativeToS9;
80
+ }
81
+ /**
82
+ * Format S-meter reading as human-readable string
83
+ * Examples: "S0", "S4", "S9", "S9+10dB", "S9+60dB"
84
+ *
85
+ * @param sUnits - S-unit value
86
+ * @param dbAboveS9 - dB above S9 (if any)
87
+ * @returns Formatted string
88
+ */
89
+ function formatSMeter(sUnits, dbAboveS9) {
90
+ if (dbAboveS9 !== undefined && dbAboveS9 > 0) {
91
+ // Above S9: show as "S9+XdB"
92
+ const roundedDb = Math.round(dbAboveS9);
93
+ return `S9+${roundedDb}dB`;
94
+ }
95
+ else {
96
+ // S0-S9: show as "SX"
97
+ const roundedS = Math.floor(sUnits);
98
+ return `S${roundedS}`;
99
+ }
100
+ }
101
+ /**
102
+ * Convert raw S-meter BCD value to complete reading with S-units, dB, and dBm
103
+ *
104
+ * @param raw - Raw BCD value (0-255) from CI-V 0x15/0x02
105
+ * @param model - Radio model name (default: 'IC-705')
106
+ * @returns Complete LevelMeterReading with all calculated fields
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * const reading = rawToSMeter(140, 'IC-705');
111
+ * console.log(reading);
112
+ * // {
113
+ * // raw: 140,
114
+ * // percent: 54.9,
115
+ * // sUnits: 9.99,
116
+ * // dbAboveS9: 9.92,
117
+ * // dBm: -63.08,
118
+ * // formatted: "S9+10dB"
119
+ * // }
120
+ * ```
121
+ */
122
+ function rawToSMeter(raw, model = 'IC-705') {
123
+ const cal = getCalibration(model);
124
+ // Calculate all fields
125
+ const sUnits = rawToSUnits(raw, cal);
126
+ const dbAboveS9 = rawToDbAboveS9(raw, cal);
127
+ const dBm = estimateDpm(sUnits, cal);
128
+ const formatted = formatSMeter(sUnits, dbAboveS9);
129
+ const percent = (raw / 255) * 100;
130
+ return {
131
+ raw,
132
+ percent: Math.round(percent * 10) / 10, // Round to 1 decimal place
133
+ sUnits: Math.round(sUnits * 100) / 100, // Round to 2 decimal places
134
+ dbAboveS9: dbAboveS9 !== undefined ? Math.round(dbAboveS9 * 100) / 100 : undefined,
135
+ dBm: Math.round(dBm * 100) / 100, // Round to 2 decimal places
136
+ formatted
137
+ };
138
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icom-wlan-node",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
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",