tci-client-node 0.2.0 → 0.3.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 +8 -0
- package/README.md +26 -6
- package/dist/audio/index.cjs +11 -0
- package/dist/audio/index.cjs.map +1 -1
- package/dist/audio/index.d.cts +3 -2
- package/dist/audio/index.d.ts +3 -2
- package/dist/audio/index.js +10 -0
- package/dist/audio/index.js.map +1 -1
- package/dist/dialect/index.cjs +18 -0
- package/dist/dialect/index.cjs.map +1 -1
- package/dist/dialect/index.d.cts +2 -2
- package/dist/dialect/index.d.ts +2 -2
- package/dist/dialect/index.js +18 -0
- package/dist/dialect/index.js.map +1 -1
- package/dist/index.cjs +203 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -4
- package/dist/index.d.ts +57 -4
- package/dist/index.js +201 -2
- package/dist/index.js.map +1 -1
- package/dist/testing/index.cjs +26 -0
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +5 -1
- package/dist/testing/index.d.ts +5 -1
- package/dist/testing/index.js +26 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/{types-xRotf9gW.d.cts → types-1YXGwrgI.d.cts} +3 -1
- package/dist/{types-9seY9Th-.d.ts → types-BtPh4Dbd.d.ts} +3 -1
- package/package.json +1 -1
package/dist/dialect/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { d as TciDialect,
|
|
2
|
-
export { B as BuiltInTciDialectId, g as TciDialectScore, h as TciDriveState,
|
|
1
|
+
import { d as TciDialect, a as TciDialectId, f as TciDialectDetectionContext, c as TciDialectSelection, e as TciDialectDetection, i as TciProtocolIdentity } from '../types-BtPh4Dbd.js';
|
|
2
|
+
export { B as BuiltInTciDialectId, g as TciDialectScore, h as TciDriveState, b as TciHandshakeResult, j as TciStreamLengthSemantics, T as TciWriteResult } from '../types-BtPh4Dbd.js';
|
|
3
3
|
import { T as TciCommand } from '../text-BwCWY1k1.js';
|
|
4
4
|
|
|
5
5
|
type VersionTuple = readonly number[];
|
package/dist/dialect/index.js
CHANGED
|
@@ -5,6 +5,8 @@ var StandardTciDialect = class {
|
|
|
5
5
|
streamLengthSemantics;
|
|
6
6
|
supportsStreamChannels;
|
|
7
7
|
supportsTxAudioSource;
|
|
8
|
+
supportsIqStream;
|
|
9
|
+
iqSampleRates;
|
|
8
10
|
driveHasTrx;
|
|
9
11
|
detector;
|
|
10
12
|
resolver;
|
|
@@ -14,6 +16,8 @@ var StandardTciDialect = class {
|
|
|
14
16
|
this.streamLengthSemantics = options.streamLengthSemantics;
|
|
15
17
|
this.supportsStreamChannels = options.supportsStreamChannels;
|
|
16
18
|
this.supportsTxAudioSource = options.supportsTxAudioSource;
|
|
19
|
+
this.supportsIqStream = options.supportsIqStream;
|
|
20
|
+
this.iqSampleRates = [...options.iqSampleRates];
|
|
17
21
|
this.driveHasTrx = options.driveHasTrx;
|
|
18
22
|
this.detector = options.detect;
|
|
19
23
|
this.resolver = options.resolve;
|
|
@@ -61,6 +65,8 @@ var expertSdr14Dialect = new StandardTciDialect({
|
|
|
61
65
|
streamLengthSemantics: "per-channel",
|
|
62
66
|
supportsStreamChannels: false,
|
|
63
67
|
supportsTxAudioSource: false,
|
|
68
|
+
supportsIqStream: true,
|
|
69
|
+
iqSampleRates: [48e3, 96e3, 192e3, 384e3],
|
|
64
70
|
driveHasTrx: false,
|
|
65
71
|
detect: (context) => {
|
|
66
72
|
const parsed = version(context);
|
|
@@ -74,6 +80,8 @@ var expertSdrLegacyDialect = new StandardTciDialect({
|
|
|
74
80
|
streamLengthSemantics: "per-channel",
|
|
75
81
|
supportsStreamChannels: false,
|
|
76
82
|
supportsTxAudioSource: false,
|
|
83
|
+
supportsIqStream: true,
|
|
84
|
+
iqSampleRates: [48e3, 96e3, 192e3, 384e3],
|
|
77
85
|
driveHasTrx: true,
|
|
78
86
|
detect: (context) => {
|
|
79
87
|
const parsed = version(context);
|
|
@@ -87,6 +95,8 @@ var expertSdrModernDialect = new StandardTciDialect({
|
|
|
87
95
|
streamLengthSemantics: "scalar",
|
|
88
96
|
supportsStreamChannels: true,
|
|
89
97
|
supportsTxAudioSource: true,
|
|
98
|
+
supportsIqStream: true,
|
|
99
|
+
iqSampleRates: [48e3, 96e3, 192e3, 384e3],
|
|
90
100
|
driveHasTrx: true,
|
|
91
101
|
detect: (context) => {
|
|
92
102
|
const parsed = version(context);
|
|
@@ -105,6 +115,8 @@ var thetisDialect = new StandardTciDialect({
|
|
|
105
115
|
streamLengthSemantics: "scalar",
|
|
106
116
|
supportsStreamChannels: true,
|
|
107
117
|
supportsTxAudioSource: true,
|
|
118
|
+
supportsIqStream: true,
|
|
119
|
+
iqSampleRates: [48e3, 96e3, 192e3, 384e3],
|
|
108
120
|
driveHasTrx: true,
|
|
109
121
|
detect: (context) => {
|
|
110
122
|
const evidence = [];
|
|
@@ -131,6 +143,8 @@ var aetherSdrDialect = new StandardTciDialect({
|
|
|
131
143
|
streamLengthSemantics: "scalar",
|
|
132
144
|
supportsStreamChannels: true,
|
|
133
145
|
supportsTxAudioSource: true,
|
|
146
|
+
supportsIqStream: true,
|
|
147
|
+
iqSampleRates: [24e3, 48e3, 96e3, 192e3],
|
|
134
148
|
driveHasTrx: true,
|
|
135
149
|
detect: (context) => {
|
|
136
150
|
if (!/^aethersdr$/i.test(context.identity.device ?? "")) return { score: 0, evidence: [] };
|
|
@@ -150,6 +164,8 @@ var genericObservedDialect = new StandardTciDialect({
|
|
|
150
164
|
streamLengthSemantics: "auto",
|
|
151
165
|
supportsStreamChannels: true,
|
|
152
166
|
supportsTxAudioSource: true,
|
|
167
|
+
supportsIqStream: false,
|
|
168
|
+
iqSampleRates: [],
|
|
153
169
|
driveHasTrx: true,
|
|
154
170
|
detect: (context) => ({
|
|
155
171
|
score: context.commandNames.has("ready") ? 10 : 0,
|
|
@@ -165,6 +181,8 @@ var genericObservedDialect = new StandardTciDialect({
|
|
|
165
181
|
streamLengthSemantics: "auto",
|
|
166
182
|
supportsStreamChannels: context.commandNames.has("audio_stream_channels"),
|
|
167
183
|
supportsTxAudioSource: compareTciVersion(version(context) ?? [0], [2, 0]) >= 0,
|
|
184
|
+
supportsIqStream: context.commandNames.has("iq_samplerate"),
|
|
185
|
+
iqSampleRates: context.commandNames.has("iq_samplerate") ? [48e3] : [],
|
|
168
186
|
driveHasTrx,
|
|
169
187
|
detect: genericObservedDialect.detect.bind(genericObservedDialect)
|
|
170
188
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/dialect/builtins.ts","../../src/errors.ts","../../src/dialect/registry.ts","../../src/dialect/handshake.ts"],"sourcesContent":["import type {\n TciDialect,\n TciDialectDetectionContext,\n TciDialectScore,\n TciDriveState,\n TciStreamLengthSemantics,\n} from './types.js';\n\ntype VersionTuple = readonly number[];\n\ninterface StandardDialectOptions {\n id: TciDialect['id'];\n label: string;\n streamLengthSemantics: TciStreamLengthSemantics;\n supportsStreamChannels: boolean;\n supportsTxAudioSource: boolean;\n driveHasTrx: boolean;\n detect: (context: TciDialectDetectionContext) => TciDialectScore;\n resolve?: (context: TciDialectDetectionContext) => TciDialect;\n}\n\nclass StandardTciDialect implements TciDialect {\n readonly id: TciDialect['id'];\n readonly label: string;\n readonly streamLengthSemantics: TciStreamLengthSemantics;\n readonly supportsStreamChannels: boolean;\n readonly supportsTxAudioSource: boolean;\n private readonly driveHasTrx: boolean;\n private readonly detector: StandardDialectOptions['detect'];\n private readonly resolver?: StandardDialectOptions['resolve'];\n\n constructor(options: StandardDialectOptions) {\n this.id = options.id;\n this.label = options.label;\n this.streamLengthSemantics = options.streamLengthSemantics;\n this.supportsStreamChannels = options.supportsStreamChannels;\n this.supportsTxAudioSource = options.supportsTxAudioSource;\n this.driveHasTrx = options.driveHasTrx;\n this.detector = options.detect;\n this.resolver = options.resolve;\n }\n\n detect(context: TciDialectDetectionContext): TciDialectScore {\n return this.detector(context);\n }\n\n resolve(context: TciDialectDetectionContext): TciDialect {\n return this.resolver?.(context) ?? this;\n }\n\n buildDriveSetArgs(trx: number, value: number): readonly unknown[] {\n return this.driveHasTrx ? [trx, value] : [value];\n }\n\n buildDriveReadArgs(trx: number): readonly unknown[] {\n return this.driveHasTrx ? [trx] : [];\n }\n\n parseDrive(args: readonly string[], defaultTrx: number): TciDriveState | undefined {\n return parseDriveState(args, defaultTrx, this.driveHasTrx);\n }\n\n buildTuneDriveSetArgs(trx: number, value: number): readonly unknown[] {\n return this.driveHasTrx ? [trx, value] : [value];\n }\n\n buildTuneDriveReadArgs(trx: number): readonly unknown[] {\n return this.driveHasTrx ? [trx] : [];\n }\n\n parseTuneDrive(args: readonly string[], defaultTrx: number): TciDriveState | undefined {\n return parseDriveState(args, defaultTrx, this.driveHasTrx);\n }\n}\n\nfunction parseDriveState(args: readonly string[], defaultTrx: number, hasTrx: boolean): TciDriveState | undefined {\n const trx = hasTrx ? Number(args[0]) : defaultTrx;\n const value = Number(args[hasTrx ? 1 : 0]);\n if (!Number.isInteger(trx) || !Number.isFinite(value)) return undefined;\n return { trx, value };\n}\n\nfunction version(context: TciDialectDetectionContext): VersionTuple | undefined {\n return parseTciVersion(context.identity.protocolVersion);\n}\n\nfunction programIncludes(context: TciDialectDetectionContext, value: string): boolean {\n return context.identity.programName?.toLowerCase().includes(value) ?? false;\n}\n\nexport const expertSdr14Dialect: TciDialect = new StandardTciDialect({\n id: 'expertsdr-1.4', label: 'ExpertSDR / TCI 1.4', streamLengthSemantics: 'per-channel',\n supportsStreamChannels: false, supportsTxAudioSource: false, driveHasTrx: false,\n detect: (context) => {\n const parsed = version(context);\n if (!parsed || compareTciVersion(parsed, [1, 4]) > 0) return { score: 0, evidence: [] };\n return { score: 80 + (programIncludes(context, 'expert') ? 10 : 0), evidence: [`protocol ${formatVersion(parsed)} <= 1.4`] };\n },\n});\n\nexport const expertSdrLegacyDialect: TciDialect = new StandardTciDialect({\n id: 'expertsdr-1.5-1.8', label: 'ExpertSDR / TCI 1.5-1.8', streamLengthSemantics: 'per-channel',\n supportsStreamChannels: false, supportsTxAudioSource: false, driveHasTrx: true,\n detect: (context) => {\n const parsed = version(context);\n if (!parsed || compareTciVersion(parsed, [1, 5]) < 0 || compareTciVersion(parsed, [1, 9]) >= 0) return { score: 0, evidence: [] };\n return { score: 80 + (programIncludes(context, 'expert') ? 10 : 0), evidence: [`protocol ${formatVersion(parsed)} is in 1.5-1.8`] };\n },\n});\n\nexport const expertSdrModernDialect: TciDialect = new StandardTciDialect({\n id: 'expertsdr-1.9-2.0', label: 'ExpertSDR / TCI 1.9-2.0', streamLengthSemantics: 'scalar',\n supportsStreamChannels: true, supportsTxAudioSource: true, driveHasTrx: true,\n detect: (context) => {\n const parsed = version(context);\n if (!parsed || compareTciVersion(parsed, [1, 9]) < 0) return { score: 0, evidence: [] };\n const future = compareTciVersion(parsed, [2, 0]) > 0;\n return {\n score: 70 + (programIncludes(context, 'expert') ? 15 : 0),\n evidence: [`protocol ${formatVersion(parsed)} uses modern stream negotiation`],\n warnings: future ? [`Unknown future TCI version ${formatVersion(parsed)}; using the modern dialect`] : [],\n };\n },\n});\n\nexport const thetisDialect: TciDialect = new StandardTciDialect({\n id: 'thetis-2.0', label: 'Thetis / TCI 2.0', streamLengthSemantics: 'scalar',\n supportsStreamChannels: true, supportsTxAudioSource: true, driveHasTrx: true,\n detect: (context) => {\n const evidence: string[] = [];\n let score = 0;\n if (programIncludes(context, 'thetis')) { score += 120; evidence.push('PROTOCOL program is Thetis'); }\n const observed = ['tx_frequency_ex', 'tx_profiles_ex', 'tx_profile_ex', 'calibration_ex']\n .filter((name) => context.commandNames.has(name));\n if (observed.length > 0) { score += 100; evidence.push(`Thetis extension commands: ${observed.join(', ')}`); }\n if (/anan|hermes|orion|saturn/i.test(context.identity.device ?? '')) {\n score += 30;\n evidence.push(`Thetis-family device: ${context.identity.device}`);\n }\n return { score, evidence };\n },\n});\n\nexport const aetherSdrDialect: TciDialect = new StandardTciDialect({\n id: 'aethersdr-1.5', label: 'AetherSDR / TCI 1.5 hybrid', streamLengthSemantics: 'scalar',\n supportsStreamChannels: true, supportsTxAudioSource: true, driveHasTrx: true,\n detect: (context) => {\n if (!/^aethersdr$/i.test(context.identity.device ?? '')) return { score: 0, evidence: [] };\n const evidence = [`AetherSDR device identity: ${context.identity.device}`];\n const modernAudioCommands = ['audio_stream_sample_type', 'audio_stream_channels', 'audio_stream_samples']\n .filter((name) => context.commandNames.has(name));\n if (modernAudioCommands.length > 0) evidence.push(`Modern audio negotiation: ${modernAudioCommands.join(', ')}`);\n return {\n score: 150,\n evidence,\n warnings: context.identity.protocolVersion === '1.5'\n ? ['AetherSDR reports TCI 1.5 but uses modern scalar audio stream semantics']\n : [],\n };\n },\n});\n\nexport const genericObservedDialect: TciDialect = new StandardTciDialect({\n id: 'generic-observed', label: 'Generic observed TCI', streamLengthSemantics: 'auto',\n supportsStreamChannels: true, supportsTxAudioSource: true, driveHasTrx: true,\n detect: (context) => ({\n score: context.commandNames.has('ready') ? 10 : 0,\n evidence: ['No vendor-specific match; using observed command shapes'],\n warnings: ['Dialect identity is uncertain'],\n }),\n resolve: (context) => {\n const drive = [...context.commands].reverse().find((command) => command.name === 'drive');\n const driveHasTrx = (drive?.args.length ?? 0) >= 2;\n return new StandardTciDialect({\n id: 'generic-observed',\n label: 'Generic observed TCI',\n streamLengthSemantics: 'auto',\n supportsStreamChannels: context.commandNames.has('audio_stream_channels'),\n supportsTxAudioSource: compareTciVersion(version(context) ?? [0], [2, 0]) >= 0,\n driveHasTrx,\n detect: genericObservedDialect.detect.bind(genericObservedDialect),\n });\n },\n});\n\nexport const builtInDialects: readonly TciDialect[] = [\n aetherSdrDialect, thetisDialect, expertSdr14Dialect, expertSdrLegacyDialect, expertSdrModernDialect, genericObservedDialect,\n];\n\nexport function parseTciVersion(value: string | undefined): VersionTuple | undefined {\n if (!value) return undefined;\n const match = value.trim().match(/^(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?/);\n if (!match) return undefined;\n return match.slice(1).filter((part): part is string => part !== undefined).map(Number);\n}\n\nexport function compareTciVersion(left: VersionTuple, right: VersionTuple): number {\n const length = Math.max(left.length, right.length);\n for (let index = 0; index < length; index += 1) {\n const difference = (left[index] ?? 0) - (right[index] ?? 0);\n if (difference !== 0) return difference;\n }\n return 0;\n}\n\nfunction formatVersion(value: VersionTuple): string { return value.join('.'); }\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","import { TciError } from '../errors.js';\nimport { builtInDialects } from './builtins.js';\nimport type {\n TciDialect,\n TciDialectDetection,\n TciDialectDetectionContext,\n TciDialectId,\n TciDialectSelection,\n} from './types.js';\n\nexport class TciDialectRegistry {\n private readonly dialects = new Map<TciDialectId, TciDialect>();\n\n constructor(dialects: readonly TciDialect[] = builtInDialects) {\n for (const dialect of dialects) this.register(dialect);\n }\n\n register(dialect: TciDialect): void { this.dialects.set(dialect.id, dialect); }\n get(id: TciDialectId): TciDialect | undefined { return this.dialects.get(id); }\n list(): TciDialect[] { return [...this.dialects.values()]; }\n\n select(context: TciDialectDetectionContext, selection: TciDialectSelection = 'auto'): TciDialectDetection {\n if (typeof selection === 'object') {\n return { dialect: selection, confidence: 'manual', evidence: ['Custom dialect supplied by caller'], warnings: [] };\n }\n if (selection !== 'auto') {\n const dialect = this.get(selection);\n if (!dialect) throw new TciError('unknown-dialect', `Unknown TCI dialect: ${selection}`);\n return { dialect: dialect.resolve?.(context) ?? dialect, confidence: 'manual', evidence: [`Dialect ${selection} selected by caller`], warnings: [] };\n }\n\n const candidates = this.list()\n .map((dialect) => ({ dialect, result: dialect.detect(context) }))\n .sort((left, right) => right.result.score - left.result.score);\n const selected = candidates[0];\n if (!selected || selected.result.score <= 0) {\n throw new TciError('unknown-dialect', 'Unable to identify the TCI server dialect');\n }\n return {\n dialect: selected.dialect.resolve?.(context) ?? selected.dialect,\n confidence: selected.result.score >= 100 ? 'high' : selected.result.score >= 70 ? 'medium' : 'low',\n evidence: selected.result.evidence,\n warnings: selected.result.warnings ?? [],\n };\n }\n}\n\nexport const defaultTciDialectRegistry = new TciDialectRegistry();\n","import { TciError } from '../errors.js';\nimport type { TciCommand } from '../protocol/text.js';\nimport type { TciProtocolIdentity } from './types.js';\n\nconst IDENTITY_COMMANDS = new Set(['protocol', 'device', 'trx_count', 'channels_count', 'channel_count']);\nconst STATE_COMMANDS = new Set(['vfo', 'modulation', 'modulations_list', 'trx', 'drive']);\n\nexport function parseProtocolIdentity(commands: readonly TciCommand[]): TciProtocolIdentity {\n const protocol = [...commands].reverse().find((command) => command.name === 'protocol');\n const device = [...commands].reverse().find((command) => command.name === 'device');\n const rawProtocolArgs = protocol?.args ?? [];\n const firstLooksLikeVersion = /^\\d+(?:\\.\\d+){0,2}/.test(rawProtocolArgs[0] ?? '');\n return {\n programName: firstLooksLikeVersion ? undefined : rawProtocolArgs[0],\n protocolVersion: firstLooksLikeVersion ? rawProtocolArgs[0] : rawProtocolArgs[1],\n rawProtocolArgs: [...rawProtocolArgs],\n device: device?.args.join(','),\n };\n}\n\nexport function assertValidTciHandshake(commands: readonly TciCommand[]): void {\n const names = new Set(commands.map((command) => command.name));\n if (!names.has('ready')) throw new TciError('handshake-timeout', 'TCI READY was not received');\n const categories = [\n [...IDENTITY_COMMANDS].some((name) => names.has(name)),\n [...STATE_COMMANDS].some((name) => names.has(name)),\n ].filter(Boolean).length;\n const identitySignals = [...IDENTITY_COMMANDS].filter((name) => names.has(name)).length;\n if (categories < 2 && identitySignals < 2) {\n throw new TciError('invalid-handshake', 'WebSocket opened but did not provide enough TCI initialization evidence');\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/dialect/builtins.ts","../../src/errors.ts","../../src/dialect/registry.ts","../../src/dialect/handshake.ts"],"sourcesContent":["import type {\n TciDialect,\n TciDialectDetectionContext,\n TciDialectScore,\n TciDriveState,\n TciStreamLengthSemantics,\n} from './types.js';\n\ntype VersionTuple = readonly number[];\n\ninterface StandardDialectOptions {\n id: TciDialect['id'];\n label: string;\n streamLengthSemantics: TciStreamLengthSemantics;\n supportsStreamChannels: boolean;\n supportsTxAudioSource: boolean;\n supportsIqStream: boolean;\n iqSampleRates: readonly number[];\n driveHasTrx: boolean;\n detect: (context: TciDialectDetectionContext) => TciDialectScore;\n resolve?: (context: TciDialectDetectionContext) => TciDialect;\n}\n\nclass StandardTciDialect implements TciDialect {\n readonly id: TciDialect['id'];\n readonly label: string;\n readonly streamLengthSemantics: TciStreamLengthSemantics;\n readonly supportsStreamChannels: boolean;\n readonly supportsTxAudioSource: boolean;\n readonly supportsIqStream: boolean;\n readonly iqSampleRates: readonly number[];\n private readonly driveHasTrx: boolean;\n private readonly detector: StandardDialectOptions['detect'];\n private readonly resolver?: StandardDialectOptions['resolve'];\n\n constructor(options: StandardDialectOptions) {\n this.id = options.id;\n this.label = options.label;\n this.streamLengthSemantics = options.streamLengthSemantics;\n this.supportsStreamChannels = options.supportsStreamChannels;\n this.supportsTxAudioSource = options.supportsTxAudioSource;\n this.supportsIqStream = options.supportsIqStream;\n this.iqSampleRates = [...options.iqSampleRates];\n this.driveHasTrx = options.driveHasTrx;\n this.detector = options.detect;\n this.resolver = options.resolve;\n }\n\n detect(context: TciDialectDetectionContext): TciDialectScore {\n return this.detector(context);\n }\n\n resolve(context: TciDialectDetectionContext): TciDialect {\n return this.resolver?.(context) ?? this;\n }\n\n buildDriveSetArgs(trx: number, value: number): readonly unknown[] {\n return this.driveHasTrx ? [trx, value] : [value];\n }\n\n buildDriveReadArgs(trx: number): readonly unknown[] {\n return this.driveHasTrx ? [trx] : [];\n }\n\n parseDrive(args: readonly string[], defaultTrx: number): TciDriveState | undefined {\n return parseDriveState(args, defaultTrx, this.driveHasTrx);\n }\n\n buildTuneDriveSetArgs(trx: number, value: number): readonly unknown[] {\n return this.driveHasTrx ? [trx, value] : [value];\n }\n\n buildTuneDriveReadArgs(trx: number): readonly unknown[] {\n return this.driveHasTrx ? [trx] : [];\n }\n\n parseTuneDrive(args: readonly string[], defaultTrx: number): TciDriveState | undefined {\n return parseDriveState(args, defaultTrx, this.driveHasTrx);\n }\n}\n\nfunction parseDriveState(args: readonly string[], defaultTrx: number, hasTrx: boolean): TciDriveState | undefined {\n const trx = hasTrx ? Number(args[0]) : defaultTrx;\n const value = Number(args[hasTrx ? 1 : 0]);\n if (!Number.isInteger(trx) || !Number.isFinite(value)) return undefined;\n return { trx, value };\n}\n\nfunction version(context: TciDialectDetectionContext): VersionTuple | undefined {\n return parseTciVersion(context.identity.protocolVersion);\n}\n\nfunction programIncludes(context: TciDialectDetectionContext, value: string): boolean {\n return context.identity.programName?.toLowerCase().includes(value) ?? false;\n}\n\nexport const expertSdr14Dialect: TciDialect = new StandardTciDialect({\n id: 'expertsdr-1.4', label: 'ExpertSDR / TCI 1.4', streamLengthSemantics: 'per-channel',\n supportsStreamChannels: false, supportsTxAudioSource: false, supportsIqStream: true,\n iqSampleRates: [48_000, 96_000, 192_000, 384_000], driveHasTrx: false,\n detect: (context) => {\n const parsed = version(context);\n if (!parsed || compareTciVersion(parsed, [1, 4]) > 0) return { score: 0, evidence: [] };\n return { score: 80 + (programIncludes(context, 'expert') ? 10 : 0), evidence: [`protocol ${formatVersion(parsed)} <= 1.4`] };\n },\n});\n\nexport const expertSdrLegacyDialect: TciDialect = new StandardTciDialect({\n id: 'expertsdr-1.5-1.8', label: 'ExpertSDR / TCI 1.5-1.8', streamLengthSemantics: 'per-channel',\n supportsStreamChannels: false, supportsTxAudioSource: false, supportsIqStream: true,\n iqSampleRates: [48_000, 96_000, 192_000, 384_000], driveHasTrx: true,\n detect: (context) => {\n const parsed = version(context);\n if (!parsed || compareTciVersion(parsed, [1, 5]) < 0 || compareTciVersion(parsed, [1, 9]) >= 0) return { score: 0, evidence: [] };\n return { score: 80 + (programIncludes(context, 'expert') ? 10 : 0), evidence: [`protocol ${formatVersion(parsed)} is in 1.5-1.8`] };\n },\n});\n\nexport const expertSdrModernDialect: TciDialect = new StandardTciDialect({\n id: 'expertsdr-1.9-2.0', label: 'ExpertSDR / TCI 1.9-2.0', streamLengthSemantics: 'scalar',\n supportsStreamChannels: true, supportsTxAudioSource: true, supportsIqStream: true,\n iqSampleRates: [48_000, 96_000, 192_000, 384_000], driveHasTrx: true,\n detect: (context) => {\n const parsed = version(context);\n if (!parsed || compareTciVersion(parsed, [1, 9]) < 0) return { score: 0, evidence: [] };\n const future = compareTciVersion(parsed, [2, 0]) > 0;\n return {\n score: 70 + (programIncludes(context, 'expert') ? 15 : 0),\n evidence: [`protocol ${formatVersion(parsed)} uses modern stream negotiation`],\n warnings: future ? [`Unknown future TCI version ${formatVersion(parsed)}; using the modern dialect`] : [],\n };\n },\n});\n\nexport const thetisDialect: TciDialect = new StandardTciDialect({\n id: 'thetis-2.0', label: 'Thetis / TCI 2.0', streamLengthSemantics: 'scalar',\n supportsStreamChannels: true, supportsTxAudioSource: true, supportsIqStream: true,\n iqSampleRates: [48_000, 96_000, 192_000, 384_000], driveHasTrx: true,\n detect: (context) => {\n const evidence: string[] = [];\n let score = 0;\n if (programIncludes(context, 'thetis')) { score += 120; evidence.push('PROTOCOL program is Thetis'); }\n const observed = ['tx_frequency_ex', 'tx_profiles_ex', 'tx_profile_ex', 'calibration_ex']\n .filter((name) => context.commandNames.has(name));\n if (observed.length > 0) { score += 100; evidence.push(`Thetis extension commands: ${observed.join(', ')}`); }\n if (/anan|hermes|orion|saturn/i.test(context.identity.device ?? '')) {\n score += 30;\n evidence.push(`Thetis-family device: ${context.identity.device}`);\n }\n return { score, evidence };\n },\n});\n\nexport const aetherSdrDialect: TciDialect = new StandardTciDialect({\n id: 'aethersdr-1.5', label: 'AetherSDR / TCI 1.5 hybrid', streamLengthSemantics: 'scalar',\n supportsStreamChannels: true, supportsTxAudioSource: true, supportsIqStream: true,\n iqSampleRates: [24_000, 48_000, 96_000, 192_000], driveHasTrx: true,\n detect: (context) => {\n if (!/^aethersdr$/i.test(context.identity.device ?? '')) return { score: 0, evidence: [] };\n const evidence = [`AetherSDR device identity: ${context.identity.device}`];\n const modernAudioCommands = ['audio_stream_sample_type', 'audio_stream_channels', 'audio_stream_samples']\n .filter((name) => context.commandNames.has(name));\n if (modernAudioCommands.length > 0) evidence.push(`Modern audio negotiation: ${modernAudioCommands.join(', ')}`);\n return {\n score: 150,\n evidence,\n warnings: context.identity.protocolVersion === '1.5'\n ? ['AetherSDR reports TCI 1.5 but uses modern scalar audio stream semantics']\n : [],\n };\n },\n});\n\nexport const genericObservedDialect: TciDialect = new StandardTciDialect({\n id: 'generic-observed', label: 'Generic observed TCI', streamLengthSemantics: 'auto',\n supportsStreamChannels: true, supportsTxAudioSource: true, supportsIqStream: false,\n iqSampleRates: [], driveHasTrx: true,\n detect: (context) => ({\n score: context.commandNames.has('ready') ? 10 : 0,\n evidence: ['No vendor-specific match; using observed command shapes'],\n warnings: ['Dialect identity is uncertain'],\n }),\n resolve: (context) => {\n const drive = [...context.commands].reverse().find((command) => command.name === 'drive');\n const driveHasTrx = (drive?.args.length ?? 0) >= 2;\n return new StandardTciDialect({\n id: 'generic-observed',\n label: 'Generic observed TCI',\n streamLengthSemantics: 'auto',\n supportsStreamChannels: context.commandNames.has('audio_stream_channels'),\n supportsTxAudioSource: compareTciVersion(version(context) ?? [0], [2, 0]) >= 0,\n supportsIqStream: context.commandNames.has('iq_samplerate'),\n iqSampleRates: context.commandNames.has('iq_samplerate') ? [48_000] : [],\n driveHasTrx,\n detect: genericObservedDialect.detect.bind(genericObservedDialect),\n });\n },\n});\n\nexport const builtInDialects: readonly TciDialect[] = [\n aetherSdrDialect, thetisDialect, expertSdr14Dialect, expertSdrLegacyDialect, expertSdrModernDialect, genericObservedDialect,\n];\n\nexport function parseTciVersion(value: string | undefined): VersionTuple | undefined {\n if (!value) return undefined;\n const match = value.trim().match(/^(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?/);\n if (!match) return undefined;\n return match.slice(1).filter((part): part is string => part !== undefined).map(Number);\n}\n\nexport function compareTciVersion(left: VersionTuple, right: VersionTuple): number {\n const length = Math.max(left.length, right.length);\n for (let index = 0; index < length; index += 1) {\n const difference = (left[index] ?? 0) - (right[index] ?? 0);\n if (difference !== 0) return difference;\n }\n return 0;\n}\n\nfunction formatVersion(value: VersionTuple): string { return value.join('.'); }\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","import { TciError } from '../errors.js';\nimport { builtInDialects } from './builtins.js';\nimport type {\n TciDialect,\n TciDialectDetection,\n TciDialectDetectionContext,\n TciDialectId,\n TciDialectSelection,\n} from './types.js';\n\nexport class TciDialectRegistry {\n private readonly dialects = new Map<TciDialectId, TciDialect>();\n\n constructor(dialects: readonly TciDialect[] = builtInDialects) {\n for (const dialect of dialects) this.register(dialect);\n }\n\n register(dialect: TciDialect): void { this.dialects.set(dialect.id, dialect); }\n get(id: TciDialectId): TciDialect | undefined { return this.dialects.get(id); }\n list(): TciDialect[] { return [...this.dialects.values()]; }\n\n select(context: TciDialectDetectionContext, selection: TciDialectSelection = 'auto'): TciDialectDetection {\n if (typeof selection === 'object') {\n return { dialect: selection, confidence: 'manual', evidence: ['Custom dialect supplied by caller'], warnings: [] };\n }\n if (selection !== 'auto') {\n const dialect = this.get(selection);\n if (!dialect) throw new TciError('unknown-dialect', `Unknown TCI dialect: ${selection}`);\n return { dialect: dialect.resolve?.(context) ?? dialect, confidence: 'manual', evidence: [`Dialect ${selection} selected by caller`], warnings: [] };\n }\n\n const candidates = this.list()\n .map((dialect) => ({ dialect, result: dialect.detect(context) }))\n .sort((left, right) => right.result.score - left.result.score);\n const selected = candidates[0];\n if (!selected || selected.result.score <= 0) {\n throw new TciError('unknown-dialect', 'Unable to identify the TCI server dialect');\n }\n return {\n dialect: selected.dialect.resolve?.(context) ?? selected.dialect,\n confidence: selected.result.score >= 100 ? 'high' : selected.result.score >= 70 ? 'medium' : 'low',\n evidence: selected.result.evidence,\n warnings: selected.result.warnings ?? [],\n };\n }\n}\n\nexport const defaultTciDialectRegistry = new TciDialectRegistry();\n","import { TciError } from '../errors.js';\nimport type { TciCommand } from '../protocol/text.js';\nimport type { TciProtocolIdentity } from './types.js';\n\nconst IDENTITY_COMMANDS = new Set(['protocol', 'device', 'trx_count', 'channels_count', 'channel_count']);\nconst STATE_COMMANDS = new Set(['vfo', 'modulation', 'modulations_list', 'trx', 'drive']);\n\nexport function parseProtocolIdentity(commands: readonly TciCommand[]): TciProtocolIdentity {\n const protocol = [...commands].reverse().find((command) => command.name === 'protocol');\n const device = [...commands].reverse().find((command) => command.name === 'device');\n const rawProtocolArgs = protocol?.args ?? [];\n const firstLooksLikeVersion = /^\\d+(?:\\.\\d+){0,2}/.test(rawProtocolArgs[0] ?? '');\n return {\n programName: firstLooksLikeVersion ? undefined : rawProtocolArgs[0],\n protocolVersion: firstLooksLikeVersion ? rawProtocolArgs[0] : rawProtocolArgs[1],\n rawProtocolArgs: [...rawProtocolArgs],\n device: device?.args.join(','),\n };\n}\n\nexport function assertValidTciHandshake(commands: readonly TciCommand[]): void {\n const names = new Set(commands.map((command) => command.name));\n if (!names.has('ready')) throw new TciError('handshake-timeout', 'TCI READY was not received');\n const categories = [\n [...IDENTITY_COMMANDS].some((name) => names.has(name)),\n [...STATE_COMMANDS].some((name) => names.has(name)),\n ].filter(Boolean).length;\n const identitySignals = [...IDENTITY_COMMANDS].filter((name) => names.has(name)).length;\n if (categories < 2 && identitySignals < 2) {\n throw new TciError('invalid-handshake', 'WebSocket opened but did not provide enough TCI initialization evidence');\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,7 @@ __export(src_exports, {
|
|
|
35
35
|
TciCommandQueue: () => TciCommandQueue,
|
|
36
36
|
TciDialectRegistry: () => TciDialectRegistry,
|
|
37
37
|
TciError: () => TciError,
|
|
38
|
+
TciIqStreamSession: () => TciIqStreamSession,
|
|
38
39
|
TciSampleType: () => TciSampleType,
|
|
39
40
|
TciStreamType: () => TciStreamType,
|
|
40
41
|
WebSocketTciTransport: () => WebSocketTciTransport,
|
|
@@ -46,6 +47,7 @@ __export(src_exports, {
|
|
|
46
47
|
commandKey: () => commandKey,
|
|
47
48
|
compareTciVersion: () => compareTciVersion,
|
|
48
49
|
createTciClient: () => createTciClient,
|
|
50
|
+
decodeInterleavedIq: () => decodeInterleavedIq,
|
|
49
51
|
defaultTciDialectRegistry: () => defaultTciDialectRegistry,
|
|
50
52
|
deinterleaveChannels: () => deinterleaveChannels,
|
|
51
53
|
escapeTciText: () => escapeTciText,
|
|
@@ -319,6 +321,15 @@ function payloadToFloat32(frameOrPayload, sampleType) {
|
|
|
319
321
|
}
|
|
320
322
|
return output;
|
|
321
323
|
}
|
|
324
|
+
function decodeInterleavedIq(frame) {
|
|
325
|
+
if (frame.streamType !== 0 /* IQ_STREAM */) {
|
|
326
|
+
throw new TciError("invalid-frame", "Expected a TCI IQ stream frame");
|
|
327
|
+
}
|
|
328
|
+
if (frame.channels !== 2 || frame.sampleCount % 2 !== 0) {
|
|
329
|
+
throw new TciError("invalid-frame", `TCI IQ frame must contain interleaved I/Q pairs, got ${frame.channels} channels`);
|
|
330
|
+
}
|
|
331
|
+
return payloadToFloat32(frame);
|
|
332
|
+
}
|
|
322
333
|
function samplesToPayload(samples, sampleType) {
|
|
323
334
|
const type = normalizeSampleType(sampleType);
|
|
324
335
|
const bytes = sampleTypeBytes(type);
|
|
@@ -673,6 +684,8 @@ var StandardTciDialect = class {
|
|
|
673
684
|
streamLengthSemantics;
|
|
674
685
|
supportsStreamChannels;
|
|
675
686
|
supportsTxAudioSource;
|
|
687
|
+
supportsIqStream;
|
|
688
|
+
iqSampleRates;
|
|
676
689
|
driveHasTrx;
|
|
677
690
|
detector;
|
|
678
691
|
resolver;
|
|
@@ -682,6 +695,8 @@ var StandardTciDialect = class {
|
|
|
682
695
|
this.streamLengthSemantics = options.streamLengthSemantics;
|
|
683
696
|
this.supportsStreamChannels = options.supportsStreamChannels;
|
|
684
697
|
this.supportsTxAudioSource = options.supportsTxAudioSource;
|
|
698
|
+
this.supportsIqStream = options.supportsIqStream;
|
|
699
|
+
this.iqSampleRates = [...options.iqSampleRates];
|
|
685
700
|
this.driveHasTrx = options.driveHasTrx;
|
|
686
701
|
this.detector = options.detect;
|
|
687
702
|
this.resolver = options.resolve;
|
|
@@ -729,6 +744,8 @@ var expertSdr14Dialect = new StandardTciDialect({
|
|
|
729
744
|
streamLengthSemantics: "per-channel",
|
|
730
745
|
supportsStreamChannels: false,
|
|
731
746
|
supportsTxAudioSource: false,
|
|
747
|
+
supportsIqStream: true,
|
|
748
|
+
iqSampleRates: [48e3, 96e3, 192e3, 384e3],
|
|
732
749
|
driveHasTrx: false,
|
|
733
750
|
detect: (context) => {
|
|
734
751
|
const parsed = version(context);
|
|
@@ -742,6 +759,8 @@ var expertSdrLegacyDialect = new StandardTciDialect({
|
|
|
742
759
|
streamLengthSemantics: "per-channel",
|
|
743
760
|
supportsStreamChannels: false,
|
|
744
761
|
supportsTxAudioSource: false,
|
|
762
|
+
supportsIqStream: true,
|
|
763
|
+
iqSampleRates: [48e3, 96e3, 192e3, 384e3],
|
|
745
764
|
driveHasTrx: true,
|
|
746
765
|
detect: (context) => {
|
|
747
766
|
const parsed = version(context);
|
|
@@ -755,6 +774,8 @@ var expertSdrModernDialect = new StandardTciDialect({
|
|
|
755
774
|
streamLengthSemantics: "scalar",
|
|
756
775
|
supportsStreamChannels: true,
|
|
757
776
|
supportsTxAudioSource: true,
|
|
777
|
+
supportsIqStream: true,
|
|
778
|
+
iqSampleRates: [48e3, 96e3, 192e3, 384e3],
|
|
758
779
|
driveHasTrx: true,
|
|
759
780
|
detect: (context) => {
|
|
760
781
|
const parsed = version(context);
|
|
@@ -773,6 +794,8 @@ var thetisDialect = new StandardTciDialect({
|
|
|
773
794
|
streamLengthSemantics: "scalar",
|
|
774
795
|
supportsStreamChannels: true,
|
|
775
796
|
supportsTxAudioSource: true,
|
|
797
|
+
supportsIqStream: true,
|
|
798
|
+
iqSampleRates: [48e3, 96e3, 192e3, 384e3],
|
|
776
799
|
driveHasTrx: true,
|
|
777
800
|
detect: (context) => {
|
|
778
801
|
const evidence = [];
|
|
@@ -799,6 +822,8 @@ var aetherSdrDialect = new StandardTciDialect({
|
|
|
799
822
|
streamLengthSemantics: "scalar",
|
|
800
823
|
supportsStreamChannels: true,
|
|
801
824
|
supportsTxAudioSource: true,
|
|
825
|
+
supportsIqStream: true,
|
|
826
|
+
iqSampleRates: [24e3, 48e3, 96e3, 192e3],
|
|
802
827
|
driveHasTrx: true,
|
|
803
828
|
detect: (context) => {
|
|
804
829
|
if (!/^aethersdr$/i.test(context.identity.device ?? "")) return { score: 0, evidence: [] };
|
|
@@ -818,6 +843,8 @@ var genericObservedDialect = new StandardTciDialect({
|
|
|
818
843
|
streamLengthSemantics: "auto",
|
|
819
844
|
supportsStreamChannels: true,
|
|
820
845
|
supportsTxAudioSource: true,
|
|
846
|
+
supportsIqStream: false,
|
|
847
|
+
iqSampleRates: [],
|
|
821
848
|
driveHasTrx: true,
|
|
822
849
|
detect: (context) => ({
|
|
823
850
|
score: context.commandNames.has("ready") ? 10 : 0,
|
|
@@ -833,6 +860,8 @@ var genericObservedDialect = new StandardTciDialect({
|
|
|
833
860
|
streamLengthSemantics: "auto",
|
|
834
861
|
supportsStreamChannels: context.commandNames.has("audio_stream_channels"),
|
|
835
862
|
supportsTxAudioSource: compareTciVersion(version(context) ?? [0], [2, 0]) >= 0,
|
|
863
|
+
supportsIqStream: context.commandNames.has("iq_samplerate"),
|
|
864
|
+
iqSampleRates: context.commandNames.has("iq_samplerate") ? [48e3] : [],
|
|
836
865
|
driveHasTrx,
|
|
837
866
|
detect: genericObservedDialect.detect.bind(genericObservedDialect)
|
|
838
867
|
});
|
|
@@ -1044,6 +1073,77 @@ function dataToBuffer(data) {
|
|
|
1044
1073
|
}
|
|
1045
1074
|
|
|
1046
1075
|
// src/client/TciClient.ts
|
|
1076
|
+
var TciIqStreamSession = class extends import_eventemitter32.EventEmitter {
|
|
1077
|
+
receiver;
|
|
1078
|
+
_appliedSampleRate;
|
|
1079
|
+
callbacks;
|
|
1080
|
+
closed = false;
|
|
1081
|
+
firstFrame;
|
|
1082
|
+
constructor(receiver, appliedSampleRate, callbacks) {
|
|
1083
|
+
super();
|
|
1084
|
+
this.receiver = receiver;
|
|
1085
|
+
this._appliedSampleRate = appliedSampleRate;
|
|
1086
|
+
this.callbacks = callbacks;
|
|
1087
|
+
}
|
|
1088
|
+
get appliedSampleRate() {
|
|
1089
|
+
return this._appliedSampleRate;
|
|
1090
|
+
}
|
|
1091
|
+
async setSampleRate(sampleRate) {
|
|
1092
|
+
if (this.closed) throw new TciError("cancelled", "TCI IQ stream is closed");
|
|
1093
|
+
const result = await this.callbacks.setSampleRate(sampleRate);
|
|
1094
|
+
this._appliedSampleRate = result.applied;
|
|
1095
|
+
return result;
|
|
1096
|
+
}
|
|
1097
|
+
async close() {
|
|
1098
|
+
if (this.closed) return;
|
|
1099
|
+
this.closed = true;
|
|
1100
|
+
this.rejectFirstFrame(new TciError("cancelled", "TCI IQ stream closed before its first frame"));
|
|
1101
|
+
try {
|
|
1102
|
+
await this.callbacks.close();
|
|
1103
|
+
} finally {
|
|
1104
|
+
this.emit("closed");
|
|
1105
|
+
this.removeAllListeners();
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
waitForFirstFrame(timeoutMs) {
|
|
1109
|
+
if (this.firstFrame) return this.firstFrame.promise;
|
|
1110
|
+
let resolvePromise;
|
|
1111
|
+
let rejectPromise;
|
|
1112
|
+
const promise = new Promise((resolve, reject) => {
|
|
1113
|
+
resolvePromise = resolve;
|
|
1114
|
+
rejectPromise = reject;
|
|
1115
|
+
});
|
|
1116
|
+
const timer = setTimeout(() => {
|
|
1117
|
+
this.firstFrame = void 0;
|
|
1118
|
+
rejectPromise(new TciError("command-timeout", `Timed out waiting for TCI IQ frame for receiver ${this.receiver}`));
|
|
1119
|
+
}, timeoutMs);
|
|
1120
|
+
this.firstFrame = { promise, resolve: resolvePromise, reject: rejectPromise, timer };
|
|
1121
|
+
return promise;
|
|
1122
|
+
}
|
|
1123
|
+
_acceptFrame(frame) {
|
|
1124
|
+
if (this.closed || frame.receiver !== this.receiver) return;
|
|
1125
|
+
this._appliedSampleRate = frame.sampleRate;
|
|
1126
|
+
const firstFrame = this.firstFrame;
|
|
1127
|
+
if (firstFrame) {
|
|
1128
|
+
clearTimeout(firstFrame.timer);
|
|
1129
|
+
this.firstFrame = void 0;
|
|
1130
|
+
firstFrame.resolve(frame);
|
|
1131
|
+
}
|
|
1132
|
+
this.emit("frame", frame);
|
|
1133
|
+
}
|
|
1134
|
+
_fail(error) {
|
|
1135
|
+
if (this.closed) return;
|
|
1136
|
+
this.rejectFirstFrame(error);
|
|
1137
|
+
this.emit("error", error);
|
|
1138
|
+
}
|
|
1139
|
+
rejectFirstFrame(error) {
|
|
1140
|
+
const firstFrame = this.firstFrame;
|
|
1141
|
+
if (!firstFrame) return;
|
|
1142
|
+
clearTimeout(firstFrame.timer);
|
|
1143
|
+
this.firstFrame = void 0;
|
|
1144
|
+
firstFrame.reject(error);
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1047
1147
|
var TciClient = class extends import_eventemitter32.EventEmitter {
|
|
1048
1148
|
options;
|
|
1049
1149
|
WebSocketImpl;
|
|
@@ -1058,6 +1158,7 @@ var TciClient = class extends import_eventemitter32.EventEmitter {
|
|
|
1058
1158
|
handshakeError;
|
|
1059
1159
|
initializationCommands = [];
|
|
1060
1160
|
handshakeWaiter;
|
|
1161
|
+
activeIqSession;
|
|
1061
1162
|
constructor(options) {
|
|
1062
1163
|
super();
|
|
1063
1164
|
this.options = {
|
|
@@ -1096,7 +1197,9 @@ var TciClient = class extends import_eventemitter32.EventEmitter {
|
|
|
1096
1197
|
split: {},
|
|
1097
1198
|
dialectWarnings: [],
|
|
1098
1199
|
rxSensors: {},
|
|
1099
|
-
txSensors: {}
|
|
1200
|
+
txSensors: {},
|
|
1201
|
+
iq: { activeReceivers: {} },
|
|
1202
|
+
dds: {}
|
|
1100
1203
|
};
|
|
1101
1204
|
this.stateReducers = this.createStateReducers();
|
|
1102
1205
|
}
|
|
@@ -1135,6 +1238,68 @@ var TciClient = class extends import_eventemitter32.EventEmitter {
|
|
|
1135
1238
|
getHandshakeResult() {
|
|
1136
1239
|
return this.handshakeResult ? cloneHandshake(this.handshakeResult) : void 0;
|
|
1137
1240
|
}
|
|
1241
|
+
getIqCapabilities() {
|
|
1242
|
+
const dialect = this.requireDialect();
|
|
1243
|
+
return {
|
|
1244
|
+
supported: dialect.supportsIqStream,
|
|
1245
|
+
currentSampleRate: this.state.iq.sampleRate,
|
|
1246
|
+
supportedSampleRates: [...dialect.iqSampleRates]
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
async setIqSampleRate(sampleRate, timeoutMs = this.options.commandTimeoutMs) {
|
|
1250
|
+
const capabilities = this.getIqCapabilities();
|
|
1251
|
+
const requested = Math.round(sampleRate);
|
|
1252
|
+
if (!capabilities.supported) throw new TciError("protocol-error", `TCI dialect ${this.requireDialect().id} does not support IQ streaming`);
|
|
1253
|
+
if (!Number.isFinite(requested) || requested <= 0) throw new TciError("protocol-error", `Invalid TCI IQ sample rate: ${sampleRate}`);
|
|
1254
|
+
if (capabilities.supportedSampleRates.length > 0 && !capabilities.supportedSampleRates.includes(requested)) {
|
|
1255
|
+
throw new TciError("protocol-error", `TCI IQ sample rate ${requested} is not supported by dialect ${this.requireDialect().id}`);
|
|
1256
|
+
}
|
|
1257
|
+
if (this.state.iq.sampleRate === requested) return writeResult(requested, requested, "state");
|
|
1258
|
+
const waiter = this.waitForCommand((command) => command.name === "iq_samplerate", timeoutMs, "IQ_SAMPLERATE readback");
|
|
1259
|
+
try {
|
|
1260
|
+
await this.sendCommand("IQ_SAMPLERATE", [requested], { waitForReply: false });
|
|
1261
|
+
const reply = await waiter.promise;
|
|
1262
|
+
const applied = parseNumber(reply.args[0]);
|
|
1263
|
+
if (applied === void 0 || applied <= 0) throw new TciError("protocol-error", `Invalid IQ_SAMPLERATE readback: ${reply.raw}`);
|
|
1264
|
+
return writeResult(requested, applied, "reply");
|
|
1265
|
+
} finally {
|
|
1266
|
+
waiter.cancel();
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
async openIqStream(options = {}) {
|
|
1270
|
+
if (this.activeIqSession) throw new TciError("protocol-error", "This TCI client already has an active IQ stream session");
|
|
1271
|
+
const capabilities = this.getIqCapabilities();
|
|
1272
|
+
if (!capabilities.supported) throw new TciError("protocol-error", `TCI dialect ${this.requireDialect().id} does not support IQ streaming`);
|
|
1273
|
+
const receiver = Math.floor(options.receiver ?? this.options.receiver);
|
|
1274
|
+
if (!Number.isInteger(receiver) || receiver < 0) throw new TciError("protocol-error", `Invalid TCI IQ receiver: ${receiver}`);
|
|
1275
|
+
const requestedRate = options.sampleRate ?? capabilities.currentSampleRate ?? capabilities.supportedSampleRates[0] ?? 48e3;
|
|
1276
|
+
const rateResult = await this.setIqSampleRate(requestedRate);
|
|
1277
|
+
let session;
|
|
1278
|
+
session = new TciIqStreamSession(receiver, rateResult.applied, {
|
|
1279
|
+
setSampleRate: (rate) => this.setIqSampleRate(rate),
|
|
1280
|
+
close: async () => {
|
|
1281
|
+
try {
|
|
1282
|
+
if (this.isConnected()) await this.sendCommand("IQ_STOP", [receiver], { waitForReply: false });
|
|
1283
|
+
} finally {
|
|
1284
|
+
this.state.iq.activeReceivers[String(receiver)] = false;
|
|
1285
|
+
if (this.activeIqSession === session) this.activeIqSession = void 0;
|
|
1286
|
+
this.emitState();
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
this.activeIqSession = session;
|
|
1291
|
+
const firstFrame = session.waitForFirstFrame(options.firstFrameTimeoutMs ?? 5e3);
|
|
1292
|
+
try {
|
|
1293
|
+
await this.sendCommand("IQ_START", [receiver], { waitForReply: false });
|
|
1294
|
+
this.state.iq.activeReceivers[String(receiver)] = true;
|
|
1295
|
+
this.emitState();
|
|
1296
|
+
await firstFrame;
|
|
1297
|
+
return session;
|
|
1298
|
+
} catch (error) {
|
|
1299
|
+
await session.close().catch(() => void 0);
|
|
1300
|
+
throw error;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1138
1303
|
async sendCommand(name, args = [], options = {}) {
|
|
1139
1304
|
const raw = formatTciCommand(name, args);
|
|
1140
1305
|
if (options.waitForReply === false) {
|
|
@@ -1624,6 +1789,22 @@ var TciClient = class extends import_eventemitter32.EventEmitter {
|
|
|
1624
1789
|
this.emit("tci:binary", frame);
|
|
1625
1790
|
this.emit("binary", frame);
|
|
1626
1791
|
switch (frame.streamType) {
|
|
1792
|
+
case 0 /* IQ_STREAM */: {
|
|
1793
|
+
if (frame.channels !== 2 || frame.sampleCount % 2 !== 0) {
|
|
1794
|
+
throw new TciError("invalid-frame", "TCI IQ stream frame must contain interleaved I/Q pairs");
|
|
1795
|
+
}
|
|
1796
|
+
this.state.iq.sampleRate = frame.sampleRate;
|
|
1797
|
+
const iqFrame = {
|
|
1798
|
+
frame,
|
|
1799
|
+
receiver: frame.receiver,
|
|
1800
|
+
sampleRate: frame.sampleRate,
|
|
1801
|
+
centerFrequency: this.state.dds[String(frame.receiver)],
|
|
1802
|
+
complexSampleCount: frame.frameCount
|
|
1803
|
+
};
|
|
1804
|
+
this.emit("iqFrame", iqFrame);
|
|
1805
|
+
this.activeIqSession?._acceptFrame(iqFrame);
|
|
1806
|
+
break;
|
|
1807
|
+
}
|
|
1627
1808
|
case 1 /* RX_AUDIO_STREAM */:
|
|
1628
1809
|
this.emit("rxAudioFrame", frame);
|
|
1629
1810
|
break;
|
|
@@ -1701,6 +1882,11 @@ var TciClient = class extends import_eventemitter32.EventEmitter {
|
|
|
1701
1882
|
this.state.modulations = args.map((mode) => mode.toLowerCase());
|
|
1702
1883
|
});
|
|
1703
1884
|
reducers.set("vfo", (args) => this.applyVfo(args));
|
|
1885
|
+
reducers.set("dds", (args) => {
|
|
1886
|
+
const receiver = parseNumber(args[0]);
|
|
1887
|
+
const frequency = parseNumber(args[1]);
|
|
1888
|
+
if (receiver !== void 0 && frequency !== void 0 && frequency >= 0) this.state.dds[String(receiver)] = frequency;
|
|
1889
|
+
});
|
|
1704
1890
|
reducers.set("modulation", (args) => this.applyModulation(args));
|
|
1705
1891
|
reducers.set("trx", (args) => this.applyTrx(args));
|
|
1706
1892
|
reducers.set("tune", (args) => this.applyBooleanByFirstArg(this.state.tune, args));
|
|
@@ -1717,6 +1903,15 @@ var TciClient = class extends import_eventemitter32.EventEmitter {
|
|
|
1717
1903
|
reducers.set("tx_stream_audio_buffering", (args) => this.updateAudioState({ txBufferingMs: parseNumber(args[0]) }));
|
|
1718
1904
|
reducers.set("audio_start", () => this.updateAudioState({ running: true }));
|
|
1719
1905
|
reducers.set("audio_stop", () => this.updateAudioState({ running: false }));
|
|
1906
|
+
reducers.set("iq_samplerate", (args) => {
|
|
1907
|
+
this.state.iq.sampleRate = parseNumber(args[0]);
|
|
1908
|
+
});
|
|
1909
|
+
reducers.set("iq_start", (args) => {
|
|
1910
|
+
this.state.iq.activeReceivers[String(args[0] ?? this.options.receiver)] = true;
|
|
1911
|
+
});
|
|
1912
|
+
reducers.set("iq_stop", (args) => {
|
|
1913
|
+
this.state.iq.activeReceivers[String(args[0] ?? this.options.receiver)] = false;
|
|
1914
|
+
});
|
|
1720
1915
|
return reducers;
|
|
1721
1916
|
}
|
|
1722
1917
|
applyVfo(args) {
|
|
@@ -1824,6 +2019,8 @@ var TciClient = class extends import_eventemitter32.EventEmitter {
|
|
|
1824
2019
|
this.state.ready = false;
|
|
1825
2020
|
this.queue.setConnected(false);
|
|
1826
2021
|
this.rejectHandshake(new TciError("disconnected", "TCI connection closed during handshake", reason));
|
|
2022
|
+
this.activeIqSession?._fail(new TciError("disconnected", "TCI connection closed while IQ stream was active", reason));
|
|
2023
|
+
this.activeIqSession = void 0;
|
|
1827
2024
|
if (wasConnected) {
|
|
1828
2025
|
this.emit("disconnected", reason);
|
|
1829
2026
|
this.emitState();
|
|
@@ -1904,7 +2101,9 @@ function cloneState(state) {
|
|
|
1904
2101
|
dialectWarnings: [...state.dialectWarnings],
|
|
1905
2102
|
rxSensors: cloneNested(state.rxSensors),
|
|
1906
2103
|
txSensors: cloneNested(state.txSensors),
|
|
1907
|
-
audio: state.audio ? { ...state.audio } : void 0
|
|
2104
|
+
audio: state.audio ? { ...state.audio } : void 0,
|
|
2105
|
+
iq: { ...state.iq, activeReceivers: { ...state.iq.activeReceivers } },
|
|
2106
|
+
dds: { ...state.dds }
|
|
1908
2107
|
};
|
|
1909
2108
|
}
|
|
1910
2109
|
function cloneHandshake(result) {
|
|
@@ -1929,6 +2128,7 @@ function cloneNested(value) {
|
|
|
1929
2128
|
TciCommandQueue,
|
|
1930
2129
|
TciDialectRegistry,
|
|
1931
2130
|
TciError,
|
|
2131
|
+
TciIqStreamSession,
|
|
1932
2132
|
TciSampleType,
|
|
1933
2133
|
TciStreamType,
|
|
1934
2134
|
WebSocketTciTransport,
|
|
@@ -1940,6 +2140,7 @@ function cloneNested(value) {
|
|
|
1940
2140
|
commandKey,
|
|
1941
2141
|
compareTciVersion,
|
|
1942
2142
|
createTciClient,
|
|
2143
|
+
decodeInterleavedIq,
|
|
1943
2144
|
defaultTciDialectRegistry,
|
|
1944
2145
|
deinterleaveChannels,
|
|
1945
2146
|
escapeTciText,
|