tci-client-node 0.2.0 → 0.4.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +50 -6
  3. package/dist/audio/index.cjs +11 -0
  4. package/dist/audio/index.cjs.map +1 -1
  5. package/dist/audio/index.d.cts +4 -2
  6. package/dist/audio/index.d.ts +4 -2
  7. package/dist/audio/index.js +10 -0
  8. package/dist/audio/index.js.map +1 -1
  9. package/dist/dialect/index.cjs +193 -12
  10. package/dist/dialect/index.cjs.map +1 -1
  11. package/dist/dialect/index.d.cts +3 -2
  12. package/dist/dialect/index.d.ts +3 -2
  13. package/dist/dialect/index.js +193 -12
  14. package/dist/dialect/index.js.map +1 -1
  15. package/dist/errors-CT3b8LLw.d.cts +9 -0
  16. package/dist/errors-CT3b8LLw.d.ts +9 -0
  17. package/dist/index.cjs +606 -6
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +68 -6
  20. package/dist/index.d.ts +68 -6
  21. package/dist/index.js +599 -6
  22. package/dist/index.js.map +1 -1
  23. package/dist/meter/index.cjs +348 -0
  24. package/dist/meter/index.cjs.map +1 -0
  25. package/dist/meter/index.d.cts +78 -0
  26. package/dist/meter/index.d.ts +78 -0
  27. package/dist/meter/index.js +317 -0
  28. package/dist/meter/index.js.map +1 -0
  29. package/dist/protocol/index.d.cts +36 -2
  30. package/dist/protocol/index.d.ts +36 -2
  31. package/dist/testing/index.cjs +58 -0
  32. package/dist/testing/index.cjs.map +1 -1
  33. package/dist/testing/index.d.cts +23 -1
  34. package/dist/testing/index.d.ts +23 -1
  35. package/dist/testing/index.js +58 -0
  36. package/dist/testing/index.js.map +1 -1
  37. package/dist/types-C0qVJVSO.d.ts +74 -0
  38. package/dist/types-C4MtLKoy.d.cts +74 -0
  39. package/dist/{types-xRotf9gW.d.cts → types-CYK_NOTz.d.cts} +5 -1
  40. package/dist/{types-9seY9Th-.d.ts → types-DVIPU-VR.d.ts} +5 -1
  41. package/package.json +11 -2
  42. package/dist/index-lbK6NGY4.d.cts +0 -42
  43. package/dist/index-paj1AOJY.d.ts +0 -42
@@ -0,0 +1,317 @@
1
+ // src/meter/types.ts
2
+ var UNKNOWN_TCI_METER_CAPABILITIES = {
3
+ rxLevel: "unknown",
4
+ rxAverageLevel: "unknown",
5
+ rxPeakBin: "unknown",
6
+ txMicLevel: "unknown",
7
+ txRmsPower: "unknown",
8
+ txPeakPower: "unknown",
9
+ txSwr: "unknown",
10
+ txAlcDbfs: "unknown"
11
+ };
12
+ function cloneMeterCapabilities(capabilities) {
13
+ return { ...capabilities };
14
+ }
15
+
16
+ // src/meter/adapters.ts
17
+ var StandardTciMeterAdapter = class {
18
+ declaredCapabilities;
19
+ options;
20
+ constructor(options = {}) {
21
+ this.options = options;
22
+ this.declaredCapabilities = {
23
+ ...UNKNOWN_TCI_METER_CAPABILITIES,
24
+ rxLevel: "declared",
25
+ txMicLevel: "declared",
26
+ txRmsPower: "declared",
27
+ txPeakPower: "declared",
28
+ txSwr: "declared",
29
+ rxAverageLevel: options.supportsRxExtended ? "declared" : "unknown",
30
+ rxPeakBin: options.supportsRxExtended ? "declared" : "unknown",
31
+ txAlcDbfs: options.txAlcUnit === "dbfs" ? "declared" : "unknown",
32
+ ...options.capabilities
33
+ };
34
+ }
35
+ normalizeInterval(intervalMs) {
36
+ const requestedMs = Math.round(intervalMs);
37
+ if (this.options.interval?.fixedMs !== void 0) {
38
+ return { requestedMs, appliedMs: this.options.interval.fixedMs };
39
+ }
40
+ const minMs = this.options.interval?.minMs ?? requestedMs;
41
+ const maxMs = this.options.interval?.maxMs ?? requestedMs;
42
+ const normalized = Math.max(minMs, Math.min(maxMs, requestedMs));
43
+ return {
44
+ requestedMs,
45
+ appliedMs: this.options.interval?.reportsApplied === false ? void 0 : normalized
46
+ };
47
+ }
48
+ buildEnableCommand(kind, enabled, intervalMs) {
49
+ return {
50
+ name: kind === "rx" ? "RX_SENSORS_ENABLE" : "TX_SENSORS_ENABLE",
51
+ args: enabled ? [true, intervalMs] : [false]
52
+ };
53
+ }
54
+ decode(command, receivedAtMs) {
55
+ switch (command.name) {
56
+ case "rx_sensors":
57
+ return decodeRx(command, receivedAtMs, "rx_sensors");
58
+ case "rx_channel_sensors":
59
+ return decodeRx(command, receivedAtMs, "rx_channel_sensors");
60
+ case "rx_channel_sensors_ex":
61
+ return decodeRx(command, receivedAtMs, "rx_channel_sensors_ex");
62
+ case "tx_sensors":
63
+ return decodeTx(command, receivedAtMs, this.options.txAlcUnit);
64
+ default:
65
+ return void 0;
66
+ }
67
+ }
68
+ };
69
+ function createUnknownTciMeterAdapter() {
70
+ return new StandardTciMeterAdapter({
71
+ capabilities: cloneMeterCapabilities(UNKNOWN_TCI_METER_CAPABILITIES),
72
+ interval: { reportsApplied: false }
73
+ });
74
+ }
75
+ function decodeRx(command, receivedAtMs, source) {
76
+ const receiver = integer(command.args[0]);
77
+ const hasChannel = source !== "rx_sensors";
78
+ const channel = hasChannel ? integer(command.args[1]) : 0;
79
+ const levelIndex = hasChannel ? 2 : 1;
80
+ const levelDbm = finite(command.args[levelIndex]);
81
+ if (receiver === void 0 || receiver < 0 || channel === void 0 || channel < 0 || levelDbm === void 0) {
82
+ return { issue: `Invalid ${command.originalName} meter frame: ${command.raw}` };
83
+ }
84
+ const frame = { receiver, channel, levelDbm, source, receivedAtMs };
85
+ if (source === "rx_channel_sensors_ex") {
86
+ const averageLevelDbm = finite(command.args[3]);
87
+ const peakBinDbm = finite(command.args[4]);
88
+ if (averageLevelDbm === void 0 || peakBinDbm === void 0) {
89
+ return { issue: `Invalid ${command.originalName} extended meter frame: ${command.raw}` };
90
+ }
91
+ frame.averageLevelDbm = averageLevelDbm;
92
+ frame.peakBinDbm = peakBinDbm;
93
+ if (command.args.length > 5) frame.extraArgs = command.args.slice(5);
94
+ } else if (command.args.length > levelIndex + 1) {
95
+ frame.extraArgs = command.args.slice(levelIndex + 1);
96
+ }
97
+ return { decoded: { kind: "rx", frame } };
98
+ }
99
+ function decodeTx(command, receivedAtMs, alcUnit) {
100
+ const trx = integer(command.args[0]);
101
+ if (trx === void 0 || trx < 0) {
102
+ return { issue: `Invalid ${command.originalName} transmitter index: ${command.raw}` };
103
+ }
104
+ const micLevelDbm = optionalFinite(command.args[1]);
105
+ const rmsPowerWatts = optionalFinite(command.args[2]);
106
+ const peakPowerWatts = optionalFinite(command.args[3]);
107
+ const swr = optionalFinite(command.args[4]);
108
+ const alcValue = alcUnit ? optionalFinite(command.args[5]) : void 0;
109
+ if (micLevelDbm.invalid || rmsPowerWatts.invalid || peakPowerWatts.invalid || swr.invalid || alcValue?.invalid) {
110
+ return { issue: `Invalid ${command.originalName} numeric meter frame: ${command.raw}` };
111
+ }
112
+ if (rmsPowerWatts.value !== void 0 && rmsPowerWatts.value < 0 || peakPowerWatts.value !== void 0 && peakPowerWatts.value < 0 || swr.value !== void 0 && swr.value < 1) {
113
+ return { issue: `Out-of-range ${command.originalName} meter frame: ${command.raw}` };
114
+ }
115
+ if (micLevelDbm.value === void 0 && rmsPowerWatts.value === void 0 && peakPowerWatts.value === void 0 && swr.value === void 0 && alcValue?.value === void 0) {
116
+ return { issue: `Empty ${command.originalName} meter frame: ${command.raw}` };
117
+ }
118
+ const frame = {
119
+ trx,
120
+ micLevelDbm: micLevelDbm.value,
121
+ rmsPowerWatts: rmsPowerWatts.value,
122
+ peakPowerWatts: peakPowerWatts.value,
123
+ swr: swr.value,
124
+ receivedAtMs
125
+ };
126
+ if (alcUnit && alcValue?.value !== void 0) frame.alc = { value: alcValue.value, unit: alcUnit };
127
+ const knownArgs = alcUnit ? 6 : 5;
128
+ if (command.args.length > knownArgs) frame.extraArgs = command.args.slice(knownArgs);
129
+ return { decoded: { kind: "tx", frame } };
130
+ }
131
+ function finite(value) {
132
+ if (value === void 0 || value === "") return void 0;
133
+ const parsed = Number(value);
134
+ return Number.isFinite(parsed) ? parsed : void 0;
135
+ }
136
+ function integer(value) {
137
+ const parsed = finite(value);
138
+ return parsed !== void 0 && Number.isInteger(parsed) ? parsed : void 0;
139
+ }
140
+ function optionalFinite(value) {
141
+ if (value === void 0 || value === "") return { invalid: false };
142
+ const parsed = Number(value);
143
+ return Number.isFinite(parsed) ? { value: parsed, invalid: false } : { invalid: true };
144
+ }
145
+
146
+ // src/meter/TciMeterStreamSession.ts
147
+ import { EventEmitter } from "eventemitter3";
148
+
149
+ // src/errors.ts
150
+ var TciError = class extends Error {
151
+ code;
152
+ details;
153
+ constructor(code, message, details) {
154
+ super(message);
155
+ this.name = "TciError";
156
+ this.code = code;
157
+ this.details = details;
158
+ }
159
+ };
160
+
161
+ // src/meter/TciMeterStreamSession.ts
162
+ var RX_COALESCE_MS = 20;
163
+ var TciMeterStreamSession = class extends EventEmitter {
164
+ receiver;
165
+ channel;
166
+ trx;
167
+ requestedIntervalMs;
168
+ appliedIntervalMs;
169
+ rxEnabled;
170
+ txEnabled;
171
+ adapter;
172
+ callbacks;
173
+ capabilities;
174
+ pendingRx = /* @__PURE__ */ new Map();
175
+ closed = false;
176
+ constructor(options) {
177
+ super();
178
+ this.receiver = options.receiver;
179
+ this.channel = options.channel;
180
+ this.trx = options.trx;
181
+ this.requestedIntervalMs = options.requestedIntervalMs;
182
+ this.appliedIntervalMs = options.appliedIntervalMs;
183
+ this.rxEnabled = options.rxEnabled;
184
+ this.txEnabled = options.txEnabled;
185
+ this.adapter = options.adapter;
186
+ this.callbacks = options.callbacks;
187
+ this.capabilities = cloneMeterCapabilities(options.adapter.declaredCapabilities);
188
+ }
189
+ getCapabilities() {
190
+ return cloneMeterCapabilities(this.capabilities);
191
+ }
192
+ async close() {
193
+ if (this.closed) return;
194
+ this.closed = true;
195
+ this.clearPendingRx();
196
+ try {
197
+ await this.callbacks.close();
198
+ } finally {
199
+ this.emit("closed");
200
+ this.removeAllListeners();
201
+ }
202
+ }
203
+ _acceptCommand(command, receivedAtMs) {
204
+ if (this.closed) return;
205
+ this.acceptEnableAcknowledgement(command);
206
+ const result = this.adapter.decode(command, receivedAtMs);
207
+ if (!result) return;
208
+ if (result.issue) {
209
+ this.emit("error", new TciError("protocol-error", result.issue));
210
+ return;
211
+ }
212
+ if (result.decoded?.kind === "rx") this.acceptRxFrame(result.decoded.frame);
213
+ if (result.decoded?.kind === "tx") this.acceptTxFrame(result.decoded.frame);
214
+ }
215
+ _fail(error) {
216
+ if (this.closed) return;
217
+ this.closed = true;
218
+ this.clearPendingRx();
219
+ this.emit("error", error);
220
+ this.emit("closed");
221
+ this.removeAllListeners();
222
+ }
223
+ acceptRxFrame(frame) {
224
+ if (!this.rxEnabled || frame.receiver !== this.receiver || frame.channel !== this.channel) return;
225
+ this.observe("rxLevel");
226
+ if (frame.averageLevelDbm !== void 0) this.observe("rxAverageLevel");
227
+ if (frame.peakBinDbm !== void 0) this.observe("rxPeakBin");
228
+ const key = `${frame.receiver}:${frame.channel}`;
229
+ const pending = this.pendingRx.get(key);
230
+ if (pending) {
231
+ const sameReading = Math.abs(pending.frame.levelDbm - frame.levelDbm) < 0.05;
232
+ if (sameReading && rxPriority(frame) >= rxPriority(pending.frame)) {
233
+ clearTimeout(pending.timer);
234
+ this.pendingRx.delete(key);
235
+ } else if (sameReading) {
236
+ return;
237
+ } else {
238
+ this.flushRx(key, pending);
239
+ }
240
+ }
241
+ const timer = setTimeout(() => {
242
+ const current = this.pendingRx.get(key);
243
+ if (current?.frame === frame) this.flushRx(key, current);
244
+ }, RX_COALESCE_MS);
245
+ this.pendingRx.set(key, { frame, timer });
246
+ }
247
+ acceptTxFrame(frame) {
248
+ if (!this.txEnabled || frame.trx !== this.trx) return;
249
+ if (frame.micLevelDbm !== void 0) this.observe("txMicLevel");
250
+ if (frame.rmsPowerWatts !== void 0) this.observe("txRmsPower");
251
+ if (frame.peakPowerWatts !== void 0) this.observe("txPeakPower");
252
+ if (frame.swr !== void 0) this.observe("txSwr");
253
+ if (frame.alc?.unit === "dbfs") this.observe("txAlcDbfs");
254
+ this.emit("txFrame", frame);
255
+ }
256
+ acceptEnableAcknowledgement(command) {
257
+ const enabled = command.args[0]?.toLowerCase();
258
+ if (enabled !== "true" && enabled !== "1" && enabled !== "on") return;
259
+ if (command.name === "rx_sensors_enable" && this.rxEnabled) {
260
+ this.acknowledge(["rxLevel"]);
261
+ }
262
+ if (command.name === "tx_sensors_enable" && this.txEnabled) {
263
+ this.acknowledge(["txMicLevel", "txRmsPower", "txPeakPower", "txSwr"]);
264
+ }
265
+ }
266
+ acknowledge(keys) {
267
+ let changed = false;
268
+ for (const key of keys) {
269
+ if (supportRank(this.capabilities[key]) < supportRank("acknowledged")) {
270
+ this.capabilities[key] = "acknowledged";
271
+ changed = true;
272
+ }
273
+ }
274
+ if (changed) this.emit("capabilitiesChanged", this.getCapabilities());
275
+ }
276
+ observe(key) {
277
+ if (this.capabilities[key] === "observed") return;
278
+ this.capabilities[key] = "observed";
279
+ this.emit("capabilitiesChanged", this.getCapabilities());
280
+ }
281
+ flushRx(key, pending) {
282
+ clearTimeout(pending.timer);
283
+ if (this.pendingRx.get(key) === pending) this.pendingRx.delete(key);
284
+ this.emit("rxFrame", pending.frame);
285
+ }
286
+ clearPendingRx() {
287
+ for (const pending of this.pendingRx.values()) clearTimeout(pending.timer);
288
+ this.pendingRx.clear();
289
+ }
290
+ };
291
+ function rxPriority(frame) {
292
+ if (frame.source === "rx_channel_sensors_ex") return 3;
293
+ if (frame.source === "rx_channel_sensors") return 2;
294
+ return 1;
295
+ }
296
+ function supportRank(value) {
297
+ switch (value) {
298
+ case "unsupported":
299
+ return -1;
300
+ case "unknown":
301
+ return 0;
302
+ case "declared":
303
+ return 1;
304
+ case "acknowledged":
305
+ return 2;
306
+ case "observed":
307
+ return 3;
308
+ }
309
+ }
310
+ export {
311
+ StandardTciMeterAdapter,
312
+ TciMeterStreamSession,
313
+ UNKNOWN_TCI_METER_CAPABILITIES,
314
+ cloneMeterCapabilities,
315
+ createUnknownTciMeterAdapter
316
+ };
317
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/meter/types.ts","../../src/meter/adapters.ts","../../src/meter/TciMeterStreamSession.ts","../../src/errors.ts"],"sourcesContent":["import type { TciCommand } from '../protocol/index.js';\n\nexport type TciMeterSupport =\n | 'unknown'\n | 'declared'\n | 'acknowledged'\n | 'observed'\n | 'unsupported';\n\nexport interface TciMeterCapabilities {\n rxLevel: TciMeterSupport;\n rxAverageLevel: TciMeterSupport;\n rxPeakBin: TciMeterSupport;\n txMicLevel: TciMeterSupport;\n txRmsPower: TciMeterSupport;\n txPeakPower: TciMeterSupport;\n txSwr: TciMeterSupport;\n txAlcDbfs: TciMeterSupport;\n}\n\nexport type TciRxMeterSource = 'rx_sensors' | 'rx_channel_sensors' | 'rx_channel_sensors_ex';\n\nexport interface TciRxMeterFrame {\n receiver: number;\n channel: number;\n levelDbm: number;\n averageLevelDbm?: number;\n peakBinDbm?: number;\n source: TciRxMeterSource;\n receivedAtMs: number;\n extraArgs?: readonly string[];\n}\n\nexport interface TciTxMeterFrame {\n trx: number;\n micLevelDbm?: number;\n rmsPowerWatts?: number;\n peakPowerWatts?: number;\n swr?: number;\n alc?: { value: number; unit: 'dbfs' | 'percent' };\n receivedAtMs: number;\n extraArgs?: readonly string[];\n}\n\nexport interface TciMeterStreamOptions {\n receiver?: number;\n channel?: number;\n trx?: number;\n rx?: boolean;\n tx?: boolean;\n intervalMs?: number;\n}\n\nexport interface TciMeterInterval {\n requestedMs: number;\n appliedMs?: number;\n}\n\nexport interface TciMeterCommand {\n name: string;\n args: readonly unknown[];\n}\n\nexport type TciMeterDecodedFrame =\n | { kind: 'rx'; frame: TciRxMeterFrame }\n | { kind: 'tx'; frame: TciTxMeterFrame };\n\nexport interface TciMeterDecodeResult {\n decoded?: TciMeterDecodedFrame;\n issue?: string;\n}\n\nexport interface TciMeterAdapter {\n readonly declaredCapabilities: TciMeterCapabilities;\n normalizeInterval(intervalMs: number): TciMeterInterval;\n buildEnableCommand(kind: 'rx' | 'tx', enabled: boolean, intervalMs: number): TciMeterCommand;\n decode(command: TciCommand, receivedAtMs: number): TciMeterDecodeResult | undefined;\n}\n\nexport const UNKNOWN_TCI_METER_CAPABILITIES: TciMeterCapabilities = {\n rxLevel: 'unknown',\n rxAverageLevel: 'unknown',\n rxPeakBin: 'unknown',\n txMicLevel: 'unknown',\n txRmsPower: 'unknown',\n txPeakPower: 'unknown',\n txSwr: 'unknown',\n txAlcDbfs: 'unknown',\n};\n\nexport function cloneMeterCapabilities(capabilities: TciMeterCapabilities): TciMeterCapabilities {\n return { ...capabilities };\n}\n","import type { TciCommand } from '../protocol/index.js';\nimport {\n cloneMeterCapabilities,\n UNKNOWN_TCI_METER_CAPABILITIES,\n type TciMeterAdapter,\n type TciMeterCapabilities,\n type TciMeterDecodeResult,\n type TciMeterInterval,\n type TciRxMeterFrame,\n type TciTxMeterFrame,\n} from './types.js';\n\ninterface StandardTciMeterAdapterOptions {\n capabilities?: Partial<TciMeterCapabilities>;\n interval?: { minMs?: number; maxMs?: number; fixedMs?: number; reportsApplied?: boolean };\n supportsRxExtended?: boolean;\n txAlcUnit?: 'dbfs' | 'percent';\n}\n\nexport class StandardTciMeterAdapter implements TciMeterAdapter {\n readonly declaredCapabilities: TciMeterCapabilities;\n private readonly options: StandardTciMeterAdapterOptions;\n\n constructor(options: StandardTciMeterAdapterOptions = {}) {\n this.options = options;\n this.declaredCapabilities = {\n ...UNKNOWN_TCI_METER_CAPABILITIES,\n rxLevel: 'declared',\n txMicLevel: 'declared',\n txRmsPower: 'declared',\n txPeakPower: 'declared',\n txSwr: 'declared',\n rxAverageLevel: options.supportsRxExtended ? 'declared' : 'unknown',\n rxPeakBin: options.supportsRxExtended ? 'declared' : 'unknown',\n txAlcDbfs: options.txAlcUnit === 'dbfs' ? 'declared' : 'unknown',\n ...options.capabilities,\n };\n }\n\n normalizeInterval(intervalMs: number): TciMeterInterval {\n const requestedMs = Math.round(intervalMs);\n if (this.options.interval?.fixedMs !== undefined) {\n return { requestedMs, appliedMs: this.options.interval.fixedMs };\n }\n const minMs = this.options.interval?.minMs ?? requestedMs;\n const maxMs = this.options.interval?.maxMs ?? requestedMs;\n const normalized = Math.max(minMs, Math.min(maxMs, requestedMs));\n return {\n requestedMs,\n appliedMs: this.options.interval?.reportsApplied === false ? undefined : normalized,\n };\n }\n\n buildEnableCommand(kind: 'rx' | 'tx', enabled: boolean, intervalMs: number) {\n return {\n name: kind === 'rx' ? 'RX_SENSORS_ENABLE' : 'TX_SENSORS_ENABLE',\n args: enabled ? [true, intervalMs] : [false],\n };\n }\n\n decode(command: TciCommand, receivedAtMs: number): TciMeterDecodeResult | undefined {\n switch (command.name) {\n case 'rx_sensors':\n return decodeRx(command, receivedAtMs, 'rx_sensors');\n case 'rx_channel_sensors':\n return decodeRx(command, receivedAtMs, 'rx_channel_sensors');\n case 'rx_channel_sensors_ex':\n return decodeRx(command, receivedAtMs, 'rx_channel_sensors_ex');\n case 'tx_sensors':\n return decodeTx(command, receivedAtMs, this.options.txAlcUnit);\n default:\n return undefined;\n }\n }\n}\n\nexport function createUnknownTciMeterAdapter(): TciMeterAdapter {\n return new StandardTciMeterAdapter({\n capabilities: cloneMeterCapabilities(UNKNOWN_TCI_METER_CAPABILITIES),\n interval: { reportsApplied: false },\n });\n}\n\nfunction decodeRx(\n command: TciCommand,\n receivedAtMs: number,\n source: TciRxMeterFrame['source'],\n): TciMeterDecodeResult {\n const receiver = integer(command.args[0]);\n const hasChannel = source !== 'rx_sensors';\n const channel = hasChannel ? integer(command.args[1]) : 0;\n const levelIndex = hasChannel ? 2 : 1;\n const levelDbm = finite(command.args[levelIndex]);\n if (receiver === undefined || receiver < 0 || channel === undefined || channel < 0 || levelDbm === undefined) {\n return { issue: `Invalid ${command.originalName} meter frame: ${command.raw}` };\n }\n\n const frame: TciRxMeterFrame = { receiver, channel, levelDbm, source, receivedAtMs };\n if (source === 'rx_channel_sensors_ex') {\n const averageLevelDbm = finite(command.args[3]);\n const peakBinDbm = finite(command.args[4]);\n if (averageLevelDbm === undefined || peakBinDbm === undefined) {\n return { issue: `Invalid ${command.originalName} extended meter frame: ${command.raw}` };\n }\n frame.averageLevelDbm = averageLevelDbm;\n frame.peakBinDbm = peakBinDbm;\n if (command.args.length > 5) frame.extraArgs = command.args.slice(5);\n } else if (command.args.length > levelIndex + 1) {\n frame.extraArgs = command.args.slice(levelIndex + 1);\n }\n return { decoded: { kind: 'rx', frame } };\n}\n\nfunction decodeTx(\n command: TciCommand,\n receivedAtMs: number,\n alcUnit?: 'dbfs' | 'percent',\n): TciMeterDecodeResult {\n const trx = integer(command.args[0]);\n if (trx === undefined || trx < 0) {\n return { issue: `Invalid ${command.originalName} transmitter index: ${command.raw}` };\n }\n\n const micLevelDbm = optionalFinite(command.args[1]);\n const rmsPowerWatts = optionalFinite(command.args[2]);\n const peakPowerWatts = optionalFinite(command.args[3]);\n const swr = optionalFinite(command.args[4]);\n const alcValue = alcUnit ? optionalFinite(command.args[5]) : undefined;\n if (micLevelDbm.invalid || rmsPowerWatts.invalid || peakPowerWatts.invalid || swr.invalid || alcValue?.invalid) {\n return { issue: `Invalid ${command.originalName} numeric meter frame: ${command.raw}` };\n }\n if ((rmsPowerWatts.value !== undefined && rmsPowerWatts.value < 0)\n || (peakPowerWatts.value !== undefined && peakPowerWatts.value < 0)\n || (swr.value !== undefined && swr.value < 1)) {\n return { issue: `Out-of-range ${command.originalName} meter frame: ${command.raw}` };\n }\n if (micLevelDbm.value === undefined && rmsPowerWatts.value === undefined\n && peakPowerWatts.value === undefined && swr.value === undefined && alcValue?.value === undefined) {\n return { issue: `Empty ${command.originalName} meter frame: ${command.raw}` };\n }\n\n const frame: TciTxMeterFrame = {\n trx,\n micLevelDbm: micLevelDbm.value,\n rmsPowerWatts: rmsPowerWatts.value,\n peakPowerWatts: peakPowerWatts.value,\n swr: swr.value,\n receivedAtMs,\n };\n if (alcUnit && alcValue?.value !== undefined) frame.alc = { value: alcValue.value, unit: alcUnit };\n const knownArgs = alcUnit ? 6 : 5;\n if (command.args.length > knownArgs) frame.extraArgs = command.args.slice(knownArgs);\n return { decoded: { kind: 'tx', frame } };\n}\n\nfunction finite(value: string | undefined): number | undefined {\n if (value === undefined || value === '') return undefined;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction integer(value: string | undefined): number | undefined {\n const parsed = finite(value);\n return parsed !== undefined && Number.isInteger(parsed) ? parsed : undefined;\n}\n\nfunction optionalFinite(value: string | undefined): { value?: number; invalid: boolean } {\n if (value === undefined || value === '') return { invalid: false };\n const parsed = Number(value);\n return Number.isFinite(parsed) ? { value: parsed, invalid: false } : { invalid: true };\n}\n","import { EventEmitter } from 'eventemitter3';\nimport { TciError } from '../errors.js';\nimport type { TciCommand } from '../protocol/index.js';\nimport {\n cloneMeterCapabilities,\n type TciMeterAdapter,\n type TciMeterCapabilities,\n type TciMeterSupport,\n type TciRxMeterFrame,\n type TciTxMeterFrame,\n} from './types.js';\n\nconst RX_COALESCE_MS = 20;\n\nexport interface TciMeterStreamEvents {\n rxFrame: (frame: TciRxMeterFrame) => void;\n txFrame: (frame: TciTxMeterFrame) => void;\n capabilitiesChanged: (capabilities: TciMeterCapabilities) => void;\n error: (error: TciError) => void;\n closed: () => void;\n}\n\ninterface TciMeterStreamCallbacks {\n close: () => Promise<void>;\n}\n\ninterface PendingRxFrame {\n frame: TciRxMeterFrame;\n timer: NodeJS.Timeout;\n}\n\nexport class TciMeterStreamSession extends EventEmitter<TciMeterStreamEvents> {\n readonly receiver: number;\n readonly channel: number;\n readonly trx: number;\n readonly requestedIntervalMs: number;\n readonly appliedIntervalMs?: number;\n readonly rxEnabled: boolean;\n readonly txEnabled: boolean;\n\n private readonly adapter: TciMeterAdapter;\n private readonly callbacks: TciMeterStreamCallbacks;\n private capabilities: TciMeterCapabilities;\n private readonly pendingRx = new Map<string, PendingRxFrame>();\n private closed = false;\n\n constructor(options: {\n receiver: number;\n channel: number;\n trx: number;\n requestedIntervalMs: number;\n appliedIntervalMs?: number;\n rxEnabled: boolean;\n txEnabled: boolean;\n adapter: TciMeterAdapter;\n callbacks: TciMeterStreamCallbacks;\n }) {\n super();\n this.receiver = options.receiver;\n this.channel = options.channel;\n this.trx = options.trx;\n this.requestedIntervalMs = options.requestedIntervalMs;\n this.appliedIntervalMs = options.appliedIntervalMs;\n this.rxEnabled = options.rxEnabled;\n this.txEnabled = options.txEnabled;\n this.adapter = options.adapter;\n this.callbacks = options.callbacks;\n this.capabilities = cloneMeterCapabilities(options.adapter.declaredCapabilities);\n }\n\n getCapabilities(): TciMeterCapabilities {\n return cloneMeterCapabilities(this.capabilities);\n }\n\n async close(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n this.clearPendingRx();\n try {\n await this.callbacks.close();\n } finally {\n this.emit('closed');\n this.removeAllListeners();\n }\n }\n\n _acceptCommand(command: TciCommand, receivedAtMs: number): void {\n if (this.closed) return;\n this.acceptEnableAcknowledgement(command);\n const result = this.adapter.decode(command, receivedAtMs);\n if (!result) return;\n if (result.issue) {\n this.emit('error', new TciError('protocol-error', result.issue));\n return;\n }\n if (result.decoded?.kind === 'rx') this.acceptRxFrame(result.decoded.frame);\n if (result.decoded?.kind === 'tx') this.acceptTxFrame(result.decoded.frame);\n }\n\n _fail(error: TciError): void {\n if (this.closed) return;\n this.closed = true;\n this.clearPendingRx();\n this.emit('error', error);\n this.emit('closed');\n this.removeAllListeners();\n }\n\n private acceptRxFrame(frame: TciRxMeterFrame): void {\n if (!this.rxEnabled || frame.receiver !== this.receiver || frame.channel !== this.channel) return;\n this.observe('rxLevel');\n if (frame.averageLevelDbm !== undefined) this.observe('rxAverageLevel');\n if (frame.peakBinDbm !== undefined) this.observe('rxPeakBin');\n\n const key = `${frame.receiver}:${frame.channel}`;\n const pending = this.pendingRx.get(key);\n if (pending) {\n const sameReading = Math.abs(pending.frame.levelDbm - frame.levelDbm) < 0.05;\n if (sameReading && rxPriority(frame) >= rxPriority(pending.frame)) {\n clearTimeout(pending.timer);\n this.pendingRx.delete(key);\n } else if (sameReading) {\n return;\n } else {\n this.flushRx(key, pending);\n }\n }\n const timer = setTimeout(() => {\n const current = this.pendingRx.get(key);\n if (current?.frame === frame) this.flushRx(key, current);\n }, RX_COALESCE_MS);\n this.pendingRx.set(key, { frame, timer });\n }\n\n private acceptTxFrame(frame: TciTxMeterFrame): void {\n if (!this.txEnabled || frame.trx !== this.trx) return;\n if (frame.micLevelDbm !== undefined) this.observe('txMicLevel');\n if (frame.rmsPowerWatts !== undefined) this.observe('txRmsPower');\n if (frame.peakPowerWatts !== undefined) this.observe('txPeakPower');\n if (frame.swr !== undefined) this.observe('txSwr');\n if (frame.alc?.unit === 'dbfs') this.observe('txAlcDbfs');\n this.emit('txFrame', frame);\n }\n\n private acceptEnableAcknowledgement(command: TciCommand): void {\n const enabled = command.args[0]?.toLowerCase();\n if (enabled !== 'true' && enabled !== '1' && enabled !== 'on') return;\n if (command.name === 'rx_sensors_enable' && this.rxEnabled) {\n this.acknowledge(['rxLevel']);\n }\n if (command.name === 'tx_sensors_enable' && this.txEnabled) {\n this.acknowledge(['txMicLevel', 'txRmsPower', 'txPeakPower', 'txSwr']);\n }\n }\n\n private acknowledge(keys: Array<keyof TciMeterCapabilities>): void {\n let changed = false;\n for (const key of keys) {\n if (supportRank(this.capabilities[key]) < supportRank('acknowledged')) {\n this.capabilities[key] = 'acknowledged';\n changed = true;\n }\n }\n if (changed) this.emit('capabilitiesChanged', this.getCapabilities());\n }\n\n private observe(key: keyof TciMeterCapabilities): void {\n if (this.capabilities[key] === 'observed') return;\n this.capabilities[key] = 'observed';\n this.emit('capabilitiesChanged', this.getCapabilities());\n }\n\n private flushRx(key: string, pending: PendingRxFrame): void {\n clearTimeout(pending.timer);\n if (this.pendingRx.get(key) === pending) this.pendingRx.delete(key);\n this.emit('rxFrame', pending.frame);\n }\n\n private clearPendingRx(): void {\n for (const pending of this.pendingRx.values()) clearTimeout(pending.timer);\n this.pendingRx.clear();\n }\n}\n\nfunction rxPriority(frame: TciRxMeterFrame): number {\n if (frame.source === 'rx_channel_sensors_ex') return 3;\n if (frame.source === 'rx_channel_sensors') return 2;\n return 1;\n}\n\nfunction supportRank(value: TciMeterSupport): number {\n switch (value) {\n case 'unsupported': return -1;\n case 'unknown': return 0;\n case 'declared': return 1;\n case 'acknowledged': return 2;\n case 'observed': return 3;\n }\n}\n","export type TciErrorCode =\n | 'connect-timeout'\n | 'handshake-timeout'\n | 'invalid-handshake'\n | 'unknown-dialect'\n | 'command-timeout'\n | 'not-connected'\n | 'disconnected'\n | 'protocol-error'\n | 'invalid-frame'\n | 'cancelled';\n\nexport class TciError extends Error {\n readonly code: TciErrorCode;\n readonly details?: unknown;\n\n constructor(code: TciErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = 'TciError';\n this.code = code;\n this.details = details;\n }\n}\n\nexport function toTciError(error: unknown, fallbackCode: TciErrorCode = 'protocol-error'): TciError {\n if (error instanceof TciError) {\n return error;\n }\n if (error instanceof Error) {\n return new TciError(fallbackCode, error.message, error);\n }\n return new TciError(fallbackCode, String(error), error);\n}\n"],"mappings":"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","names":[]}
@@ -1,2 +1,36 @@
1
- export { T as TciCommand, a as TciCommandInput, c as commandKey, e as escapeTciText, f as formatTciCommand, i as isCommandReplyTo, n as normalizeCommandName, p as parseTciCommand, b as parseTciText, u as unescapeTciText } from '../text-BwCWY1k1.cjs';
2
- export { Q as QueueCommandOptions, a as QueuedCommandResult, b as TciCommandMatcher, c as TciCommandQueue, d as TciCommandQueueOptions } from '../index-lbK6NGY4.cjs';
1
+ import { T as TciCommand, a as TciCommandInput } from '../text-BwCWY1k1.cjs';
2
+ export { c as commandKey, e as escapeTciText, f as formatTciCommand, i as isCommandReplyTo, n as normalizeCommandName, p as parseTciCommand, b as parseTciText, u as unescapeTciText } from '../text-BwCWY1k1.cjs';
3
+ import { T as TciError } from '../errors-CT3b8LLw.cjs';
4
+
5
+ type TciCommandMatcher = (reply: TciCommand, request: TciCommand) => boolean;
6
+ interface QueueCommandOptions {
7
+ timeoutMs?: number;
8
+ matcher?: TciCommandMatcher;
9
+ signal?: AbortSignal;
10
+ }
11
+ interface QueuedCommandResult {
12
+ request: TciCommand;
13
+ reply: TciCommand;
14
+ }
15
+ interface TciCommandQueueOptions {
16
+ send: (raw: string) => void | Promise<void>;
17
+ timeoutMs?: number;
18
+ }
19
+ declare class TciCommandQueue {
20
+ private readonly send;
21
+ private readonly defaultTimeoutMs;
22
+ private queue;
23
+ private active?;
24
+ private connected;
25
+ constructor(options: TciCommandQueueOptions);
26
+ setConnected(connected: boolean): void;
27
+ enqueue(command: TciCommandInput, options?: QueueCommandOptions): Promise<QueuedCommandResult>;
28
+ handleCommand(commandInput: TciCommandInput): boolean;
29
+ cancelAll(error?: TciError): void;
30
+ get size(): number;
31
+ private pump;
32
+ private finishActive;
33
+ private rejectPending;
34
+ }
35
+
36
+ export { type QueueCommandOptions, type QueuedCommandResult, TciCommand, TciCommandInput, type TciCommandMatcher, TciCommandQueue, type TciCommandQueueOptions };
@@ -1,2 +1,36 @@
1
- export { T as TciCommand, a as TciCommandInput, c as commandKey, e as escapeTciText, f as formatTciCommand, i as isCommandReplyTo, n as normalizeCommandName, p as parseTciCommand, b as parseTciText, u as unescapeTciText } from '../text-BwCWY1k1.js';
2
- export { Q as QueueCommandOptions, a as QueuedCommandResult, b as TciCommandMatcher, c as TciCommandQueue, d as TciCommandQueueOptions } from '../index-paj1AOJY.js';
1
+ import { T as TciCommand, a as TciCommandInput } from '../text-BwCWY1k1.js';
2
+ export { c as commandKey, e as escapeTciText, f as formatTciCommand, i as isCommandReplyTo, n as normalizeCommandName, p as parseTciCommand, b as parseTciText, u as unescapeTciText } from '../text-BwCWY1k1.js';
3
+ import { T as TciError } from '../errors-CT3b8LLw.js';
4
+
5
+ type TciCommandMatcher = (reply: TciCommand, request: TciCommand) => boolean;
6
+ interface QueueCommandOptions {
7
+ timeoutMs?: number;
8
+ matcher?: TciCommandMatcher;
9
+ signal?: AbortSignal;
10
+ }
11
+ interface QueuedCommandResult {
12
+ request: TciCommand;
13
+ reply: TciCommand;
14
+ }
15
+ interface TciCommandQueueOptions {
16
+ send: (raw: string) => void | Promise<void>;
17
+ timeoutMs?: number;
18
+ }
19
+ declare class TciCommandQueue {
20
+ private readonly send;
21
+ private readonly defaultTimeoutMs;
22
+ private queue;
23
+ private active?;
24
+ private connected;
25
+ constructor(options: TciCommandQueueOptions);
26
+ setConnected(connected: boolean): void;
27
+ enqueue(command: TciCommandInput, options?: QueueCommandOptions): Promise<QueuedCommandResult>;
28
+ handleCommand(commandInput: TciCommandInput): boolean;
29
+ cancelAll(error?: TciError): void;
30
+ get size(): number;
31
+ private pump;
32
+ private finishActive;
33
+ private rejectPending;
34
+ }
35
+
36
+ export { type QueueCommandOptions, type QueuedCommandResult, TciCommand, TciCommandInput, type TciCommandMatcher, TciCommandQueue, type TciCommandQueueOptions };
@@ -337,6 +337,7 @@ var MockTciServer = class {
337
337
  mode = "DIGU";
338
338
  ptt = false;
339
339
  drive = 30;
340
+ iqSampleRate = 48e3;
340
341
  constructor(options = {}) {
341
342
  this.options = {
342
343
  port: options.port ?? 0,
@@ -408,6 +409,20 @@ var MockTciServer = class {
408
409
  });
409
410
  this.broadcastBinary(frame);
410
411
  }
412
+ sendIqFrame(options = {}) {
413
+ const samples = options.samples ?? new Float32Array(4096);
414
+ const frame = buildStreamFrame({
415
+ receiver: options.receiver ?? 0,
416
+ sampleRate: options.sampleRate ?? this.iqSampleRate,
417
+ sampleType: options.sampleType ?? 3 /* FLOAT32 */,
418
+ streamType: 0 /* IQ_STREAM */,
419
+ channels: options.channels ?? 2,
420
+ samples,
421
+ payload: options.payload,
422
+ lengthSemantics: options.lengthSemantics
423
+ });
424
+ this.broadcastBinary(frame);
425
+ }
411
426
  sendTxChrono(options = {}) {
412
427
  const sampleType = options.sampleType ?? 3 /* FLOAT32 */;
413
428
  const channels = options.channels ?? 1;
@@ -423,6 +438,38 @@ var MockTciServer = class {
423
438
  });
424
439
  this.broadcastBinary(frame);
425
440
  }
441
+ sendRxMeterFrame(options) {
442
+ const receiver = options.receiver ?? 0;
443
+ const channel = options.channel ?? 0;
444
+ const source = options.source ?? "channel";
445
+ if (source === "legacy") {
446
+ this.broadcastCommand("RX_SENSORS", [receiver, options.levelDbm]);
447
+ return;
448
+ }
449
+ if (source === "extended") {
450
+ this.broadcastCommand("RX_CHANNEL_SENSORS_EX", [
451
+ receiver,
452
+ channel,
453
+ options.levelDbm,
454
+ options.averageLevelDbm ?? options.levelDbm,
455
+ options.peakBinDbm ?? options.levelDbm
456
+ ]);
457
+ return;
458
+ }
459
+ this.broadcastCommand("RX_CHANNEL_SENSORS", [receiver, channel, options.levelDbm]);
460
+ }
461
+ sendTxMeterFrame(options = {}) {
462
+ const args = [
463
+ options.trx ?? 0,
464
+ options.micLevelDbm ?? -20,
465
+ options.rmsPowerWatts ?? 0,
466
+ options.peakPowerWatts ?? options.rmsPowerWatts ?? 0,
467
+ options.swr ?? 1
468
+ ];
469
+ if (options.alc !== void 0) args.push(options.alc);
470
+ if (options.extraArgs) args.push(...options.extraArgs);
471
+ this.broadcastCommand("TX_SENSORS", args);
472
+ }
426
473
  closeClients() {
427
474
  for (const socket of this.sockets) {
428
475
  socket.close();
@@ -437,6 +484,8 @@ var MockTciServer = class {
437
484
  "DEVICE:Mock ExpertSDR3;",
438
485
  "MODULATIONS_LIST:LSB,USB,CW,AM,NFM,DIGU,DIGL;",
439
486
  `VFO:0,0,${this.frequency};`,
487
+ `DDS:0,${this.frequency};`,
488
+ `IQ_SAMPLERATE:${this.iqSampleRate};`,
440
489
  `MODULATION:0,${this.mode};`,
441
490
  `TRX:0,${this.ptt};`,
442
491
  `DRIVE:0,${this.drive};`,
@@ -517,6 +566,15 @@ var MockTciServer = class {
517
566
  case "tx_stream_audio_buffering":
518
567
  socket.send(formatTciCommand(command.originalName, command.args));
519
568
  break;
569
+ case "iq_samplerate": {
570
+ if (command.args[0] !== void 0) this.iqSampleRate = Number(command.args[0]);
571
+ socket.send(formatTciCommand("IQ_SAMPLERATE", [this.iqSampleRate]));
572
+ break;
573
+ }
574
+ case "iq_start":
575
+ case "iq_stop":
576
+ socket.send(formatTciCommand(command.originalName, command.args));
577
+ break;
520
578
  default:
521
579
  if (this.options.echoUnknown) {
522
580
  socket.send(formatTciCommand(command.originalName, command.args));