icom-wlan-node 0.6.2 → 0.6.3

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
@@ -132,6 +132,8 @@ await rig.setSplitEnabled(true);
132
132
  await rig.setSplitFrequency(7074000);
133
133
  await rig.setTuningStep(100);
134
134
  await rig.setToneFrequency(88.5);
135
+ await rig.setMode('CW');
136
+ await rig.sendMorse('CQ CQ DE N0CALL');
135
137
  await rig.setSpectrumSpeed('slow');
136
138
  ```
137
139
 
@@ -235,6 +237,7 @@ await rig.disableScope();
235
237
  - **Antenna Tuner**: `readTunerStatus()`, `setTunerEnabled()`, `startManualTune()`
236
238
  - **Functions**: `getFunction()`, `setFunction()`, plus wrappers for NB/NR/COMP/VOX/MON/ANF/MN/LOCK/BK-IN
237
239
  - **Levels**: `getLevel()`, `setLevel()`, plus wrappers for RF gain, IF shift, PBT, CW pitch, key speed, monitor gain, VOX gain
240
+ - **CW Text**: `sendMorse()`, `sendCwText()`, `stopMorse()` — Hamlib-aligned CI-V `0x17` text handoff to the rig keyer
238
241
  - **RIT/XIT + Split/VFO**: `getRitOffset()`, `setRitOffset()`, `getSplitEnabled()`, `setSplitFrequency()`, `getVfo()`, `setVfo()`, `vfoOperation()`
239
242
  - **Tone/Tuning**: `getTuningStep()`, `setTuningStep()`, `getToneFrequency()`, `setToneFrequency()`, `getRepeaterShift()`, `setRepeaterOffset()`
240
243
  - **Meters (RX)**: `readSquelchStatus()`, `readAudioSquelch()`, `readOvfStatus()`, `getLevelMeter()`
@@ -384,6 +387,8 @@ The library exposes common CI‑V operations as friendly methods. Addresses are
384
387
  - `getBreakInMode() => Promise<'off'|'semi'|'full'|null>` / `setBreakInMode(mode)` — Hamlib-aligned BK-IN control using `0x16 0x47`, where raw `1` is semi and raw `2` is full.
385
388
  - `getLevel(name: IcomLevelName, options?: QueryOptions) => Promise<number|null>` / `setLevel(name, value)` — Generic level layer for `AF`, `RF`, `SQL`, `IF`, `PBT_IN`, `PBT_OUT`, `CWPITCH`, `KEYSPD`, `NOTCHF_RAW`, `COMP`, `MONITOR_GAIN`, `VOXGAIN`, `ANTIVOX`, and profile ext levels.
386
389
  - Common level wrappers: `getRFGain()/setRFGain()`, `getIFShift()/setIFShift()`, `getPbtIn()/setPbtIn()`, `getPbtOut()/setPbtOut()`, `getCwPitch()/setCwPitch()`, `getKeySpeed()/setKeySpeed()`, `getNotchRaw()/setNotchRaw()`, `getCompressionLevel()/setCompressionLevel()`, `getMonitorGain()/setMonitorGain()`, `getVoxGain()/setVoxGain()`, `getAntiVox()/setAntiVox()`.
390
+ - `sendMorse(text, options?)` / `sendCwText(text, options?)` — Send printable ASCII text through the rig's internal CW keyer using CI-V `0x17`; text is uppercased and split into 30-byte chunks by default.
391
+ - `stopMorse(options?)` — Stop the rig CW keyer by sending CI-V `0x17 0xff` and waiting for ACK.
387
392
  - `getRitOffset()/setRitOffset()` and `getXitOffset()/setXitOffset()` — RIT/XIT delta register using Hamlib `0x21 0x00`; `getRitEnabled()/setRitEnabled()` and `getXitEnabled()/setXitEnabled()` use `0x21 0x01/0x02`.
388
393
  - `getSplitEnabled()/setSplitEnabled()`, `getSplitFrequency()/setSplitFrequency()`, `getSplitMode()/setSplitMode()` — Modern profiles use targetable TX VFO `0x25/0x26` with VFO number `1`.
389
394
  - `getVfo()/setVfo()` and `vfoOperation('copy'|'exchange'|'from-vfo'|'to-vfo'|'memory-clear'|'tune')` — Safe Hamlib VFO operation subset.
@@ -391,6 +396,20 @@ The library exposes common CI‑V operations as friendly methods. Addresses are
391
396
  - `getToneFrequency()/setToneFrequency(hz)` and `getToneSquelchFrequency()/setToneSquelchFrequency(hz)` — CTCSS values use public Hz, encoded as tenth-Hz BCD BE.
392
397
  - `getRepeaterShift()/setRepeaterShift('none'|'minus'|'plus')` and `getRepeaterOffset()/setRepeaterOffset(hz)` — Repeater offset uses public Hz, encoded as 100 Hz units.
393
398
 
399
+ #### CW Text Sending
400
+
401
+ ```ts
402
+ await rig.setMode('CW');
403
+ rig.setBreakInMode('semi');
404
+ rig.setKeySpeed(20);
405
+
406
+ // Sends printable ASCII text to the radio's built-in CW keyer via CI-V 0x17.
407
+ await rig.sendMorse('CQ CQ DE N0CALL', { timeout: 3000 });
408
+ await rig.stopMorse();
409
+ ```
410
+
411
+ `sendMorse()` does not synthesize local Morse audio and does not automatically change mode, PTT, power, or break-in settings. By default it first verifies that the radio reports `CW` or `CW_R`; pass `{ checkMode: false }` only when your application already controls that state. CI-V ACK/NAK replies do not include request IDs, so avoid mixing raw `sendCiv()` write commands that also expect ACKs while a CW text send is in progress.
412
+
394
413
  #### Scope / Spectrum
395
414
 
396
415
  - `scope: IcomScopeService` — Standalone scope service object that can be reused with other CI‑V transport paths in the future
@@ -25,6 +25,7 @@ export declare const CIV: {
25
25
  readonly C_CTL_LVL: 20;
26
26
  readonly C_RD_SQSM: 21;
27
27
  readonly C_CTL_FUNC: 22;
28
+ readonly C_SND_CW: 23;
28
29
  readonly C_SET_TONE: 27;
29
30
  readonly C_CTL_MEM: 26;
30
31
  readonly C_CTL_PTT: 28;
@@ -28,6 +28,7 @@ exports.CIV = {
28
28
  C_CTL_LVL: 0x14,
29
29
  C_RD_SQSM: 0x15,
30
30
  C_CTL_FUNC: 0x16,
31
+ C_SND_CW: 0x17,
31
32
  C_SET_TONE: 0x1b,
32
33
  C_CTL_MEM: 0x1a,
33
34
  C_CTL_PTT: 0x1c,
@@ -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, LevelReading, IcomScopeSpanInfo, IcomScopeMode, IcomScopeModeInfo, IcomScopeEdgeInfo, IcomScopeFixedEdgeInfo, IcomSpectrumDisplayState, IcomSpectrumDisplayConfig, IcomFunctionName, IcomLevelName, IcomParameterName, IcomVfoName, IcomVfoOperation, IcomRepeaterShift, IcomSpectrumSpeed, IcomSpectrumCenterType, IcomAudioIfSource } from '../types';
1
+ import { IcomRigOptions, RigEventEmitter, IcomMode, ConnectorDataMode, SetModeOptions, SendMorseOptions, QueryOptions, SwrReading, AlcReading, WlanLevelReading, LevelMeterReading, SquelchStatusReading, AudioSquelchReading, OvfStatusReading, PowerLevelReading, CompLevelReading, VoltageReading, CurrentReading, ConnectionState, ConnectionMonitorConfig, ConnectionPhase, ConnectionMetrics, DisconnectReason, DisconnectOptions, TunerStatusReading, LevelReading, IcomScopeSpanInfo, IcomScopeMode, IcomScopeModeInfo, IcomScopeEdgeInfo, IcomScopeFixedEdgeInfo, IcomSpectrumDisplayState, IcomSpectrumDisplayConfig, IcomFunctionName, IcomLevelName, IcomParameterName, IcomVfoName, IcomVfoOperation, IcomRepeaterShift, IcomSpectrumSpeed, IcomSpectrumCenterType, IcomAudioIfSource } from '../types';
2
2
  import { IcomCiv } from './IcomCiv';
3
3
  import { IcomAudio } from './IcomAudio';
4
4
  import { IcomScopeService } from '../scope/IcomScopeService';
@@ -20,6 +20,8 @@ export declare class IcomControl {
20
20
  private meterTimer?;
21
21
  private activeProfile;
22
22
  private lastFilter;
23
+ private cwQueue;
24
+ private cwGeneration;
23
25
  private connectionSession;
24
26
  private nextSessionId;
25
27
  private abortHandlers;
@@ -433,6 +435,11 @@ export declare class IcomControl {
433
435
  setCwPitch(hz: number): void;
434
436
  getKeySpeed(options?: QueryOptions): Promise<number | null>;
435
437
  setKeySpeed(wpm: number): void;
438
+ sendMorse(text: string, options?: SendMorseOptions): Promise<void>;
439
+ sendCwText(text: string, options?: SendMorseOptions): Promise<void>;
440
+ stopMorse(options?: {
441
+ timeout?: number;
442
+ }): Promise<void>;
436
443
  getNotchRaw(options?: QueryOptions): Promise<number | null>;
437
444
  setNotchRaw(value: number): void;
438
445
  getCompressionLevel(options?: QueryOptions): Promise<number | null>;
@@ -525,6 +532,8 @@ export declare class IcomControl {
525
532
  getSpectrumCenterType(options?: QueryOptions): Promise<IcomSpectrumCenterType | null>;
526
533
  setSpectrumCenterType(type: IcomSpectrumCenterType): void;
527
534
  private unsupported;
535
+ private enqueueCw;
536
+ private sendCwFrameAndWaitForAck;
528
537
  private readFunctionRaw;
529
538
  private writeFunctionRaw;
530
539
  private readLevelRaw;
@@ -554,6 +563,8 @@ export declare class IcomControl {
554
563
  private static extractTrailingBcd;
555
564
  private static matchCommand;
556
565
  private static matchCommandFrame;
566
+ private static matchAckNakFrame;
567
+ private static normalizeMorseText;
557
568
  private waitForCiv;
558
569
  static parseFrequencyReply(frame: Buffer, payloadOffsetAfterCommand: number, byteLength?: number): number | null;
559
570
  static parseIcomFreqFromReply(frame: Buffer): number | null;
@@ -150,6 +150,8 @@ class IcomControl {
150
150
  this.civAssembleBuf = Buffer.alloc(0); // CIV stream reassembler
151
151
  this.activeProfile = (0, IcomProfiles_1.getProfileByModel)('generic-modern-icom');
152
152
  this.lastFilter = 1;
153
+ this.cwQueue = Promise.resolve();
154
+ this.cwGeneration = 0;
153
155
  // Connection state machine (replaces old fragmented state flags)
154
156
  this.connectionSession = {
155
157
  phase: types_1.ConnectionPhase.IDLE,
@@ -1605,6 +1607,61 @@ class IcomControl {
1605
1607
  setCwPitch(hz) { this.setLevel('CWPITCH', hz); }
1606
1608
  async getKeySpeed(options) { return this.getLevel('KEYSPD', options); }
1607
1609
  setKeySpeed(wpm) { this.setLevel('KEYSPD', wpm); }
1610
+ async sendMorse(text, options = {}) {
1611
+ const normalized = IcomControl.normalizeMorseText(text);
1612
+ if (normalized.length === 0)
1613
+ return;
1614
+ const generation = this.cwGeneration;
1615
+ return this.enqueueCw(async () => {
1616
+ if (generation !== this.cwGeneration)
1617
+ return;
1618
+ if (!this.activeProfile.cw.sendMorse) {
1619
+ throw this.unsupported('SEND_MORSE', 'sendMorse', 'CW text sending is not enabled for active profile');
1620
+ }
1621
+ const timeoutMs = options.timeout ?? 3000;
1622
+ if (options.checkMode !== false) {
1623
+ const mode = await this.readOperatingMode({ timeout: timeoutMs });
1624
+ if (!mode || (mode.mode !== IcomConstants_1.MODE_MAP.CW && mode.mode !== IcomConstants_1.MODE_MAP.CW_R)) {
1625
+ throw new Error(`CW 0x17 sendMorse requires CW/CW_R mode; current mode is ${mode?.modeName ?? mode?.mode ?? 'unknown'}`);
1626
+ }
1627
+ }
1628
+ const profileMax = Math.max(1, Math.min(30, this.activeProfile.cw.maxChunkLength || 30));
1629
+ const requested = Number.isFinite(options.chunkLength) ? Math.floor(options.chunkLength) : profileMax;
1630
+ const chunkLength = Math.max(1, Math.min(30, profileMax, requested));
1631
+ const interChunkDelayMs = Number.isFinite(options.interChunkDelayMs)
1632
+ ? Math.max(0, Math.floor(options.interChunkDelayMs))
1633
+ : 0;
1634
+ const bytes = Buffer.from(normalized, 'ascii');
1635
+ const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1636
+ const rigAddr = this.civ.civAddress & 0xff;
1637
+ for (let offset = 0, chunkIndex = 1; offset < bytes.length; offset += chunkLength, chunkIndex++) {
1638
+ if (generation !== this.cwGeneration)
1639
+ return;
1640
+ const chunk = bytes.subarray(offset, Math.min(offset + chunkLength, bytes.length));
1641
+ const frame = IcomRigCommands_1.IcomRigCommands.sendMorseText(ctrAddr, rigAddr, chunk);
1642
+ await this.sendCwFrameAndWaitForAck(frame, timeoutMs, `chunk ${chunkIndex}`);
1643
+ if (generation !== this.cwGeneration)
1644
+ return;
1645
+ if (interChunkDelayMs > 0 && offset + chunkLength < bytes.length) {
1646
+ await new Promise((resolve) => setTimeout(resolve, interChunkDelayMs));
1647
+ }
1648
+ }
1649
+ });
1650
+ }
1651
+ sendCwText(text, options) {
1652
+ return this.sendMorse(text, options);
1653
+ }
1654
+ async stopMorse(options = {}) {
1655
+ this.cwGeneration += 1;
1656
+ return this.enqueueCw(async () => {
1657
+ if (!this.activeProfile.cw.sendMorse) {
1658
+ throw this.unsupported('SEND_MORSE', 'stopMorse', 'CW text sending is not enabled for active profile');
1659
+ }
1660
+ const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1661
+ const rigAddr = this.civ.civAddress & 0xff;
1662
+ await this.sendCwFrameAndWaitForAck(IcomRigCommands_1.IcomRigCommands.stopMorse(ctrAddr, rigAddr), options.timeout ?? 3000, 'stop');
1663
+ });
1664
+ }
1608
1665
  async getNotchRaw(options) { return this.getLevel('NOTCHF_RAW', options); }
1609
1666
  setNotchRaw(value) { this.setLevel('NOTCHF_RAW', value); }
1610
1667
  async getCompressionLevel(options) { return this.getLevel('COMP', options); }
@@ -1885,6 +1942,22 @@ class IcomControl {
1885
1942
  reason,
1886
1943
  });
1887
1944
  }
1945
+ enqueueCw(operation) {
1946
+ const run = this.cwQueue.then(operation, operation);
1947
+ this.cwQueue = run.then(() => undefined, () => undefined);
1948
+ return run;
1949
+ }
1950
+ async sendCwFrameAndWaitForAck(frame, timeoutMs, label) {
1951
+ const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
1952
+ const rigAddr = this.civ.civAddress & 0xff;
1953
+ const resp = await this.waitForCivFrame('cw:ack:0x17', (candidate) => IcomControl.matchAckNakFrame(candidate, ctrAddr, rigAddr), timeoutMs, () => this.sendCiv(frame));
1954
+ if (!resp) {
1955
+ throw new Error(`CW 0x17 ${label} ACK timeout`);
1956
+ }
1957
+ if (resp[4] === IcomCivSpec_1.CIV.NAK) {
1958
+ throw new Error(`CW 0x17 ${label} NAK received`);
1959
+ }
1960
+ }
1888
1961
  async readFunctionRaw(name, spec, options) {
1889
1962
  const timeoutMs = options?.timeout ?? 3000;
1890
1963
  const ctrAddr = IcomConstants_1.DEFAULT_CONTROLLER_ADDR;
@@ -2124,6 +2197,29 @@ class IcomControl {
2124
2197
  return false;
2125
2198
  return true;
2126
2199
  }
2200
+ static matchAckNakFrame(frame, ctrAddr, rigAddr) {
2201
+ if (!(frame.length >= 6 && frame[0] === IcomCivSpec_1.CIV.PR && frame[1] === IcomCivSpec_1.CIV.PR))
2202
+ return false;
2203
+ const addrCtrOk = frame[2] === (ctrAddr & 0xff) || frame[2] === 0x00;
2204
+ if (!addrCtrOk || frame[3] !== (rigAddr & 0xff))
2205
+ return false;
2206
+ if (frame[4] !== IcomCivSpec_1.CIV.ACK && frame[4] !== IcomCivSpec_1.CIV.NAK)
2207
+ return false;
2208
+ return frame[frame.length - 1] === IcomCivSpec_1.CIV.FI;
2209
+ }
2210
+ static normalizeMorseText(text) {
2211
+ if (typeof text !== 'string') {
2212
+ throw new Error('CW 0x17 sendMorse text must be a string');
2213
+ }
2214
+ const normalized = text.toUpperCase().replace(/[\r\n\t]/g, ' ');
2215
+ for (let i = 0; i < normalized.length; i++) {
2216
+ const code = normalized.charCodeAt(i);
2217
+ if (code < 0x20 || code > 0x7e) {
2218
+ throw new Error(`CW 0x17 sendMorse text contains unsupported non-printable or non-ASCII character at index ${i}`);
2219
+ }
2220
+ }
2221
+ return normalized;
2222
+ }
2127
2223
  async waitForCiv(predicate, timeoutMs, onSend) {
2128
2224
  return new Promise((resolve) => {
2129
2225
  let done = false;
@@ -41,6 +41,10 @@ export interface IcomProfile {
41
41
  vfos: IcomVfoName[];
42
42
  repeater: boolean;
43
43
  tone: boolean;
44
+ cw: {
45
+ sendMorse: boolean;
46
+ maxChunkLength: number;
47
+ };
44
48
  spectrumAdvanced: Array<'dataOutput' | 'hold' | 'speed' | 'ref' | 'avg' | 'vbw' | 'rbw' | 'duringTx' | 'centerType'>;
45
49
  audioIfSources: IcomAudioIfSource[];
46
50
  calibrations: {
@@ -115,6 +115,7 @@ const TARGETABLE_VFOS = ['A', 'B', 'MAIN', 'SUB', 'MAIN_A', 'MAIN_B', 'SUB_A', '
115
115
  const COMMON_VFO_OPS = ['copy', 'exchange', 'from-vfo', 'to-vfo', 'memory-clear', 'tune'];
116
116
  const IC905_VFO_OPS = ['copy', 'from-vfo', 'to-vfo', 'memory-clear', 'tune'];
117
117
  const SPECTRUM_ADVANCED = ['dataOutput', 'hold', 'speed', 'ref', 'avg', 'vbw', 'rbw', 'duringTx', 'centerType'];
118
+ const CW_TEXT_SUPPORTED = { sendMorse: true, maxChunkLength: 30 };
118
119
  const TS_IC756PRO = [
119
120
  { hz: 10, code: 0x00 },
120
121
  { hz: 100, code: 0x01 },
@@ -180,6 +181,7 @@ function baseProfile(overrides) {
180
181
  vfos: DEFAULT_VFOS,
181
182
  repeater: false,
182
183
  tone: false,
184
+ cw: { sendMorse: false, maxChunkLength: 30 },
183
185
  spectrumAdvanced: ['dataOutput', 'hold', 'speed', 'ref', 'vbw', 'duringTx', 'centerType'],
184
186
  audioIfSources: ['default'],
185
187
  calibrations: {
@@ -289,6 +291,7 @@ exports.ICOM_PROFILES = {
289
291
  vfos: DEFAULT_VFOS,
290
292
  repeater: true,
291
293
  tone: true,
294
+ cw: CW_TEXT_SUPPORTED,
292
295
  spectrumAdvanced: ['dataOutput', 'hold', 'speed', 'ref', 'avg', 'vbw', 'duringTx', 'centerType'],
293
296
  audioIfSources: ['default', 'wlan'],
294
297
  calibrations: {
@@ -319,6 +322,7 @@ exports.ICOM_PROFILES = {
319
322
  vfos: DEFAULT_VFOS,
320
323
  repeater: true,
321
324
  tone: true,
325
+ cw: CW_TEXT_SUPPORTED,
322
326
  spectrumAdvanced: ['dataOutput', 'hold', 'speed', 'ref', 'avg', 'vbw', 'duringTx', 'centerType'],
323
327
  audioIfSources: ['default', 'wlan'],
324
328
  calibrations: {
@@ -348,6 +352,7 @@ exports.ICOM_PROFILES = {
348
352
  vfos: DEFAULT_VFOS,
349
353
  repeater: false,
350
354
  tone: true,
355
+ cw: CW_TEXT_SUPPORTED,
351
356
  spectrumAdvanced: ['dataOutput', 'hold', 'speed', 'ref', 'avg', 'vbw', 'duringTx', 'centerType'],
352
357
  audioIfSources: ['default'],
353
358
  extParams: {
@@ -374,6 +379,7 @@ exports.ICOM_PROFILES = {
374
379
  vfos: TARGETABLE_VFOS,
375
380
  repeater: true,
376
381
  tone: true,
382
+ cw: CW_TEXT_SUPPORTED,
377
383
  spectrumAdvanced: ['dataOutput', 'hold', 'speed', 'ref', 'avg', 'vbw', 'duringTx', 'centerType'],
378
384
  audioIfSources: ['default', 'lan', 'acc'],
379
385
  extParams: {
@@ -396,6 +402,7 @@ exports.ICOM_PROFILES = {
396
402
  vfos: TARGETABLE_VFOS,
397
403
  repeater: false,
398
404
  tone: true,
405
+ cw: CW_TEXT_SUPPORTED,
399
406
  spectrumAdvanced: SPECTRUM_ADVANCED,
400
407
  audioIfSources: ['default'],
401
408
  extParams: {
@@ -426,6 +433,7 @@ exports.ICOM_PROFILES = {
426
433
  vfos: DEFAULT_VFOS,
427
434
  repeater: false,
428
435
  tone: true,
436
+ cw: CW_TEXT_SUPPORTED,
429
437
  spectrumAdvanced: [],
430
438
  extParamSpecs: {
431
439
  ...IC7760_EXT_SPECS,
@@ -34,6 +34,8 @@ export declare const IcomRigCommands: {
34
34
  writeCommand(ctrAddr: number, rigAddr: number, cmd: number, subcmd: number | number[] | undefined, payload?: number[] | Buffer): Buffer;
35
35
  readFunction(ctrAddr: number, rigAddr: number, subcmd: number, payload?: number[] | Buffer): Buffer;
36
36
  setFunction(ctrAddr: number, rigAddr: number, subcmd: number, value: number, payloadPrefix?: number[]): Buffer;
37
+ sendMorseText(ctrAddr: number, rigAddr: number, textBytes: Buffer | number[] | string): Buffer;
38
+ stopMorse(ctrAddr: number, rigAddr: number): Buffer;
37
39
  readExtParam(ctrAddr: number, rigAddr: number, command: number, subcmd: number, subext?: number[]): Buffer;
38
40
  writeExtParam(ctrAddr: number, rigAddr: number, command: number, subcmd: number, subext?: number[], payload?: number[] | Buffer): Buffer;
39
41
  readRitOffset(ctrAddr: number, rigAddr: number): Buffer;
@@ -116,6 +116,13 @@ exports.IcomRigCommands = {
116
116
  setFunction(ctrAddr, rigAddr, subcmd, value, payloadPrefix = []) {
117
117
  return (0, IcomCivFrame_1.buildCivFrame)({ rigAddr, ctrlAddr: ctrAddr, cmd: IcomCivSpec_1.CIV.C_CTL_FUNC, subcmd, payload: [...payloadPrefix, value & 0xff] });
118
118
  },
119
+ sendMorseText(ctrAddr, rigAddr, textBytes) {
120
+ const payload = typeof textBytes === 'string' ? Buffer.from(textBytes, 'ascii') : textBytes;
121
+ return (0, IcomCivFrame_1.buildCivFrame)({ rigAddr, ctrlAddr: ctrAddr, cmd: IcomCivSpec_1.CIV.C_SND_CW, payload });
122
+ },
123
+ stopMorse(ctrAddr, rigAddr) {
124
+ return (0, IcomCivFrame_1.buildCivFrame)({ rigAddr, ctrlAddr: ctrAddr, cmd: IcomCivSpec_1.CIV.C_SND_CW, payload: [0xff] });
125
+ },
119
126
  readExtParam(ctrAddr, rigAddr, command, subcmd, subext = []) {
120
127
  return (0, IcomCivFrame_1.buildCivFrame)({ rigAddr, ctrlAddr: ctrAddr, cmd: command, subcmd, payload: subext });
121
128
  },
package/dist/types.d.ts CHANGED
@@ -91,6 +91,16 @@ export interface SetModeOptions {
91
91
  /** Optional IF filter preset. Modern DSP rigs commonly support 1, 2, or 3. */
92
92
  filter?: 1 | 2 | 3;
93
93
  }
94
+ export interface SendMorseOptions {
95
+ /** ACK/NAK timeout per CI-V 0x17 chunk. Defaults to 3000 ms. */
96
+ timeout?: number;
97
+ /** Maximum text bytes per CI-V 0x17 chunk. Defaults to the active profile limit. */
98
+ chunkLength?: number;
99
+ /** Read and require CW/CW_R mode before sending. Defaults to true. */
100
+ checkMode?: boolean;
101
+ /** Optional delay between acknowledged chunks. Defaults to 0 ms. */
102
+ interChunkDelayMs?: number;
103
+ }
94
104
  /**
95
105
  * Options for query operations (frequency, meters, etc.)
96
106
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icom-wlan-node",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
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",