icom-wlan-node 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/core/IcomPackets.d.ts +104 -0
- package/dist/core/IcomPackets.js +333 -0
- package/dist/core/Session.d.ts +43 -0
- package/dist/core/Session.js +105 -0
- package/dist/demo.d.ts +12 -0
- package/dist/demo.js +329 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +42 -0
- package/dist/rig/IcomAudio.d.ts +27 -0
- package/dist/rig/IcomAudio.js +143 -0
- package/dist/rig/IcomCiv.d.ts +14 -0
- package/dist/rig/IcomCiv.js +44 -0
- package/dist/rig/IcomConstants.d.ts +84 -0
- package/dist/rig/IcomConstants.js +112 -0
- package/dist/rig/IcomControl.d.ts +155 -0
- package/dist/rig/IcomControl.js +912 -0
- package/dist/rig/IcomRigCommands.d.ts +17 -0
- package/dist/rig/IcomRigCommands.js +73 -0
- package/dist/transport/UdpClient.d.ts +14 -0
- package/dist/transport/UdpClient.js +47 -0
- package/dist/types.d.ts +160 -0
- package/dist/types.js +2 -0
- package/dist/utils/bcd.d.ts +36 -0
- package/dist/utils/bcd.js +59 -0
- package/dist/utils/codec.d.ts +22 -0
- package/dist/utils/codec.js +56 -0
- package/dist/utils/debug.d.ts +4 -0
- package/dist/utils/debug.js +15 -0
- package/package.json +31 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const IcomRigCommands: {
|
|
2
|
+
setPTT(ctrAddr: number, rigAddr: number, on: boolean): Buffer;
|
|
3
|
+
setMode(ctrAddr: number, rigAddr: number, mode: number): Buffer;
|
|
4
|
+
setFrequency(ctrAddr: number, rigAddr: number, hz: number): Buffer;
|
|
5
|
+
readOperatingFrequency(ctrAddr: number, rigAddr: number): Buffer;
|
|
6
|
+
readOperatingMode(ctrAddr: number, rigAddr: number): Buffer;
|
|
7
|
+
readTransmitFrequency(ctrAddr: number, rigAddr: number): Buffer;
|
|
8
|
+
readTransceiverState(ctrAddr: number, rigAddr: number): Buffer;
|
|
9
|
+
readBandEdges(ctrAddr: number, rigAddr: number): Buffer;
|
|
10
|
+
setOperationDataMode(ctrAddr: number, rigAddr: number, mode: number): Buffer;
|
|
11
|
+
getSWRState(ctrAddr: number, rigAddr: number): Buffer;
|
|
12
|
+
getALCState(ctrAddr: number, rigAddr: number): Buffer;
|
|
13
|
+
getLevelMeter(ctrAddr: number, rigAddr: number): Buffer;
|
|
14
|
+
getConnectorWLanLevel(ctrAddr: number, rigAddr: number): Buffer;
|
|
15
|
+
setConnectorWLanLevel(ctrAddr: number, rigAddr: number, level: number): Buffer;
|
|
16
|
+
setConnectorDataMode(ctrAddr: number, rigAddr: number, mode: number): Buffer;
|
|
17
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Helpers to build CI-V frames for common operations (PTT, mode, frequency)
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.IcomRigCommands = void 0;
|
|
5
|
+
exports.IcomRigCommands = {
|
|
6
|
+
// FE FE [rigAddr] [ctrAddr] 1C 00 [01|00] FD
|
|
7
|
+
setPTT(ctrAddr, rigAddr, on) {
|
|
8
|
+
return Buffer.from([
|
|
9
|
+
0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x1c, 0x00, on ? 0x01 : 0x00, 0xfd
|
|
10
|
+
]);
|
|
11
|
+
},
|
|
12
|
+
setMode(ctrAddr, rigAddr, mode) {
|
|
13
|
+
return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x06, mode & 0xff, 0x01, 0xfd]);
|
|
14
|
+
},
|
|
15
|
+
setFrequency(ctrAddr, rigAddr, hz) {
|
|
16
|
+
// 05: BCD format, little-endian nibbles per Java logic
|
|
17
|
+
const bcd = (n) => (((n / 10) | 0) << 4) + (n % 10);
|
|
18
|
+
const d0 = bcd(Math.floor(hz % 100));
|
|
19
|
+
const d1 = bcd(Math.floor((hz % 10000) / 100));
|
|
20
|
+
const d2 = bcd(Math.floor((hz % 1000000) / 10000));
|
|
21
|
+
const d3 = bcd(Math.floor((hz % 100000000) / 1000000));
|
|
22
|
+
const d4 = bcd(Math.floor(hz / 100000000)); // Fixed: was /1000000000
|
|
23
|
+
return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x05, d0, d1, d2, d3, d4, 0xfd]);
|
|
24
|
+
},
|
|
25
|
+
readOperatingFrequency(ctrAddr, rigAddr) {
|
|
26
|
+
// FE FE [rigAddr] [ctrAddr] 0x03 FD
|
|
27
|
+
return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x03, 0xfd]);
|
|
28
|
+
},
|
|
29
|
+
readOperatingMode(ctrAddr, rigAddr) {
|
|
30
|
+
// FE FE [rig] [ctr] 0x04 FD
|
|
31
|
+
return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x04, 0xfd]);
|
|
32
|
+
},
|
|
33
|
+
readTransmitFrequency(ctrAddr, rigAddr) {
|
|
34
|
+
// FE FE [rig] [ctr] 0x1C 0x03 FD
|
|
35
|
+
return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x1c, 0x03, 0xfd]);
|
|
36
|
+
},
|
|
37
|
+
readTransceiverState(ctrAddr, rigAddr) {
|
|
38
|
+
// FE FE [rig] [ctr] 0x1A 0x00 0x48 FD (not recommended by Java, but available)
|
|
39
|
+
return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x1a, 0x00, 0x48, 0xfd]);
|
|
40
|
+
},
|
|
41
|
+
readBandEdges(ctrAddr, rigAddr) {
|
|
42
|
+
// FE FE [rig] [ctr] 0x02 FD
|
|
43
|
+
return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x02, 0xfd]);
|
|
44
|
+
},
|
|
45
|
+
setOperationDataMode(ctrAddr, rigAddr, mode) {
|
|
46
|
+
// FE FE [rig] [ctr] 0x26 0x00 [mode] 0x01 0x01 FD
|
|
47
|
+
return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x26, 0x00, mode & 0xff, 0x01, 0x01, 0xfd]);
|
|
48
|
+
},
|
|
49
|
+
getSWRState(ctrAddr, rigAddr) {
|
|
50
|
+
// FE FE [rig] [ctr] 0x15 0x12 FD
|
|
51
|
+
return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x15, 0x12, 0xfd]);
|
|
52
|
+
},
|
|
53
|
+
getALCState(ctrAddr, rigAddr) {
|
|
54
|
+
// FE FE [rig] [ctr] 0x15 0x13 FD
|
|
55
|
+
return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x15, 0x13, 0xfd]);
|
|
56
|
+
},
|
|
57
|
+
getLevelMeter(ctrAddr, rigAddr) {
|
|
58
|
+
// FE FE [rig] [ctr] 0x15 0x02 FD
|
|
59
|
+
return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x15, 0x02, 0xfd]);
|
|
60
|
+
},
|
|
61
|
+
getConnectorWLanLevel(ctrAddr, rigAddr) {
|
|
62
|
+
// FE FE [rig] [ctr] 0x1A 0x05 0x01 0x17 FD
|
|
63
|
+
return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x1a, 0x05, 0x01, 0x17, 0xfd]);
|
|
64
|
+
},
|
|
65
|
+
setConnectorWLanLevel(ctrAddr, rigAddr, level) {
|
|
66
|
+
// FE FE [rig] [ctr] 0x1A 0x05 0x01 0x17 [level_hi] [level_lo] FD
|
|
67
|
+
return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x1a, 0x05, 0x01, 0x17, (level >> 8) & 0xff, level & 0xff, 0xfd]);
|
|
68
|
+
},
|
|
69
|
+
setConnectorDataMode(ctrAddr, rigAddr, mode) {
|
|
70
|
+
// FE FE [rig] [ctr] 0x1A 0x05 0x01 0x19 [mode] FD
|
|
71
|
+
return Buffer.from([0xfe, 0xfe, rigAddr & 0xff, ctrAddr & 0xff, 0x1a, 0x05, 0x01, 0x19, mode & 0xff, 0xfd]);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import dgram from 'dgram';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
export interface UdpClientEvents {
|
|
4
|
+
data: (rinfo: dgram.RemoteInfo, data: Buffer) => void;
|
|
5
|
+
error: (err: Error) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare class UdpClient extends EventEmitter {
|
|
8
|
+
private socket?;
|
|
9
|
+
private _localPort;
|
|
10
|
+
get localPort(): number;
|
|
11
|
+
open(localPort?: number): void;
|
|
12
|
+
close(): void;
|
|
13
|
+
send(buf: Buffer, ip: string, port: number): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.UdpClient = void 0;
|
|
7
|
+
const dgram_1 = __importDefault(require("dgram"));
|
|
8
|
+
const events_1 = require("events");
|
|
9
|
+
class UdpClient extends events_1.EventEmitter {
|
|
10
|
+
constructor() {
|
|
11
|
+
super(...arguments);
|
|
12
|
+
this._localPort = 0;
|
|
13
|
+
}
|
|
14
|
+
get localPort() { return this._localPort; }
|
|
15
|
+
open(localPort) {
|
|
16
|
+
if (this.socket)
|
|
17
|
+
return;
|
|
18
|
+
this.socket = dgram_1.default.createSocket('udp4');
|
|
19
|
+
const sock = this.socket;
|
|
20
|
+
sock.on('message', (msg, rinfo) => this.emit('data', rinfo, Buffer.from(msg)));
|
|
21
|
+
sock.on('error', (err) => this.emit('error', err));
|
|
22
|
+
sock.bind(localPort ?? 0, () => {
|
|
23
|
+
if (!sock)
|
|
24
|
+
return;
|
|
25
|
+
const addr = sock.address();
|
|
26
|
+
if (typeof addr === 'object')
|
|
27
|
+
this._localPort = addr.port;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
close() {
|
|
31
|
+
if (!this.socket)
|
|
32
|
+
return;
|
|
33
|
+
try {
|
|
34
|
+
this.socket.close();
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
this.socket = undefined;
|
|
38
|
+
this._localPort = 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
send(buf, ip, port) {
|
|
42
|
+
if (!this.socket)
|
|
43
|
+
throw new Error('UDP socket not opened');
|
|
44
|
+
this.socket.send(buf, port, ip);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.UdpClient = UdpClient;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { MODE_MAP, CONNECTOR_MODE_MAP } from './rig/IcomConstants';
|
|
3
|
+
export type UdpAddress = {
|
|
4
|
+
ip: string;
|
|
5
|
+
port: number;
|
|
6
|
+
};
|
|
7
|
+
export interface IcomCredentials {
|
|
8
|
+
userName: string;
|
|
9
|
+
password: string;
|
|
10
|
+
}
|
|
11
|
+
export interface IcomRigOptions extends IcomCredentials {
|
|
12
|
+
control: UdpAddress;
|
|
13
|
+
}
|
|
14
|
+
export interface CivCommand {
|
|
15
|
+
bytes: Buffer;
|
|
16
|
+
}
|
|
17
|
+
export interface AudioFrame {
|
|
18
|
+
pcm16: Buffer;
|
|
19
|
+
}
|
|
20
|
+
export interface LoginResult {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
errorCode?: number;
|
|
23
|
+
connection?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface StatusInfo {
|
|
26
|
+
civPort: number;
|
|
27
|
+
audioPort: number;
|
|
28
|
+
authOK: boolean;
|
|
29
|
+
connected: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface CapabilitiesInfo {
|
|
32
|
+
civAddress?: number;
|
|
33
|
+
audioName?: string;
|
|
34
|
+
supportTX?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface IcomRigEvents {
|
|
37
|
+
login: (res: LoginResult) => void;
|
|
38
|
+
status: (s: StatusInfo) => void;
|
|
39
|
+
capabilities: (c: CapabilitiesInfo) => void;
|
|
40
|
+
civ: (data: Buffer) => void;
|
|
41
|
+
civFrame: (frame: Buffer) => void;
|
|
42
|
+
audio: (frame: AudioFrame) => void;
|
|
43
|
+
error: (err: Error) => void;
|
|
44
|
+
}
|
|
45
|
+
export interface Disposable {
|
|
46
|
+
dispose(): void;
|
|
47
|
+
}
|
|
48
|
+
export type RigEventEmitter = EventEmitter & {
|
|
49
|
+
on<U extends keyof IcomRigEvents>(event: U, listener: IcomRigEvents[U]): RigEventEmitter;
|
|
50
|
+
emit<U extends keyof IcomRigEvents>(event: U, ...args: Parameters<IcomRigEvents[U]>): boolean;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Operating mode for ICOM radios
|
|
54
|
+
* LSB, USB, AM, CW, RTTY, FM, WFM, CW_R, RTTY_R, DV
|
|
55
|
+
*/
|
|
56
|
+
export type IcomMode = keyof typeof MODE_MAP;
|
|
57
|
+
/**
|
|
58
|
+
* Connector data routing mode
|
|
59
|
+
* MIC: Microphone, ACC: Accessory port, USB: USB audio, WLAN: Network audio
|
|
60
|
+
*/
|
|
61
|
+
export type ConnectorDataMode = keyof typeof CONNECTOR_MODE_MAP;
|
|
62
|
+
/**
|
|
63
|
+
* Options for setting operating mode
|
|
64
|
+
*/
|
|
65
|
+
export interface SetModeOptions {
|
|
66
|
+
/**
|
|
67
|
+
* Enable data mode (e.g., USB-D for digital modes)
|
|
68
|
+
* Uses CI-V command 0x26 instead of 0x06
|
|
69
|
+
*/
|
|
70
|
+
dataMode?: boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Options for query operations (frequency, meters, etc.)
|
|
74
|
+
*/
|
|
75
|
+
export interface QueryOptions {
|
|
76
|
+
/**
|
|
77
|
+
* Timeout in milliseconds (default: 3000)
|
|
78
|
+
*/
|
|
79
|
+
timeout?: number;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Result of a meter reading operation (SWR, ALC, etc.)
|
|
83
|
+
* @deprecated Use specific types like SwrReading, AlcReading instead
|
|
84
|
+
*/
|
|
85
|
+
export interface MeterReading {
|
|
86
|
+
/**
|
|
87
|
+
* Raw meter value as buffer
|
|
88
|
+
*/
|
|
89
|
+
value: Buffer;
|
|
90
|
+
/**
|
|
91
|
+
* Whether the reading was successful
|
|
92
|
+
*/
|
|
93
|
+
success: boolean;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* SWR (Standing Wave Ratio) meter reading
|
|
97
|
+
* Represents antenna impedance matching quality
|
|
98
|
+
*/
|
|
99
|
+
export interface SwrReading {
|
|
100
|
+
/**
|
|
101
|
+
* Raw BCD value (0-300+)
|
|
102
|
+
* Actual SWR = raw / 100
|
|
103
|
+
* Example: 120 = SWR 1.2, 250 = SWR 2.5
|
|
104
|
+
*/
|
|
105
|
+
raw: number;
|
|
106
|
+
/**
|
|
107
|
+
* Calculated SWR value (typically 1.0 - 3.0)
|
|
108
|
+
* 1.0 = perfect match, >2.0 = poor match
|
|
109
|
+
*/
|
|
110
|
+
swr: number;
|
|
111
|
+
/**
|
|
112
|
+
* Alert flag when SWR is too high (≥1.2)
|
|
113
|
+
* High SWR can damage transmitter
|
|
114
|
+
*/
|
|
115
|
+
alert: boolean;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* ALC (Automatic Level Control) meter reading
|
|
119
|
+
* Represents transmitter output power control level
|
|
120
|
+
*/
|
|
121
|
+
export interface AlcReading {
|
|
122
|
+
/**
|
|
123
|
+
* Raw BCD value (0-255)
|
|
124
|
+
*/
|
|
125
|
+
raw: number;
|
|
126
|
+
/**
|
|
127
|
+
* Percentage of maximum ALC (0-100%)
|
|
128
|
+
*/
|
|
129
|
+
percent: number;
|
|
130
|
+
/**
|
|
131
|
+
* Alert flag when ALC exceeds safe threshold (>120)
|
|
132
|
+
* Excessive ALC indicates over-driving the transmitter
|
|
133
|
+
*/
|
|
134
|
+
alert: boolean;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* WLAN connector audio level reading
|
|
138
|
+
* Represents network audio signal strength/quality
|
|
139
|
+
*/
|
|
140
|
+
export interface WlanLevelReading {
|
|
141
|
+
/**
|
|
142
|
+
* Raw value (0-255)
|
|
143
|
+
* Higher values indicate stronger signal
|
|
144
|
+
*/
|
|
145
|
+
raw: number;
|
|
146
|
+
/**
|
|
147
|
+
* Percentage of maximum level (0-100%)
|
|
148
|
+
*/
|
|
149
|
+
percent: number;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Generic level meter (0-255) reading
|
|
153
|
+
* For CI-V 0x15/0x02 experimental level meter
|
|
154
|
+
*/
|
|
155
|
+
export interface LevelMeterReading {
|
|
156
|
+
/** Raw 0-255 value */
|
|
157
|
+
raw: number;
|
|
158
|
+
/** Percentage (0-100%) */
|
|
159
|
+
percent: number;
|
|
160
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BCD (Binary Coded Decimal) parsing utilities
|
|
3
|
+
* Based on FT8CN's IcomRigConstant.java twoByteBcdToInt()
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Parse a two-byte BCD encoded value to integer
|
|
7
|
+
*
|
|
8
|
+
* BCD format encodes decimal digits as nibbles (4-bit values):
|
|
9
|
+
* - Byte 0: thousands (high nibble) + hundreds (low nibble)
|
|
10
|
+
* - Byte 1: tens (high nibble) + ones (low nibble)
|
|
11
|
+
*
|
|
12
|
+
* @param data - Buffer containing at least 2 bytes of BCD data
|
|
13
|
+
* @returns Parsed integer value, or 0 if data is invalid
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // Buffer [0x12, 0x34] represents 1234
|
|
17
|
+
* const value = parseTwoByteBcd(Buffer.from([0x12, 0x34]));
|
|
18
|
+
* console.log(value); // 1234
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // Buffer [0x02, 0x40] represents 240 (SWR 2.40)
|
|
22
|
+
* const swr = parseTwoByteBcd(Buffer.from([0x02, 0x40]));
|
|
23
|
+
* console.log(swr / 100); // 2.40
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseTwoByteBcd(data: Buffer): number;
|
|
26
|
+
/**
|
|
27
|
+
* Convert integer to two-byte BCD format
|
|
28
|
+
*
|
|
29
|
+
* @param value - Integer value (0-9999)
|
|
30
|
+
* @returns Buffer containing 2 bytes of BCD data
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* const bcd = intToTwoByteBcd(1234);
|
|
34
|
+
* console.log(bcd); // Buffer [0x12, 0x34]
|
|
35
|
+
*/
|
|
36
|
+
export declare function intToTwoByteBcd(value: number): Buffer;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* BCD (Binary Coded Decimal) parsing utilities
|
|
4
|
+
* Based on FT8CN's IcomRigConstant.java twoByteBcdToInt()
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.parseTwoByteBcd = parseTwoByteBcd;
|
|
8
|
+
exports.intToTwoByteBcd = intToTwoByteBcd;
|
|
9
|
+
/**
|
|
10
|
+
* Parse a two-byte BCD encoded value to integer
|
|
11
|
+
*
|
|
12
|
+
* BCD format encodes decimal digits as nibbles (4-bit values):
|
|
13
|
+
* - Byte 0: thousands (high nibble) + hundreds (low nibble)
|
|
14
|
+
* - Byte 1: tens (high nibble) + ones (low nibble)
|
|
15
|
+
*
|
|
16
|
+
* @param data - Buffer containing at least 2 bytes of BCD data
|
|
17
|
+
* @returns Parsed integer value, or 0 if data is invalid
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Buffer [0x12, 0x34] represents 1234
|
|
21
|
+
* const value = parseTwoByteBcd(Buffer.from([0x12, 0x34]));
|
|
22
|
+
* console.log(value); // 1234
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // Buffer [0x02, 0x40] represents 240 (SWR 2.40)
|
|
26
|
+
* const swr = parseTwoByteBcd(Buffer.from([0x02, 0x40]));
|
|
27
|
+
* console.log(swr / 100); // 2.40
|
|
28
|
+
*/
|
|
29
|
+
function parseTwoByteBcd(data) {
|
|
30
|
+
if (data.length < 2)
|
|
31
|
+
return 0;
|
|
32
|
+
// Extract nibbles from each byte
|
|
33
|
+
const ones = data[1] & 0x0f; // Low nibble of byte 1
|
|
34
|
+
const tens = (data[1] >> 4) & 0x0f; // High nibble of byte 1
|
|
35
|
+
const hundreds = data[0] & 0x0f; // Low nibble of byte 0
|
|
36
|
+
const thousands = (data[0] >> 4) & 0x0f; // High nibble of byte 0
|
|
37
|
+
return ones + tens * 10 + hundreds * 100 + thousands * 1000;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Convert integer to two-byte BCD format
|
|
41
|
+
*
|
|
42
|
+
* @param value - Integer value (0-9999)
|
|
43
|
+
* @returns Buffer containing 2 bytes of BCD data
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* const bcd = intToTwoByteBcd(1234);
|
|
47
|
+
* console.log(bcd); // Buffer [0x12, 0x34]
|
|
48
|
+
*/
|
|
49
|
+
function intToTwoByteBcd(value) {
|
|
50
|
+
// Clamp to valid range
|
|
51
|
+
const val = Math.max(0, Math.min(9999, Math.floor(value)));
|
|
52
|
+
const ones = val % 10;
|
|
53
|
+
const tens = Math.floor((val % 100) / 10);
|
|
54
|
+
const hundreds = Math.floor((val % 1000) / 100);
|
|
55
|
+
const thousands = Math.floor(val / 1000);
|
|
56
|
+
const byte0 = (thousands << 4) | hundreds;
|
|
57
|
+
const byte1 = (tens << 4) | ones;
|
|
58
|
+
return Buffer.from([byte0, byte1]);
|
|
59
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const be16: {
|
|
2
|
+
read: (buf: Buffer, off: number) => number;
|
|
3
|
+
write: (buf: Buffer, off: number, v: number) => number;
|
|
4
|
+
};
|
|
5
|
+
export declare const be32: {
|
|
6
|
+
read: (buf: Buffer, off: number) => number;
|
|
7
|
+
write: (buf: Buffer, off: number, v: number) => number;
|
|
8
|
+
};
|
|
9
|
+
export declare const le16: {
|
|
10
|
+
read: (buf: Buffer, off: number) => number;
|
|
11
|
+
write: (buf: Buffer, off: number, v: number) => number;
|
|
12
|
+
};
|
|
13
|
+
export declare const le32: {
|
|
14
|
+
read: (buf: Buffer, off: number) => number;
|
|
15
|
+
write: (buf: Buffer, off: number, v: number) => number;
|
|
16
|
+
};
|
|
17
|
+
export declare function shortToBytesLE(n: number): Buffer;
|
|
18
|
+
export declare function shortToBytesBE(n: number): Buffer;
|
|
19
|
+
export declare function intToBytesLE(n: number): Buffer;
|
|
20
|
+
export declare function intToBytesBE(n: number): Buffer;
|
|
21
|
+
export declare function strToFixedBytes(str: string, len: number): Buffer;
|
|
22
|
+
export declare function hex(buf: Buffer): string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Byte order helpers for Icom packet formats
|
|
3
|
+
// Note: These now use CORRECT naming (be = Big-Endian, le = Little-Endian)
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.le32 = exports.le16 = exports.be32 = exports.be16 = void 0;
|
|
6
|
+
exports.shortToBytesLE = shortToBytesLE;
|
|
7
|
+
exports.shortToBytesBE = shortToBytesBE;
|
|
8
|
+
exports.intToBytesLE = intToBytesLE;
|
|
9
|
+
exports.intToBytesBE = intToBytesBE;
|
|
10
|
+
exports.strToFixedBytes = strToFixedBytes;
|
|
11
|
+
exports.hex = hex;
|
|
12
|
+
exports.be16 = {
|
|
13
|
+
read: (buf, off) => buf.readUInt16BE(off), // Big-Endian: high byte first
|
|
14
|
+
write: (buf, off, v) => buf.writeUInt16BE(v & 0xffff, off)
|
|
15
|
+
};
|
|
16
|
+
exports.be32 = {
|
|
17
|
+
read: (buf, off) => buf.readUInt32BE(off), // Big-Endian: high byte first
|
|
18
|
+
write: (buf, off, v) => buf.writeUInt32BE(v >>> 0, off)
|
|
19
|
+
};
|
|
20
|
+
exports.le16 = {
|
|
21
|
+
read: (buf, off) => buf.readUInt16LE(off), // Little-Endian: low byte first
|
|
22
|
+
write: (buf, off, v) => buf.writeUInt16LE(v & 0xffff, off)
|
|
23
|
+
};
|
|
24
|
+
exports.le32 = {
|
|
25
|
+
read: (buf, off) => buf.readUInt32LE(off), // Little-Endian: low byte first
|
|
26
|
+
write: (buf, off, v) => buf.writeUInt32LE(v >>> 0, off)
|
|
27
|
+
};
|
|
28
|
+
function shortToBytesLE(n) {
|
|
29
|
+
const b = Buffer.alloc(2);
|
|
30
|
+
exports.le16.write(b, 0, n);
|
|
31
|
+
return b;
|
|
32
|
+
}
|
|
33
|
+
function shortToBytesBE(n) {
|
|
34
|
+
const b = Buffer.alloc(2);
|
|
35
|
+
exports.be16.write(b, 0, n);
|
|
36
|
+
return b;
|
|
37
|
+
}
|
|
38
|
+
function intToBytesLE(n) {
|
|
39
|
+
const b = Buffer.alloc(4);
|
|
40
|
+
exports.le32.write(b, 0, n);
|
|
41
|
+
return b;
|
|
42
|
+
}
|
|
43
|
+
function intToBytesBE(n) {
|
|
44
|
+
const b = Buffer.alloc(4);
|
|
45
|
+
exports.be32.write(b, 0, n);
|
|
46
|
+
return b;
|
|
47
|
+
}
|
|
48
|
+
function strToFixedBytes(str, len) {
|
|
49
|
+
const out = Buffer.alloc(len, 0);
|
|
50
|
+
const src = Buffer.from(str, 'utf8');
|
|
51
|
+
src.copy(out, 0, 0, Math.min(len, src.length));
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
function hex(buf) {
|
|
55
|
+
return [...buf].map(b => b.toString(16).padStart(2, '0')).join(' ');
|
|
56
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEBUG_ICOM_LEVEL = exports.DEBUG_ICOM = void 0;
|
|
4
|
+
exports.dbg = dbg;
|
|
5
|
+
exports.dbgV = dbgV;
|
|
6
|
+
exports.DEBUG_ICOM = process.env.DEBUG_ICOM === '1' || process.env.DEBUG_ICOM === 'true';
|
|
7
|
+
exports.DEBUG_ICOM_LEVEL = Number.parseInt(process.env.DEBUG_ICOM_LEVEL || (exports.DEBUG_ICOM ? '1' : '0'), 10) || 0;
|
|
8
|
+
function dbg(...args) {
|
|
9
|
+
if (exports.DEBUG_ICOM_LEVEL >= 1)
|
|
10
|
+
console.log('[icom]', ...args);
|
|
11
|
+
}
|
|
12
|
+
function dbgV(...args) {
|
|
13
|
+
if (exports.DEBUG_ICOM_LEVEL >= 2)
|
|
14
|
+
console.log('[icom]', ...args);
|
|
15
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "icom-wlan-node",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Icom WLAN (CI‑V, audio) protocol implementation for Node.js/TypeScript.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc -p tsconfig.json",
|
|
9
|
+
"clean": "rimraf dist",
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"prepublishOnly": "npm run build",
|
|
12
|
+
"lint": "eslint ."
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["icom", "civ", "cat", "udp", "hamradio", "wlan", "audio"],
|
|
15
|
+
"author": "boybook",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/boybook/icom-wlan-node.git"
|
|
20
|
+
},
|
|
21
|
+
"files": ["dist"],
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/jest": "^29.5.12",
|
|
24
|
+
"@types/node": "^20.10.6",
|
|
25
|
+
"eslint": "^8.57.0",
|
|
26
|
+
"jest": "^29.7.0",
|
|
27
|
+
"rimraf": "^5.0.5",
|
|
28
|
+
"ts-jest": "^29.1.1",
|
|
29
|
+
"typescript": "^5.4.5"
|
|
30
|
+
}
|
|
31
|
+
}
|