tci-client-node 0.3.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.
- package/CHANGELOG.md +9 -0
- package/README.md +24 -0
- package/dist/audio/index.d.cts +2 -1
- package/dist/audio/index.d.ts +2 -1
- package/dist/dialect/index.cjs +175 -12
- package/dist/dialect/index.cjs.map +1 -1
- package/dist/dialect/index.d.cts +3 -2
- package/dist/dialect/index.d.ts +3 -2
- package/dist/dialect/index.js +175 -12
- package/dist/dialect/index.js.map +1 -1
- package/dist/errors-CT3b8LLw.d.cts +9 -0
- package/dist/errors-CT3b8LLw.d.ts +9 -0
- package/dist/index.cjs +404 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -5
- package/dist/index.d.ts +14 -5
- package/dist/index.js +399 -5
- package/dist/index.js.map +1 -1
- package/dist/meter/index.cjs +348 -0
- package/dist/meter/index.cjs.map +1 -0
- package/dist/meter/index.d.cts +78 -0
- package/dist/meter/index.d.ts +78 -0
- package/dist/meter/index.js +317 -0
- package/dist/meter/index.js.map +1 -0
- package/dist/protocol/index.d.cts +36 -2
- package/dist/protocol/index.d.ts +36 -2
- package/dist/testing/index.cjs +32 -0
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +19 -1
- package/dist/testing/index.d.ts +19 -1
- package/dist/testing/index.js +32 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/types-C0qVJVSO.d.ts +74 -0
- package/dist/types-C4MtLKoy.d.cts +74 -0
- package/dist/{types-1YXGwrgI.d.cts → types-CYK_NOTz.d.cts} +2 -0
- package/dist/{types-BtPh4Dbd.d.ts → types-DVIPU-VR.d.ts} +2 -0
- package/package.json +11 -2
- package/dist/index-lbK6NGY4.d.cts +0 -42
- package/dist/index-paj1AOJY.d.ts +0 -42
package/dist/index.d.cts
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { a as
|
|
1
|
+
import { T as TciError } from './errors-CT3b8LLw.cjs';
|
|
2
|
+
export { a as TciErrorCode, t as toTciError } from './errors-CT3b8LLw.cjs';
|
|
3
3
|
import { EventEmitter } from 'eventemitter3';
|
|
4
4
|
import WebSocket from 'ws';
|
|
5
5
|
import { TciSampleType, TciSampleTypeName, TciStreamFrame, BuildTxAudioFrameOptions } from './audio/index.cjs';
|
|
6
6
|
export { BuildStreamFrameOptions, ParseStreamFrameOptions, TCI_STREAM_HEADER_BYTES, TciStreamType, buildStreamFrame, buildTxAudioFrame, decodeInterleavedIq, deinterleaveChannels, float32ToPcm16, mixToMono, normalizeSampleType, normalizeStreamType, parseStreamFrame, payloadToFloat32, pcm16ToFloat32, sampleTypeBytes, sampleTypeName, samplesToPayload } from './audio/index.cjs';
|
|
7
7
|
import { T as TciCommand } from './text-BwCWY1k1.cjs';
|
|
8
8
|
export { 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';
|
|
9
|
-
import {
|
|
10
|
-
export {
|
|
9
|
+
import { QueueCommandOptions } from './protocol/index.cjs';
|
|
10
|
+
export { QueuedCommandResult, TciCommandMatcher, TciCommandQueue, TciCommandQueueOptions } from './protocol/index.cjs';
|
|
11
|
+
import { T as TciWriteResult, a as TciDialectId, b as TciHandshakeResult, c as TciDialectSelection } from './types-CYK_NOTz.cjs';
|
|
12
|
+
export { B as BuiltInTciDialectId, d as TciDialect, e as TciDialectDetection, f as TciDialectDetectionContext, g as TciDialectScore, h as TciDriveState, i as TciProtocolIdentity, j as TciStreamLengthSemantics } from './types-CYK_NOTz.cjs';
|
|
11
13
|
import { TciDialectRegistry } from './dialect/index.cjs';
|
|
12
14
|
export { aetherSdrDialect, assertValidTciHandshake, builtInDialects, compareTciVersion, defaultTciDialectRegistry, expertSdr14Dialect, expertSdrLegacyDialect, expertSdrModernDialect, genericObservedDialect, parseProtocolIdentity, parseTciVersion, thetisDialect } from './dialect/index.cjs';
|
|
13
15
|
import { TciTransportFactory } from './transport/index.cjs';
|
|
14
16
|
export { TciTransport, TciTransportEvents, WebSocketTciTransport } from './transport/index.cjs';
|
|
17
|
+
import { T as TciMeterCapabilities, a as TciMeterStreamOptions } from './types-C4MtLKoy.cjs';
|
|
18
|
+
export { b as TciMeterAdapter, c as TciMeterCommand, d as TciMeterDecodeResult, e as TciMeterDecodedFrame, f as TciMeterInterval, g as TciMeterSupport, h as TciRxMeterFrame, i as TciRxMeterSource, j as TciTxMeterFrame, U as UNKNOWN_TCI_METER_CAPABILITIES, k as cloneMeterCapabilities } from './types-C4MtLKoy.cjs';
|
|
19
|
+
import { TciMeterStreamSession } from './meter/index.cjs';
|
|
20
|
+
export { StandardTciMeterAdapter, TciMeterStreamEvents, createUnknownTciMeterAdapter } from './meter/index.cjs';
|
|
15
21
|
|
|
16
22
|
interface TciClientOptions {
|
|
17
23
|
url: string;
|
|
@@ -175,6 +181,7 @@ declare class TciClient extends EventEmitter<TciClientEvents> {
|
|
|
175
181
|
private initializationCommands;
|
|
176
182
|
private handshakeWaiter?;
|
|
177
183
|
private activeIqSession?;
|
|
184
|
+
private activeMeterSession?;
|
|
178
185
|
constructor(options: TciClientOptions);
|
|
179
186
|
connect(): Promise<TciHandshakeResult>;
|
|
180
187
|
disconnect(code?: number, reason?: string): Promise<void>;
|
|
@@ -182,6 +189,8 @@ declare class TciClient extends EventEmitter<TciClientEvents> {
|
|
|
182
189
|
getState(): TciClientState;
|
|
183
190
|
getHandshakeResult(): TciHandshakeResult | undefined;
|
|
184
191
|
getIqCapabilities(): TciIqCapabilities;
|
|
192
|
+
getMeterCapabilities(): TciMeterCapabilities;
|
|
193
|
+
openMeterStream(options?: TciMeterStreamOptions): Promise<TciMeterStreamSession>;
|
|
185
194
|
setIqSampleRate(sampleRate: number, timeoutMs?: number): Promise<IqSampleRateResult>;
|
|
186
195
|
openIqStream(options?: TciIqStreamOptions): Promise<TciIqStreamSession>;
|
|
187
196
|
sendCommand(name: string, args?: readonly unknown[], options?: SendCommandOptions): Promise<TciCommand | undefined>;
|
|
@@ -240,4 +249,4 @@ declare class TciClient extends EventEmitter<TciClientEvents> {
|
|
|
240
249
|
}
|
|
241
250
|
declare function createTciClient(options: TciClientOptions): TciClient;
|
|
242
251
|
|
|
243
|
-
export { BuildTxAudioFrameOptions, type IqSampleRateResult, QueueCommandOptions, type SendCommandOptions, type TciAudioConfig, TciClient, type TciClientEvents, type TciClientOptions, type TciClientState, TciCommand, TciDialectId, TciDialectRegistry, TciDialectSelection, TciError, TciHandshakeResult, type TciIqCapabilities, type TciIqFrame, type TciIqStreamEvents, type TciIqStreamOptions, TciIqStreamSession, type TciPttOptions, TciSampleType, TciSampleTypeName, TciStreamFrame, TciTransportFactory, type TciTxChronoRequest, type TciWriteAckMode, type TciWriteOptions, TciWriteResult, createTciClient };
|
|
252
|
+
export { BuildTxAudioFrameOptions, type IqSampleRateResult, QueueCommandOptions, type SendCommandOptions, type TciAudioConfig, TciClient, type TciClientEvents, type TciClientOptions, type TciClientState, TciCommand, TciDialectId, TciDialectRegistry, TciDialectSelection, TciError, TciHandshakeResult, type TciIqCapabilities, type TciIqFrame, type TciIqStreamEvents, type TciIqStreamOptions, TciIqStreamSession, TciMeterCapabilities, TciMeterStreamOptions, TciMeterStreamSession, type TciPttOptions, TciSampleType, TciSampleTypeName, TciStreamFrame, TciTransportFactory, type TciTxChronoRequest, type TciWriteAckMode, type TciWriteOptions, TciWriteResult, createTciClient };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { a as
|
|
1
|
+
import { T as TciError } from './errors-CT3b8LLw.js';
|
|
2
|
+
export { a as TciErrorCode, t as toTciError } from './errors-CT3b8LLw.js';
|
|
3
3
|
import { EventEmitter } from 'eventemitter3';
|
|
4
4
|
import WebSocket from 'ws';
|
|
5
5
|
import { TciSampleType, TciSampleTypeName, TciStreamFrame, BuildTxAudioFrameOptions } from './audio/index.js';
|
|
6
6
|
export { BuildStreamFrameOptions, ParseStreamFrameOptions, TCI_STREAM_HEADER_BYTES, TciStreamType, buildStreamFrame, buildTxAudioFrame, decodeInterleavedIq, deinterleaveChannels, float32ToPcm16, mixToMono, normalizeSampleType, normalizeStreamType, parseStreamFrame, payloadToFloat32, pcm16ToFloat32, sampleTypeBytes, sampleTypeName, samplesToPayload } from './audio/index.js';
|
|
7
7
|
import { T as TciCommand } from './text-BwCWY1k1.js';
|
|
8
8
|
export { 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';
|
|
9
|
-
import {
|
|
10
|
-
export {
|
|
9
|
+
import { QueueCommandOptions } from './protocol/index.js';
|
|
10
|
+
export { QueuedCommandResult, TciCommandMatcher, TciCommandQueue, TciCommandQueueOptions } from './protocol/index.js';
|
|
11
|
+
import { T as TciWriteResult, a as TciDialectId, b as TciHandshakeResult, c as TciDialectSelection } from './types-DVIPU-VR.js';
|
|
12
|
+
export { B as BuiltInTciDialectId, d as TciDialect, e as TciDialectDetection, f as TciDialectDetectionContext, g as TciDialectScore, h as TciDriveState, i as TciProtocolIdentity, j as TciStreamLengthSemantics } from './types-DVIPU-VR.js';
|
|
11
13
|
import { TciDialectRegistry } from './dialect/index.js';
|
|
12
14
|
export { aetherSdrDialect, assertValidTciHandshake, builtInDialects, compareTciVersion, defaultTciDialectRegistry, expertSdr14Dialect, expertSdrLegacyDialect, expertSdrModernDialect, genericObservedDialect, parseProtocolIdentity, parseTciVersion, thetisDialect } from './dialect/index.js';
|
|
13
15
|
import { TciTransportFactory } from './transport/index.js';
|
|
14
16
|
export { TciTransport, TciTransportEvents, WebSocketTciTransport } from './transport/index.js';
|
|
17
|
+
import { T as TciMeterCapabilities, a as TciMeterStreamOptions } from './types-C0qVJVSO.js';
|
|
18
|
+
export { b as TciMeterAdapter, c as TciMeterCommand, d as TciMeterDecodeResult, e as TciMeterDecodedFrame, f as TciMeterInterval, g as TciMeterSupport, h as TciRxMeterFrame, i as TciRxMeterSource, j as TciTxMeterFrame, U as UNKNOWN_TCI_METER_CAPABILITIES, k as cloneMeterCapabilities } from './types-C0qVJVSO.js';
|
|
19
|
+
import { TciMeterStreamSession } from './meter/index.js';
|
|
20
|
+
export { StandardTciMeterAdapter, TciMeterStreamEvents, createUnknownTciMeterAdapter } from './meter/index.js';
|
|
15
21
|
|
|
16
22
|
interface TciClientOptions {
|
|
17
23
|
url: string;
|
|
@@ -175,6 +181,7 @@ declare class TciClient extends EventEmitter<TciClientEvents> {
|
|
|
175
181
|
private initializationCommands;
|
|
176
182
|
private handshakeWaiter?;
|
|
177
183
|
private activeIqSession?;
|
|
184
|
+
private activeMeterSession?;
|
|
178
185
|
constructor(options: TciClientOptions);
|
|
179
186
|
connect(): Promise<TciHandshakeResult>;
|
|
180
187
|
disconnect(code?: number, reason?: string): Promise<void>;
|
|
@@ -182,6 +189,8 @@ declare class TciClient extends EventEmitter<TciClientEvents> {
|
|
|
182
189
|
getState(): TciClientState;
|
|
183
190
|
getHandshakeResult(): TciHandshakeResult | undefined;
|
|
184
191
|
getIqCapabilities(): TciIqCapabilities;
|
|
192
|
+
getMeterCapabilities(): TciMeterCapabilities;
|
|
193
|
+
openMeterStream(options?: TciMeterStreamOptions): Promise<TciMeterStreamSession>;
|
|
185
194
|
setIqSampleRate(sampleRate: number, timeoutMs?: number): Promise<IqSampleRateResult>;
|
|
186
195
|
openIqStream(options?: TciIqStreamOptions): Promise<TciIqStreamSession>;
|
|
187
196
|
sendCommand(name: string, args?: readonly unknown[], options?: SendCommandOptions): Promise<TciCommand | undefined>;
|
|
@@ -240,4 +249,4 @@ declare class TciClient extends EventEmitter<TciClientEvents> {
|
|
|
240
249
|
}
|
|
241
250
|
declare function createTciClient(options: TciClientOptions): TciClient;
|
|
242
251
|
|
|
243
|
-
export { BuildTxAudioFrameOptions, type IqSampleRateResult, QueueCommandOptions, type SendCommandOptions, type TciAudioConfig, TciClient, type TciClientEvents, type TciClientOptions, type TciClientState, TciCommand, TciDialectId, TciDialectRegistry, TciDialectSelection, TciError, TciHandshakeResult, type TciIqCapabilities, type TciIqFrame, type TciIqStreamEvents, type TciIqStreamOptions, TciIqStreamSession, type TciPttOptions, TciSampleType, TciSampleTypeName, TciStreamFrame, TciTransportFactory, type TciTxChronoRequest, type TciWriteAckMode, type TciWriteOptions, TciWriteResult, createTciClient };
|
|
252
|
+
export { BuildTxAudioFrameOptions, type IqSampleRateResult, QueueCommandOptions, type SendCommandOptions, type TciAudioConfig, TciClient, type TciClientEvents, type TciClientOptions, type TciClientState, TciCommand, TciDialectId, TciDialectRegistry, TciDialectSelection, TciError, TciHandshakeResult, type TciIqCapabilities, type TciIqFrame, type TciIqStreamEvents, type TciIqStreamOptions, TciIqStreamSession, TciMeterCapabilities, TciMeterStreamOptions, TciMeterStreamSession, type TciPttOptions, TciSampleType, TciSampleTypeName, TciStreamFrame, TciTransportFactory, type TciTxChronoRequest, type TciWriteAckMode, type TciWriteOptions, TciWriteResult, createTciClient };
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ function toTciError(error, fallbackCode = "protocol-error") {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// src/client/TciClient.ts
|
|
23
|
-
import { EventEmitter as
|
|
23
|
+
import { EventEmitter as EventEmitter3 } from "eventemitter3";
|
|
24
24
|
import WebSocket2 from "ws";
|
|
25
25
|
|
|
26
26
|
// src/audio/streamFrame.ts
|
|
@@ -597,6 +597,302 @@ function ensureSemicolon(command) {
|
|
|
597
597
|
return command.trim().endsWith(";") ? command.trim() : `${command.trim()};`;
|
|
598
598
|
}
|
|
599
599
|
|
|
600
|
+
// src/meter/types.ts
|
|
601
|
+
var UNKNOWN_TCI_METER_CAPABILITIES = {
|
|
602
|
+
rxLevel: "unknown",
|
|
603
|
+
rxAverageLevel: "unknown",
|
|
604
|
+
rxPeakBin: "unknown",
|
|
605
|
+
txMicLevel: "unknown",
|
|
606
|
+
txRmsPower: "unknown",
|
|
607
|
+
txPeakPower: "unknown",
|
|
608
|
+
txSwr: "unknown",
|
|
609
|
+
txAlcDbfs: "unknown"
|
|
610
|
+
};
|
|
611
|
+
function cloneMeterCapabilities(capabilities) {
|
|
612
|
+
return { ...capabilities };
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/meter/adapters.ts
|
|
616
|
+
var StandardTciMeterAdapter = class {
|
|
617
|
+
declaredCapabilities;
|
|
618
|
+
options;
|
|
619
|
+
constructor(options = {}) {
|
|
620
|
+
this.options = options;
|
|
621
|
+
this.declaredCapabilities = {
|
|
622
|
+
...UNKNOWN_TCI_METER_CAPABILITIES,
|
|
623
|
+
rxLevel: "declared",
|
|
624
|
+
txMicLevel: "declared",
|
|
625
|
+
txRmsPower: "declared",
|
|
626
|
+
txPeakPower: "declared",
|
|
627
|
+
txSwr: "declared",
|
|
628
|
+
rxAverageLevel: options.supportsRxExtended ? "declared" : "unknown",
|
|
629
|
+
rxPeakBin: options.supportsRxExtended ? "declared" : "unknown",
|
|
630
|
+
txAlcDbfs: options.txAlcUnit === "dbfs" ? "declared" : "unknown",
|
|
631
|
+
...options.capabilities
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
normalizeInterval(intervalMs) {
|
|
635
|
+
const requestedMs = Math.round(intervalMs);
|
|
636
|
+
if (this.options.interval?.fixedMs !== void 0) {
|
|
637
|
+
return { requestedMs, appliedMs: this.options.interval.fixedMs };
|
|
638
|
+
}
|
|
639
|
+
const minMs = this.options.interval?.minMs ?? requestedMs;
|
|
640
|
+
const maxMs = this.options.interval?.maxMs ?? requestedMs;
|
|
641
|
+
const normalized = Math.max(minMs, Math.min(maxMs, requestedMs));
|
|
642
|
+
return {
|
|
643
|
+
requestedMs,
|
|
644
|
+
appliedMs: this.options.interval?.reportsApplied === false ? void 0 : normalized
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
buildEnableCommand(kind, enabled, intervalMs) {
|
|
648
|
+
return {
|
|
649
|
+
name: kind === "rx" ? "RX_SENSORS_ENABLE" : "TX_SENSORS_ENABLE",
|
|
650
|
+
args: enabled ? [true, intervalMs] : [false]
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
decode(command, receivedAtMs) {
|
|
654
|
+
switch (command.name) {
|
|
655
|
+
case "rx_sensors":
|
|
656
|
+
return decodeRx(command, receivedAtMs, "rx_sensors");
|
|
657
|
+
case "rx_channel_sensors":
|
|
658
|
+
return decodeRx(command, receivedAtMs, "rx_channel_sensors");
|
|
659
|
+
case "rx_channel_sensors_ex":
|
|
660
|
+
return decodeRx(command, receivedAtMs, "rx_channel_sensors_ex");
|
|
661
|
+
case "tx_sensors":
|
|
662
|
+
return decodeTx(command, receivedAtMs, this.options.txAlcUnit);
|
|
663
|
+
default:
|
|
664
|
+
return void 0;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
function createUnknownTciMeterAdapter() {
|
|
669
|
+
return new StandardTciMeterAdapter({
|
|
670
|
+
capabilities: cloneMeterCapabilities(UNKNOWN_TCI_METER_CAPABILITIES),
|
|
671
|
+
interval: { reportsApplied: false }
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
function decodeRx(command, receivedAtMs, source) {
|
|
675
|
+
const receiver = integer(command.args[0]);
|
|
676
|
+
const hasChannel = source !== "rx_sensors";
|
|
677
|
+
const channel = hasChannel ? integer(command.args[1]) : 0;
|
|
678
|
+
const levelIndex = hasChannel ? 2 : 1;
|
|
679
|
+
const levelDbm = finite(command.args[levelIndex]);
|
|
680
|
+
if (receiver === void 0 || receiver < 0 || channel === void 0 || channel < 0 || levelDbm === void 0) {
|
|
681
|
+
return { issue: `Invalid ${command.originalName} meter frame: ${command.raw}` };
|
|
682
|
+
}
|
|
683
|
+
const frame = { receiver, channel, levelDbm, source, receivedAtMs };
|
|
684
|
+
if (source === "rx_channel_sensors_ex") {
|
|
685
|
+
const averageLevelDbm = finite(command.args[3]);
|
|
686
|
+
const peakBinDbm = finite(command.args[4]);
|
|
687
|
+
if (averageLevelDbm === void 0 || peakBinDbm === void 0) {
|
|
688
|
+
return { issue: `Invalid ${command.originalName} extended meter frame: ${command.raw}` };
|
|
689
|
+
}
|
|
690
|
+
frame.averageLevelDbm = averageLevelDbm;
|
|
691
|
+
frame.peakBinDbm = peakBinDbm;
|
|
692
|
+
if (command.args.length > 5) frame.extraArgs = command.args.slice(5);
|
|
693
|
+
} else if (command.args.length > levelIndex + 1) {
|
|
694
|
+
frame.extraArgs = command.args.slice(levelIndex + 1);
|
|
695
|
+
}
|
|
696
|
+
return { decoded: { kind: "rx", frame } };
|
|
697
|
+
}
|
|
698
|
+
function decodeTx(command, receivedAtMs, alcUnit) {
|
|
699
|
+
const trx = integer(command.args[0]);
|
|
700
|
+
if (trx === void 0 || trx < 0) {
|
|
701
|
+
return { issue: `Invalid ${command.originalName} transmitter index: ${command.raw}` };
|
|
702
|
+
}
|
|
703
|
+
const micLevelDbm = optionalFinite(command.args[1]);
|
|
704
|
+
const rmsPowerWatts = optionalFinite(command.args[2]);
|
|
705
|
+
const peakPowerWatts = optionalFinite(command.args[3]);
|
|
706
|
+
const swr = optionalFinite(command.args[4]);
|
|
707
|
+
const alcValue = alcUnit ? optionalFinite(command.args[5]) : void 0;
|
|
708
|
+
if (micLevelDbm.invalid || rmsPowerWatts.invalid || peakPowerWatts.invalid || swr.invalid || alcValue?.invalid) {
|
|
709
|
+
return { issue: `Invalid ${command.originalName} numeric meter frame: ${command.raw}` };
|
|
710
|
+
}
|
|
711
|
+
if (rmsPowerWatts.value !== void 0 && rmsPowerWatts.value < 0 || peakPowerWatts.value !== void 0 && peakPowerWatts.value < 0 || swr.value !== void 0 && swr.value < 1) {
|
|
712
|
+
return { issue: `Out-of-range ${command.originalName} meter frame: ${command.raw}` };
|
|
713
|
+
}
|
|
714
|
+
if (micLevelDbm.value === void 0 && rmsPowerWatts.value === void 0 && peakPowerWatts.value === void 0 && swr.value === void 0 && alcValue?.value === void 0) {
|
|
715
|
+
return { issue: `Empty ${command.originalName} meter frame: ${command.raw}` };
|
|
716
|
+
}
|
|
717
|
+
const frame = {
|
|
718
|
+
trx,
|
|
719
|
+
micLevelDbm: micLevelDbm.value,
|
|
720
|
+
rmsPowerWatts: rmsPowerWatts.value,
|
|
721
|
+
peakPowerWatts: peakPowerWatts.value,
|
|
722
|
+
swr: swr.value,
|
|
723
|
+
receivedAtMs
|
|
724
|
+
};
|
|
725
|
+
if (alcUnit && alcValue?.value !== void 0) frame.alc = { value: alcValue.value, unit: alcUnit };
|
|
726
|
+
const knownArgs = alcUnit ? 6 : 5;
|
|
727
|
+
if (command.args.length > knownArgs) frame.extraArgs = command.args.slice(knownArgs);
|
|
728
|
+
return { decoded: { kind: "tx", frame } };
|
|
729
|
+
}
|
|
730
|
+
function finite(value) {
|
|
731
|
+
if (value === void 0 || value === "") return void 0;
|
|
732
|
+
const parsed = Number(value);
|
|
733
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
734
|
+
}
|
|
735
|
+
function integer(value) {
|
|
736
|
+
const parsed = finite(value);
|
|
737
|
+
return parsed !== void 0 && Number.isInteger(parsed) ? parsed : void 0;
|
|
738
|
+
}
|
|
739
|
+
function optionalFinite(value) {
|
|
740
|
+
if (value === void 0 || value === "") return { invalid: false };
|
|
741
|
+
const parsed = Number(value);
|
|
742
|
+
return Number.isFinite(parsed) ? { value: parsed, invalid: false } : { invalid: true };
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/meter/TciMeterStreamSession.ts
|
|
746
|
+
import { EventEmitter } from "eventemitter3";
|
|
747
|
+
var RX_COALESCE_MS = 20;
|
|
748
|
+
var TciMeterStreamSession = class extends EventEmitter {
|
|
749
|
+
receiver;
|
|
750
|
+
channel;
|
|
751
|
+
trx;
|
|
752
|
+
requestedIntervalMs;
|
|
753
|
+
appliedIntervalMs;
|
|
754
|
+
rxEnabled;
|
|
755
|
+
txEnabled;
|
|
756
|
+
adapter;
|
|
757
|
+
callbacks;
|
|
758
|
+
capabilities;
|
|
759
|
+
pendingRx = /* @__PURE__ */ new Map();
|
|
760
|
+
closed = false;
|
|
761
|
+
constructor(options) {
|
|
762
|
+
super();
|
|
763
|
+
this.receiver = options.receiver;
|
|
764
|
+
this.channel = options.channel;
|
|
765
|
+
this.trx = options.trx;
|
|
766
|
+
this.requestedIntervalMs = options.requestedIntervalMs;
|
|
767
|
+
this.appliedIntervalMs = options.appliedIntervalMs;
|
|
768
|
+
this.rxEnabled = options.rxEnabled;
|
|
769
|
+
this.txEnabled = options.txEnabled;
|
|
770
|
+
this.adapter = options.adapter;
|
|
771
|
+
this.callbacks = options.callbacks;
|
|
772
|
+
this.capabilities = cloneMeterCapabilities(options.adapter.declaredCapabilities);
|
|
773
|
+
}
|
|
774
|
+
getCapabilities() {
|
|
775
|
+
return cloneMeterCapabilities(this.capabilities);
|
|
776
|
+
}
|
|
777
|
+
async close() {
|
|
778
|
+
if (this.closed) return;
|
|
779
|
+
this.closed = true;
|
|
780
|
+
this.clearPendingRx();
|
|
781
|
+
try {
|
|
782
|
+
await this.callbacks.close();
|
|
783
|
+
} finally {
|
|
784
|
+
this.emit("closed");
|
|
785
|
+
this.removeAllListeners();
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
_acceptCommand(command, receivedAtMs) {
|
|
789
|
+
if (this.closed) return;
|
|
790
|
+
this.acceptEnableAcknowledgement(command);
|
|
791
|
+
const result = this.adapter.decode(command, receivedAtMs);
|
|
792
|
+
if (!result) return;
|
|
793
|
+
if (result.issue) {
|
|
794
|
+
this.emit("error", new TciError("protocol-error", result.issue));
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
if (result.decoded?.kind === "rx") this.acceptRxFrame(result.decoded.frame);
|
|
798
|
+
if (result.decoded?.kind === "tx") this.acceptTxFrame(result.decoded.frame);
|
|
799
|
+
}
|
|
800
|
+
_fail(error) {
|
|
801
|
+
if (this.closed) return;
|
|
802
|
+
this.closed = true;
|
|
803
|
+
this.clearPendingRx();
|
|
804
|
+
this.emit("error", error);
|
|
805
|
+
this.emit("closed");
|
|
806
|
+
this.removeAllListeners();
|
|
807
|
+
}
|
|
808
|
+
acceptRxFrame(frame) {
|
|
809
|
+
if (!this.rxEnabled || frame.receiver !== this.receiver || frame.channel !== this.channel) return;
|
|
810
|
+
this.observe("rxLevel");
|
|
811
|
+
if (frame.averageLevelDbm !== void 0) this.observe("rxAverageLevel");
|
|
812
|
+
if (frame.peakBinDbm !== void 0) this.observe("rxPeakBin");
|
|
813
|
+
const key = `${frame.receiver}:${frame.channel}`;
|
|
814
|
+
const pending = this.pendingRx.get(key);
|
|
815
|
+
if (pending) {
|
|
816
|
+
const sameReading = Math.abs(pending.frame.levelDbm - frame.levelDbm) < 0.05;
|
|
817
|
+
if (sameReading && rxPriority(frame) >= rxPriority(pending.frame)) {
|
|
818
|
+
clearTimeout(pending.timer);
|
|
819
|
+
this.pendingRx.delete(key);
|
|
820
|
+
} else if (sameReading) {
|
|
821
|
+
return;
|
|
822
|
+
} else {
|
|
823
|
+
this.flushRx(key, pending);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
const timer = setTimeout(() => {
|
|
827
|
+
const current = this.pendingRx.get(key);
|
|
828
|
+
if (current?.frame === frame) this.flushRx(key, current);
|
|
829
|
+
}, RX_COALESCE_MS);
|
|
830
|
+
this.pendingRx.set(key, { frame, timer });
|
|
831
|
+
}
|
|
832
|
+
acceptTxFrame(frame) {
|
|
833
|
+
if (!this.txEnabled || frame.trx !== this.trx) return;
|
|
834
|
+
if (frame.micLevelDbm !== void 0) this.observe("txMicLevel");
|
|
835
|
+
if (frame.rmsPowerWatts !== void 0) this.observe("txRmsPower");
|
|
836
|
+
if (frame.peakPowerWatts !== void 0) this.observe("txPeakPower");
|
|
837
|
+
if (frame.swr !== void 0) this.observe("txSwr");
|
|
838
|
+
if (frame.alc?.unit === "dbfs") this.observe("txAlcDbfs");
|
|
839
|
+
this.emit("txFrame", frame);
|
|
840
|
+
}
|
|
841
|
+
acceptEnableAcknowledgement(command) {
|
|
842
|
+
const enabled = command.args[0]?.toLowerCase();
|
|
843
|
+
if (enabled !== "true" && enabled !== "1" && enabled !== "on") return;
|
|
844
|
+
if (command.name === "rx_sensors_enable" && this.rxEnabled) {
|
|
845
|
+
this.acknowledge(["rxLevel"]);
|
|
846
|
+
}
|
|
847
|
+
if (command.name === "tx_sensors_enable" && this.txEnabled) {
|
|
848
|
+
this.acknowledge(["txMicLevel", "txRmsPower", "txPeakPower", "txSwr"]);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
acknowledge(keys) {
|
|
852
|
+
let changed = false;
|
|
853
|
+
for (const key of keys) {
|
|
854
|
+
if (supportRank(this.capabilities[key]) < supportRank("acknowledged")) {
|
|
855
|
+
this.capabilities[key] = "acknowledged";
|
|
856
|
+
changed = true;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (changed) this.emit("capabilitiesChanged", this.getCapabilities());
|
|
860
|
+
}
|
|
861
|
+
observe(key) {
|
|
862
|
+
if (this.capabilities[key] === "observed") return;
|
|
863
|
+
this.capabilities[key] = "observed";
|
|
864
|
+
this.emit("capabilitiesChanged", this.getCapabilities());
|
|
865
|
+
}
|
|
866
|
+
flushRx(key, pending) {
|
|
867
|
+
clearTimeout(pending.timer);
|
|
868
|
+
if (this.pendingRx.get(key) === pending) this.pendingRx.delete(key);
|
|
869
|
+
this.emit("rxFrame", pending.frame);
|
|
870
|
+
}
|
|
871
|
+
clearPendingRx() {
|
|
872
|
+
for (const pending of this.pendingRx.values()) clearTimeout(pending.timer);
|
|
873
|
+
this.pendingRx.clear();
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
function rxPriority(frame) {
|
|
877
|
+
if (frame.source === "rx_channel_sensors_ex") return 3;
|
|
878
|
+
if (frame.source === "rx_channel_sensors") return 2;
|
|
879
|
+
return 1;
|
|
880
|
+
}
|
|
881
|
+
function supportRank(value) {
|
|
882
|
+
switch (value) {
|
|
883
|
+
case "unsupported":
|
|
884
|
+
return -1;
|
|
885
|
+
case "unknown":
|
|
886
|
+
return 0;
|
|
887
|
+
case "declared":
|
|
888
|
+
return 1;
|
|
889
|
+
case "acknowledged":
|
|
890
|
+
return 2;
|
|
891
|
+
case "observed":
|
|
892
|
+
return 3;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
600
896
|
// src/dialect/builtins.ts
|
|
601
897
|
var StandardTciDialect = class {
|
|
602
898
|
id;
|
|
@@ -606,6 +902,7 @@ var StandardTciDialect = class {
|
|
|
606
902
|
supportsTxAudioSource;
|
|
607
903
|
supportsIqStream;
|
|
608
904
|
iqSampleRates;
|
|
905
|
+
meterAdapter;
|
|
609
906
|
driveHasTrx;
|
|
610
907
|
detector;
|
|
611
908
|
resolver;
|
|
@@ -617,6 +914,7 @@ var StandardTciDialect = class {
|
|
|
617
914
|
this.supportsTxAudioSource = options.supportsTxAudioSource;
|
|
618
915
|
this.supportsIqStream = options.supportsIqStream;
|
|
619
916
|
this.iqSampleRates = [...options.iqSampleRates];
|
|
917
|
+
this.meterAdapter = options.meterAdapter;
|
|
620
918
|
this.driveHasTrx = options.driveHasTrx;
|
|
621
919
|
this.detector = options.detect;
|
|
622
920
|
this.resolver = options.resolve;
|
|
@@ -667,6 +965,7 @@ var expertSdr14Dialect = new StandardTciDialect({
|
|
|
667
965
|
supportsIqStream: true,
|
|
668
966
|
iqSampleRates: [48e3, 96e3, 192e3, 384e3],
|
|
669
967
|
driveHasTrx: false,
|
|
968
|
+
meterAdapter: new StandardTciMeterAdapter({ interval: { reportsApplied: false } }),
|
|
670
969
|
detect: (context) => {
|
|
671
970
|
const parsed = version(context);
|
|
672
971
|
if (!parsed || compareTciVersion(parsed, [1, 4]) > 0) return { score: 0, evidence: [] };
|
|
@@ -682,6 +981,7 @@ var expertSdrLegacyDialect = new StandardTciDialect({
|
|
|
682
981
|
supportsIqStream: true,
|
|
683
982
|
iqSampleRates: [48e3, 96e3, 192e3, 384e3],
|
|
684
983
|
driveHasTrx: true,
|
|
984
|
+
meterAdapter: new StandardTciMeterAdapter({ interval: { reportsApplied: false } }),
|
|
685
985
|
detect: (context) => {
|
|
686
986
|
const parsed = version(context);
|
|
687
987
|
if (!parsed || compareTciVersion(parsed, [1, 5]) < 0 || compareTciVersion(parsed, [1, 9]) >= 0) return { score: 0, evidence: [] };
|
|
@@ -697,6 +997,7 @@ var expertSdrModernDialect = new StandardTciDialect({
|
|
|
697
997
|
supportsIqStream: true,
|
|
698
998
|
iqSampleRates: [48e3, 96e3, 192e3, 384e3],
|
|
699
999
|
driveHasTrx: true,
|
|
1000
|
+
meterAdapter: new StandardTciMeterAdapter({ interval: { reportsApplied: false } }),
|
|
700
1001
|
detect: (context) => {
|
|
701
1002
|
const parsed = version(context);
|
|
702
1003
|
if (!parsed || compareTciVersion(parsed, [1, 9]) < 0) return { score: 0, evidence: [] };
|
|
@@ -717,6 +1018,10 @@ var thetisDialect = new StandardTciDialect({
|
|
|
717
1018
|
supportsIqStream: true,
|
|
718
1019
|
iqSampleRates: [48e3, 96e3, 192e3, 384e3],
|
|
719
1020
|
driveHasTrx: true,
|
|
1021
|
+
meterAdapter: new StandardTciMeterAdapter({
|
|
1022
|
+
interval: { minMs: 30, maxMs: 1e3 },
|
|
1023
|
+
supportsRxExtended: true
|
|
1024
|
+
}),
|
|
720
1025
|
detect: (context) => {
|
|
721
1026
|
const evidence = [];
|
|
722
1027
|
let score = 0;
|
|
@@ -745,6 +1050,10 @@ var aetherSdrDialect = new StandardTciDialect({
|
|
|
745
1050
|
supportsIqStream: true,
|
|
746
1051
|
iqSampleRates: [24e3, 48e3, 96e3, 192e3],
|
|
747
1052
|
driveHasTrx: true,
|
|
1053
|
+
meterAdapter: new StandardTciMeterAdapter({
|
|
1054
|
+
interval: { fixedMs: 200 },
|
|
1055
|
+
txAlcUnit: "dbfs"
|
|
1056
|
+
}),
|
|
748
1057
|
detect: (context) => {
|
|
749
1058
|
if (!/^aethersdr$/i.test(context.identity.device ?? "")) return { score: 0, evidence: [] };
|
|
750
1059
|
const evidence = [`AetherSDR device identity: ${context.identity.device}`];
|
|
@@ -766,6 +1075,7 @@ var genericObservedDialect = new StandardTciDialect({
|
|
|
766
1075
|
supportsIqStream: false,
|
|
767
1076
|
iqSampleRates: [],
|
|
768
1077
|
driveHasTrx: true,
|
|
1078
|
+
meterAdapter: createUnknownTciMeterAdapter(),
|
|
769
1079
|
detect: (context) => ({
|
|
770
1080
|
score: context.commandNames.has("ready") ? 10 : 0,
|
|
771
1081
|
evidence: ["No vendor-specific match; using observed command shapes"],
|
|
@@ -783,6 +1093,7 @@ var genericObservedDialect = new StandardTciDialect({
|
|
|
783
1093
|
supportsIqStream: context.commandNames.has("iq_samplerate"),
|
|
784
1094
|
iqSampleRates: context.commandNames.has("iq_samplerate") ? [48e3] : [],
|
|
785
1095
|
driveHasTrx,
|
|
1096
|
+
meterAdapter: createUnknownTciMeterAdapter(),
|
|
786
1097
|
detect: genericObservedDialect.detect.bind(genericObservedDialect)
|
|
787
1098
|
});
|
|
788
1099
|
}
|
|
@@ -881,9 +1192,9 @@ function assertValidTciHandshake(commands) {
|
|
|
881
1192
|
}
|
|
882
1193
|
|
|
883
1194
|
// src/transport/WebSocketTransport.ts
|
|
884
|
-
import { EventEmitter } from "eventemitter3";
|
|
1195
|
+
import { EventEmitter as EventEmitter2 } from "eventemitter3";
|
|
885
1196
|
import WebSocket from "ws";
|
|
886
|
-
var WebSocketTciTransport = class extends
|
|
1197
|
+
var WebSocketTciTransport = class extends EventEmitter2 {
|
|
887
1198
|
constructor(url, WebSocketImpl = WebSocket) {
|
|
888
1199
|
super();
|
|
889
1200
|
this.url = url;
|
|
@@ -993,7 +1304,7 @@ function dataToBuffer(data) {
|
|
|
993
1304
|
}
|
|
994
1305
|
|
|
995
1306
|
// src/client/TciClient.ts
|
|
996
|
-
var TciIqStreamSession = class extends
|
|
1307
|
+
var TciIqStreamSession = class extends EventEmitter3 {
|
|
997
1308
|
receiver;
|
|
998
1309
|
_appliedSampleRate;
|
|
999
1310
|
callbacks;
|
|
@@ -1064,7 +1375,7 @@ var TciIqStreamSession = class extends EventEmitter2 {
|
|
|
1064
1375
|
firstFrame.reject(error);
|
|
1065
1376
|
}
|
|
1066
1377
|
};
|
|
1067
|
-
var TciClient = class extends
|
|
1378
|
+
var TciClient = class extends EventEmitter3 {
|
|
1068
1379
|
options;
|
|
1069
1380
|
WebSocketImpl;
|
|
1070
1381
|
transportFactory;
|
|
@@ -1079,6 +1390,7 @@ var TciClient = class extends EventEmitter2 {
|
|
|
1079
1390
|
initializationCommands = [];
|
|
1080
1391
|
handshakeWaiter;
|
|
1081
1392
|
activeIqSession;
|
|
1393
|
+
activeMeterSession;
|
|
1082
1394
|
constructor(options) {
|
|
1083
1395
|
super();
|
|
1084
1396
|
this.options = {
|
|
@@ -1144,6 +1456,7 @@ var TciClient = class extends EventEmitter2 {
|
|
|
1144
1456
|
}
|
|
1145
1457
|
}
|
|
1146
1458
|
async disconnect(code = 1e3, reason = "client disconnect") {
|
|
1459
|
+
await this.activeMeterSession?.close().catch(() => void 0);
|
|
1147
1460
|
const transport = this.transport;
|
|
1148
1461
|
if (!transport) return;
|
|
1149
1462
|
await transport.disconnect(code, reason);
|
|
@@ -1166,6 +1479,71 @@ var TciClient = class extends EventEmitter2 {
|
|
|
1166
1479
|
supportedSampleRates: [...dialect.iqSampleRates]
|
|
1167
1480
|
};
|
|
1168
1481
|
}
|
|
1482
|
+
getMeterCapabilities() {
|
|
1483
|
+
if (this.activeMeterSession) return this.activeMeterSession.getCapabilities();
|
|
1484
|
+
const adapter = this.activeDialect?.meterAdapter;
|
|
1485
|
+
return adapter ? cloneMeterCapabilities(adapter.declaredCapabilities) : cloneMeterCapabilities(UNKNOWN_TCI_METER_CAPABILITIES);
|
|
1486
|
+
}
|
|
1487
|
+
async openMeterStream(options = {}) {
|
|
1488
|
+
if (this.activeMeterSession) throw new TciError("protocol-error", "This TCI client already has an active meter stream session");
|
|
1489
|
+
this.requireDialect();
|
|
1490
|
+
const receiver = normalizeNonNegativeInteger(options.receiver ?? this.options.receiver, "meter receiver");
|
|
1491
|
+
const channel = normalizeNonNegativeInteger(options.channel ?? this.options.vfo, "meter channel");
|
|
1492
|
+
const trx = normalizeNonNegativeInteger(options.trx ?? this.options.trx, "meter transmitter");
|
|
1493
|
+
const rxEnabled = options.rx ?? true;
|
|
1494
|
+
const txEnabled = options.tx ?? true;
|
|
1495
|
+
if (!rxEnabled && !txEnabled) throw new TciError("protocol-error", "TCI meter stream must enable RX, TX, or both");
|
|
1496
|
+
const requestedIntervalMs = Math.round(options.intervalMs ?? 300);
|
|
1497
|
+
if (!Number.isFinite(requestedIntervalMs) || requestedIntervalMs <= 0) {
|
|
1498
|
+
throw new TciError("protocol-error", `Invalid TCI meter interval: ${options.intervalMs}`);
|
|
1499
|
+
}
|
|
1500
|
+
const adapter = this.activeDialect?.meterAdapter ?? createUnknownTciMeterAdapter();
|
|
1501
|
+
const interval = adapter.normalizeInterval(requestedIntervalMs);
|
|
1502
|
+
let session;
|
|
1503
|
+
session = new TciMeterStreamSession({
|
|
1504
|
+
receiver,
|
|
1505
|
+
channel,
|
|
1506
|
+
trx,
|
|
1507
|
+
requestedIntervalMs: interval.requestedMs,
|
|
1508
|
+
appliedIntervalMs: interval.appliedMs,
|
|
1509
|
+
rxEnabled,
|
|
1510
|
+
txEnabled,
|
|
1511
|
+
adapter,
|
|
1512
|
+
callbacks: {
|
|
1513
|
+
close: async () => {
|
|
1514
|
+
try {
|
|
1515
|
+
if (this.isConnected()) {
|
|
1516
|
+
if (rxEnabled) {
|
|
1517
|
+
const command = adapter.buildEnableCommand("rx", false, interval.appliedMs ?? interval.requestedMs);
|
|
1518
|
+
await this.sendCommand(command.name, command.args, { waitForReply: false }).catch(() => void 0);
|
|
1519
|
+
}
|
|
1520
|
+
if (txEnabled) {
|
|
1521
|
+
const command = adapter.buildEnableCommand("tx", false, interval.appliedMs ?? interval.requestedMs);
|
|
1522
|
+
await this.sendCommand(command.name, command.args, { waitForReply: false }).catch(() => void 0);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
} finally {
|
|
1526
|
+
if (this.activeMeterSession === session) this.activeMeterSession = void 0;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
this.activeMeterSession = session;
|
|
1532
|
+
try {
|
|
1533
|
+
if (rxEnabled) {
|
|
1534
|
+
const command = adapter.buildEnableCommand("rx", true, interval.appliedMs ?? interval.requestedMs);
|
|
1535
|
+
await this.sendCommand(command.name, command.args, { waitForReply: false });
|
|
1536
|
+
}
|
|
1537
|
+
if (txEnabled) {
|
|
1538
|
+
const command = adapter.buildEnableCommand("tx", true, interval.appliedMs ?? interval.requestedMs);
|
|
1539
|
+
await this.sendCommand(command.name, command.args, { waitForReply: false });
|
|
1540
|
+
}
|
|
1541
|
+
return session;
|
|
1542
|
+
} catch (error) {
|
|
1543
|
+
await session.close().catch(() => void 0);
|
|
1544
|
+
throw error;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1169
1547
|
async setIqSampleRate(sampleRate, timeoutMs = this.options.commandTimeoutMs) {
|
|
1170
1548
|
const capabilities = this.getIqCapabilities();
|
|
1171
1549
|
const requested = Math.round(sampleRate);
|
|
@@ -1690,11 +2068,13 @@ var TciClient = class extends EventEmitter2 {
|
|
|
1690
2068
|
handleText(raw) {
|
|
1691
2069
|
try {
|
|
1692
2070
|
const commands = parseTciText(raw);
|
|
2071
|
+
const receivedAtMs = Date.now();
|
|
1693
2072
|
this.emit("tci:rx", raw, commands);
|
|
1694
2073
|
for (const command of commands) {
|
|
1695
2074
|
if (!this.handshakeResult) this.initializationCommands.push(command);
|
|
1696
2075
|
this.queue.handleCommand(command);
|
|
1697
2076
|
this.applyCommand(command);
|
|
2077
|
+
this.activeMeterSession?._acceptCommand(command, receivedAtMs);
|
|
1698
2078
|
this.emit("command", command);
|
|
1699
2079
|
}
|
|
1700
2080
|
} catch (error) {
|
|
@@ -1814,6 +2194,7 @@ var TciClient = class extends EventEmitter2 {
|
|
|
1814
2194
|
reducers.set("tune_drive", (args) => this.applyTuneDrive(args));
|
|
1815
2195
|
reducers.set("split_enable", (args) => this.applyBooleanByFirstArg(this.state.split, args));
|
|
1816
2196
|
reducers.set("rx_channel_sensors", (args) => this.applyRxChannelSensors(args));
|
|
2197
|
+
reducers.set("rx_channel_sensors_ex", (args) => this.applyRxChannelSensors(args));
|
|
1817
2198
|
reducers.set("rx_sensors", (args) => this.applyRxSensors(args));
|
|
1818
2199
|
reducers.set("tx_sensors", (args) => this.applyTxSensors(args));
|
|
1819
2200
|
reducers.set("audio_samplerate", (args) => this.updateAudioState({ sampleRate: parseNumber(args[0]) }));
|
|
@@ -1941,6 +2322,8 @@ var TciClient = class extends EventEmitter2 {
|
|
|
1941
2322
|
this.rejectHandshake(new TciError("disconnected", "TCI connection closed during handshake", reason));
|
|
1942
2323
|
this.activeIqSession?._fail(new TciError("disconnected", "TCI connection closed while IQ stream was active", reason));
|
|
1943
2324
|
this.activeIqSession = void 0;
|
|
2325
|
+
this.activeMeterSession?._fail(new TciError("disconnected", "TCI connection closed while meter stream was active", reason));
|
|
2326
|
+
this.activeMeterSession = void 0;
|
|
1944
2327
|
if (wasConnected) {
|
|
1945
2328
|
this.emit("disconnected", reason);
|
|
1946
2329
|
this.emitState();
|
|
@@ -1967,6 +2350,12 @@ function parseNumber(value) {
|
|
|
1967
2350
|
const number = Number(value);
|
|
1968
2351
|
return Number.isFinite(number) ? number : void 0;
|
|
1969
2352
|
}
|
|
2353
|
+
function normalizeNonNegativeInteger(value, description) {
|
|
2354
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
2355
|
+
throw new TciError("protocol-error", `Invalid TCI ${description}: ${value}`);
|
|
2356
|
+
}
|
|
2357
|
+
return value;
|
|
2358
|
+
}
|
|
1970
2359
|
function parseBoolean(value) {
|
|
1971
2360
|
if (value === void 0) {
|
|
1972
2361
|
return void 0;
|
|
@@ -2042,23 +2431,28 @@ function cloneNested(value) {
|
|
|
2042
2431
|
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, { ...item }]));
|
|
2043
2432
|
}
|
|
2044
2433
|
export {
|
|
2434
|
+
StandardTciMeterAdapter,
|
|
2045
2435
|
TCI_STREAM_HEADER_BYTES,
|
|
2046
2436
|
TciClient,
|
|
2047
2437
|
TciCommandQueue,
|
|
2048
2438
|
TciDialectRegistry,
|
|
2049
2439
|
TciError,
|
|
2050
2440
|
TciIqStreamSession,
|
|
2441
|
+
TciMeterStreamSession,
|
|
2051
2442
|
TciSampleType,
|
|
2052
2443
|
TciStreamType,
|
|
2444
|
+
UNKNOWN_TCI_METER_CAPABILITIES,
|
|
2053
2445
|
WebSocketTciTransport,
|
|
2054
2446
|
aetherSdrDialect,
|
|
2055
2447
|
assertValidTciHandshake,
|
|
2056
2448
|
buildStreamFrame,
|
|
2057
2449
|
buildTxAudioFrame,
|
|
2058
2450
|
builtInDialects,
|
|
2451
|
+
cloneMeterCapabilities,
|
|
2059
2452
|
commandKey,
|
|
2060
2453
|
compareTciVersion,
|
|
2061
2454
|
createTciClient,
|
|
2455
|
+
createUnknownTciMeterAdapter,
|
|
2062
2456
|
decodeInterleavedIq,
|
|
2063
2457
|
defaultTciDialectRegistry,
|
|
2064
2458
|
deinterleaveChannels,
|