@tx5dr/contracts 2.0.0 → 2.5.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/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/schema/__tests__/assisted-queue.schema.test.js +1 -0
- package/dist/schema/__tests__/assisted-queue.schema.test.js.map +1 -1
- package/dist/schema/__tests__/meter.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/meter.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/meter.schema.test.js +27 -0
- package/dist/schema/__tests__/meter.schema.test.js.map +1 -0
- package/dist/schema/__tests__/operator-context-report.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/operator-context-report.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/operator-context-report.schema.test.js +70 -0
- package/dist/schema/__tests__/operator-context-report.schema.test.js.map +1 -0
- package/dist/schema/__tests__/plugin-market.schema.test.js +107 -3
- package/dist/schema/__tests__/plugin-market.schema.test.js.map +1 -1
- package/dist/schema/__tests__/qso.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/qso.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/qso.schema.test.js +70 -0
- package/dist/schema/__tests__/qso.schema.test.js.map +1 -0
- package/dist/schema/__tests__/radio-power-support.test.js +4 -1
- package/dist/schema/__tests__/radio-power-support.test.js.map +1 -1
- package/dist/schema/__tests__/radio.schema.test.js +52 -1
- package/dist/schema/__tests__/radio.schema.test.js.map +1 -1
- package/dist/schema/__tests__/remote-access.schema.test.js +1 -1
- package/dist/schema/__tests__/remote-access.schema.test.js.map +1 -1
- package/dist/schema/__tests__/spectrum.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/spectrum.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/spectrum.schema.test.js +107 -0
- package/dist/schema/__tests__/spectrum.schema.test.js.map +1 -0
- package/dist/schema/__tests__/strategy-transmission.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/strategy-transmission.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/strategy-transmission.schema.test.js +95 -0
- package/dist/schema/__tests__/strategy-transmission.schema.test.js.map +1 -0
- package/dist/schema/api-response.schema.d.ts +235 -0
- package/dist/schema/api-response.schema.d.ts.map +1 -1
- package/dist/schema/api-response.schema.js +9 -0
- package/dist/schema/api-response.schema.js.map +1 -1
- package/dist/schema/auth.schema.d.ts +2 -2
- package/dist/schema/cpu-profile.schema.d.ts +2 -2
- package/dist/schema/device-ui.schema.d.ts +10 -10
- package/dist/schema/image-radio.schema.d.ts +2597 -0
- package/dist/schema/image-radio.schema.d.ts.map +1 -0
- package/dist/schema/image-radio.schema.js +283 -0
- package/dist/schema/image-radio.schema.js.map +1 -0
- package/dist/schema/logbook.schema.d.ts +203 -15
- package/dist/schema/logbook.schema.d.ts.map +1 -1
- package/dist/schema/logbook.schema.js +1 -0
- package/dist/schema/logbook.schema.js.map +1 -1
- package/dist/schema/mode.schema.d.ts +2 -0
- package/dist/schema/mode.schema.d.ts.map +1 -1
- package/dist/schema/mode.schema.js +16 -0
- package/dist/schema/mode.schema.js.map +1 -1
- package/dist/schema/openwebrx.schema.d.ts +2 -2
- package/dist/schema/operator.schema.d.ts +2676 -0
- package/dist/schema/operator.schema.d.ts.map +1 -1
- package/dist/schema/operator.schema.js +4 -19
- package/dist/schema/operator.schema.js.map +1 -1
- package/dist/schema/plugin.schema.d.ts +1780 -475
- package/dist/schema/plugin.schema.d.ts.map +1 -1
- package/dist/schema/plugin.schema.js +108 -12
- package/dist/schema/plugin.schema.js.map +1 -1
- package/dist/schema/qso.schema.d.ts +239 -1
- package/dist/schema/qso.schema.d.ts.map +1 -1
- package/dist/schema/qso.schema.js +61 -0
- package/dist/schema/qso.schema.js.map +1 -1
- package/dist/schema/radio-capability.schema.d.ts +1 -1
- package/dist/schema/radio-capability.schema.d.ts.map +1 -1
- package/dist/schema/radio-capability.schema.js +1 -0
- package/dist/schema/radio-capability.schema.js.map +1 -1
- package/dist/schema/radio-power.schema.d.ts +15 -0
- package/dist/schema/radio-power.schema.d.ts.map +1 -1
- package/dist/schema/radio-profile.schema.d.ts +251 -0
- package/dist/schema/radio-profile.schema.d.ts.map +1 -1
- package/dist/schema/radio-profile.schema.js +7 -0
- package/dist/schema/radio-profile.schema.js.map +1 -1
- package/dist/schema/radio.schema.d.ts +174 -3
- package/dist/schema/radio.schema.d.ts.map +1 -1
- package/dist/schema/radio.schema.js +31 -2
- package/dist/schema/radio.schema.js.map +1 -1
- package/dist/schema/rigctld.schema.d.ts +6 -6
- package/dist/schema/slot-info.schema.d.ts +13 -0
- package/dist/schema/slot-info.schema.d.ts.map +1 -1
- package/dist/schema/slot-info.schema.js +2 -1
- package/dist/schema/slot-info.schema.js.map +1 -1
- package/dist/schema/spectrum-config.schema.d.ts +638 -0
- package/dist/schema/spectrum-config.schema.d.ts.map +1 -0
- package/dist/schema/spectrum-config.schema.js +129 -0
- package/dist/schema/spectrum-config.schema.js.map +1 -0
- package/dist/schema/spectrum.schema.d.ts +1313 -20
- package/dist/schema/spectrum.schema.d.ts.map +1 -1
- package/dist/schema/spectrum.schema.js +72 -0
- package/dist/schema/spectrum.schema.js.map +1 -1
- package/dist/schema/transmission.schema.d.ts +7 -0
- package/dist/schema/transmission.schema.d.ts.map +1 -1
- package/dist/schema/tx-audio-input.schema.d.ts +8 -0
- package/dist/schema/tx-audio-input.schema.d.ts.map +1 -0
- package/dist/schema/tx-audio-input.schema.js +19 -0
- package/dist/schema/tx-audio-input.schema.js.map +1 -0
- package/dist/schema/voice.schema.d.ts +1 -1
- package/dist/schema/voice.schema.d.ts.map +1 -1
- package/dist/schema/voice.schema.js +1 -1
- package/dist/schema/voice.schema.js.map +1 -1
- package/dist/schema/websocket.schema.d.ts +27480 -2525
- package/dist/schema/websocket.schema.d.ts.map +1 -1
- package/dist/schema/websocket.schema.js +298 -11
- package/dist/schema/websocket.schema.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +4 -0
- package/src/schema/__tests__/assisted-queue.schema.test.ts +1 -0
- package/src/schema/__tests__/meter.schema.test.ts +28 -0
- package/src/schema/__tests__/operator-context-report.schema.test.ts +77 -0
- package/src/schema/__tests__/plugin-market.schema.test.ts +116 -2
- package/src/schema/__tests__/qso.schema.test.ts +91 -0
- package/src/schema/__tests__/radio-power-support.test.ts +4 -1
- package/src/schema/__tests__/radio.schema.test.ts +60 -1
- package/src/schema/__tests__/remote-access.schema.test.ts +1 -1
- package/src/schema/__tests__/spectrum.schema.test.ts +119 -0
- package/src/schema/__tests__/strategy-transmission.schema.test.ts +106 -0
- package/src/schema/api-response.schema.ts +11 -0
- package/src/schema/image-radio.schema.ts +342 -0
- package/src/schema/logbook.schema.ts +1 -0
- package/src/schema/mode.schema.ts +16 -0
- package/src/schema/operator.schema.ts +4 -19
- package/src/schema/plugin.schema.ts +119 -13
- package/src/schema/qso.schema.ts +71 -0
- package/src/schema/radio-capability.schema.ts +1 -0
- package/src/schema/radio-profile.schema.ts +7 -0
- package/src/schema/radio.schema.ts +35 -2
- package/src/schema/slot-info.schema.ts +2 -1
- package/src/schema/spectrum-config.schema.ts +156 -0
- package/src/schema/spectrum.schema.ts +89 -0
- package/src/schema/tx-audio-input.schema.ts +22 -0
- package/src/schema/voice.schema.ts +1 -1
- package/src/schema/websocket.schema.ts +383 -12
- package/test/image-radio-schema.test.ts +107 -0
- package/test/plugin-slot-schema.test.ts +31 -0
- package/CLAUDE.md +0 -41
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { DigitalModeRadioModePreferenceSchema, HamlibConfigSchema, PresetFrequencySchema, RadioInfoSchema } from '../radio.schema.js';
|
|
2
|
+
import { DdsFrequencyRequestSchema, DigitalModeRadioModePreferenceSchema, HamlibConfigSchema, PresetFrequencySchema, RadioInfoSchema } from '../radio.schema.js';
|
|
3
3
|
|
|
4
4
|
describe('HamlibConfigSchema digital mode radio mode preference', () => {
|
|
5
5
|
it('accepts legacy configs without a digital mode radio mode preference', () => {
|
|
@@ -38,6 +38,12 @@ describe('HamlibConfigSchema TCI connection', () => {
|
|
|
38
38
|
});
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
+
it('accepts only credential-free WebSocket URLs for TCI', () => {
|
|
42
|
+
expect(() => HamlibConfigSchema.parse({ type: 'tci', tci: { url: 'wss://radio.example/tci' } })).not.toThrow();
|
|
43
|
+
expect(() => HamlibConfigSchema.parse({ type: 'tci', tci: { url: 'https://radio.example/tci' } })).toThrow();
|
|
44
|
+
expect(() => HamlibConfigSchema.parse({ type: 'tci', tci: { url: 'ws://user:secret@radio.example/tci' } })).toThrow();
|
|
45
|
+
});
|
|
46
|
+
|
|
41
47
|
it('accepts TCI as a reported radio connection type', () => {
|
|
42
48
|
const parsed = RadioInfoSchema.parse({
|
|
43
49
|
manufacturer: 'Expert Electronics',
|
|
@@ -49,6 +55,17 @@ describe('HamlibConfigSchema TCI connection', () => {
|
|
|
49
55
|
});
|
|
50
56
|
});
|
|
51
57
|
|
|
58
|
+
describe('DdsFrequencyRequestSchema', () => {
|
|
59
|
+
it('accepts non-negative center frequency and receiver values', () => {
|
|
60
|
+
expect(DdsFrequencyRequestSchema.parse({ frequency: 14_075_000, receiver: 0 })).toEqual({ frequency: 14_075_000, receiver: 0 });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('rejects invalid center frequency and receiver values', () => {
|
|
64
|
+
expect(() => DdsFrequencyRequestSchema.parse({ frequency: -1 })).toThrow();
|
|
65
|
+
expect(() => DdsFrequencyRequestSchema.parse({ frequency: 14_075_000, receiver: 0.5 })).toThrow();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
52
69
|
describe('PresetFrequencySchema repeater DUP fields', () => {
|
|
53
70
|
it('accepts existing presets without repeater DUP fields', () => {
|
|
54
71
|
const parsed = PresetFrequencySchema.parse({
|
|
@@ -119,6 +136,48 @@ describe('PresetFrequencySchema repeater DUP fields', () => {
|
|
|
119
136
|
});
|
|
120
137
|
});
|
|
121
138
|
|
|
139
|
+
describe('PresetFrequencySchema dial frequency precision', () => {
|
|
140
|
+
it('accepts integer-Hz presets represented by up to six MHz decimal places', () => {
|
|
141
|
+
const parsed = PresetFrequencySchema.parse({
|
|
142
|
+
band: '4MHz',
|
|
143
|
+
mode: 'FAX',
|
|
144
|
+
radioMode: 'USB',
|
|
145
|
+
frequency: 4_233_100,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(parsed.frequency).toBe(4_233_100);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('accepts marine FAX assigned-frequency and emission metadata', () => {
|
|
152
|
+
const parsed = PresetFrequencySchema.parse({
|
|
153
|
+
band: '4MHz',
|
|
154
|
+
mode: 'FAX',
|
|
155
|
+
radioMode: 'USB',
|
|
156
|
+
frequency: 4_197_850,
|
|
157
|
+
assignedFrequency: 4_199_750,
|
|
158
|
+
audioCenterHz: 1_900,
|
|
159
|
+
faxEmission: 'F3C',
|
|
160
|
+
imagePurpose: 'weatherfax',
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(parsed.assignedFrequency).toBe(4_199_750);
|
|
164
|
+
expect(parsed.faxEmission).toBe('F3C');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('rejects fractional-Hz and out-of-range preset frequencies', () => {
|
|
168
|
+
expect(() => PresetFrequencySchema.parse({
|
|
169
|
+
band: '20m',
|
|
170
|
+
mode: 'SSTV',
|
|
171
|
+
frequency: 14_230_000.5,
|
|
172
|
+
})).toThrow();
|
|
173
|
+
expect(() => PresetFrequencySchema.parse({
|
|
174
|
+
band: 'custom',
|
|
175
|
+
mode: 'VOICE',
|
|
176
|
+
frequency: 1_000_000_001,
|
|
177
|
+
})).toThrow();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
122
181
|
describe('PresetFrequencySchema tone squelch fields', () => {
|
|
123
182
|
it('accepts CTCSS tone presets with positive 0.1 Hz values', () => {
|
|
124
183
|
const parsed = PresetFrequencySchema.parse({
|
|
@@ -12,7 +12,7 @@ describe('remote access origins', () => {
|
|
|
12
12
|
expect(normalizeRemoteAccessOrigin('https://user:pass@radio.example.com')).toBeNull();
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
it('
|
|
15
|
+
it('identifies origins with transport security without enforcing public-access policy', () => {
|
|
16
16
|
expect(isSecureRemoteAccessOrigin('https://radio.example.com')).toBe(true);
|
|
17
17
|
expect(isSecureRemoteAccessOrigin('http://radio.example.com')).toBe(false);
|
|
18
18
|
expect(isSecureRemoteAccessOrigin('http://203.0.113.10:8076')).toBe(false);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { SpectrumFrameSchema, SpectrumLevelDescriptorSchema, SpectrumSessionInteractionStateSchema } from '../spectrum.schema.js';
|
|
3
|
+
import {
|
|
4
|
+
SpectrumSettingsResponseSchema,
|
|
5
|
+
SpectrumSettingsUpdateRequestSchema,
|
|
6
|
+
TciSpectrumSettingsSchema,
|
|
7
|
+
getSpectrumPresetDefinition,
|
|
8
|
+
} from '../spectrum-config.schema.js';
|
|
9
|
+
|
|
10
|
+
describe('spectrum level descriptor schema', () => {
|
|
11
|
+
it('accepts dBFS, calibrated dB and raw Level descriptors', () => {
|
|
12
|
+
for (const level of [
|
|
13
|
+
{ domain: 'dbfs', unit: 'dBFS', reference: 'full-scale', calibrated: true, min: -120, max: 0 },
|
|
14
|
+
{ domain: 'calibrated-db', unit: 'dB', reference: 'device', calibrated: true, min: -130, max: 10 },
|
|
15
|
+
{ domain: 'raw', unit: 'Level', reference: 'none', calibrated: false, min: 0, max: 255 },
|
|
16
|
+
]) {
|
|
17
|
+
expect(SpectrumLevelDescriptorSchema.safeParse(level).success).toBe(true);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('rejects invalid or non-increasing level ranges', () => {
|
|
22
|
+
expect(SpectrumLevelDescriptorSchema.safeParse({
|
|
23
|
+
domain: 'raw',
|
|
24
|
+
unit: 'dBFS',
|
|
25
|
+
reference: 'none',
|
|
26
|
+
calibrated: false,
|
|
27
|
+
min: 255,
|
|
28
|
+
max: 0,
|
|
29
|
+
}).success).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('keeps level optional for old spectrum frames', () => {
|
|
33
|
+
const result = SpectrumFrameSchema.safeParse({
|
|
34
|
+
timestamp: 1,
|
|
35
|
+
kind: 'radio-sdr',
|
|
36
|
+
frequencyRange: { min: 7_000_000, max: 7_100_000 },
|
|
37
|
+
binaryData: {
|
|
38
|
+
data: 'AA==',
|
|
39
|
+
format: { type: 'int16', length: 1 },
|
|
40
|
+
},
|
|
41
|
+
meta: {
|
|
42
|
+
sourceBinCount: 1,
|
|
43
|
+
displayBinCount: 1,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
expect(result.success).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('defaults legacy interaction state to radio-center and accepts split TX targets', () => {
|
|
50
|
+
const interaction = SpectrumSessionInteractionStateSchema.parse({
|
|
51
|
+
showTxMarkers: false,
|
|
52
|
+
showRxMarkers: false,
|
|
53
|
+
canDragTx: false,
|
|
54
|
+
canRightClickSetFrequency: false,
|
|
55
|
+
canDoubleClickSetFrequency: false,
|
|
56
|
+
canDragFrequency: false,
|
|
57
|
+
frequencyGestureTarget: null,
|
|
58
|
+
frequencyStepHz: null,
|
|
59
|
+
presetMarkers: [],
|
|
60
|
+
frequencyOverlays: [{
|
|
61
|
+
id: 'split',
|
|
62
|
+
label: 'TX',
|
|
63
|
+
lineFrequency: 14_052_500,
|
|
64
|
+
rangeStartFrequency: 14_052_500,
|
|
65
|
+
rangeEndFrequency: 14_052_500,
|
|
66
|
+
variant: 'tx',
|
|
67
|
+
draggable: true,
|
|
68
|
+
frequencyTarget: 'split-frequency',
|
|
69
|
+
}],
|
|
70
|
+
canDragVoiceOverlay: false,
|
|
71
|
+
showVoiceOverlay: false,
|
|
72
|
+
canLocalViewportZoom: false,
|
|
73
|
+
canLocalViewportPan: false,
|
|
74
|
+
supportsManualRange: true,
|
|
75
|
+
supportsAutoRange: false,
|
|
76
|
+
defaultRangeMode: 'manual',
|
|
77
|
+
});
|
|
78
|
+
expect(interaction.viewMode).toBe('radio-center');
|
|
79
|
+
expect(interaction.viewport.enabled).toBe(false);
|
|
80
|
+
expect(interaction.frequencyOverlays[0]?.frequencyTarget).toBe('split-frequency');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('spectrum analysis settings schemas', () => {
|
|
85
|
+
it('validates preset updates and derived render settings', () => {
|
|
86
|
+
expect(SpectrumSettingsUpdateRequestSchema.parse({ preset: 'fine' })).toEqual({ preset: 'fine' });
|
|
87
|
+
const balanced = getSpectrumPresetDefinition('balanced');
|
|
88
|
+
expect(SpectrumSettingsResponseSchema.parse({
|
|
89
|
+
success: true,
|
|
90
|
+
currentSettings: { ...balanced, revision: 3 },
|
|
91
|
+
presets: [getSpectrumPresetDefinition('responsive'), balanced, getSpectrumPresetDefinition('fine')],
|
|
92
|
+
}).currentSettings.displayBinCount).toBe(4097);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('rejects arbitrary analysis parameters', () => {
|
|
96
|
+
expect(() => SpectrumSettingsUpdateRequestSchema.parse({ preset: 'custom', fftSize: 4096 })).toThrow();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('accepts a validated custom analysis draft', () => {
|
|
100
|
+
expect(SpectrumSettingsUpdateRequestSchema.parse({
|
|
101
|
+
preset: 'custom',
|
|
102
|
+
settings: {
|
|
103
|
+
analysisIntervalMs: 275,
|
|
104
|
+
fftSize: 4096,
|
|
105
|
+
targetSampleRate: 8000,
|
|
106
|
+
windowFunction: 'hann',
|
|
107
|
+
haloReduce: true,
|
|
108
|
+
},
|
|
109
|
+
}).preset).toBe('custom');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('accepts high-performance TCI analysis settings within the supported bounds', () => {
|
|
113
|
+
expect(TciSpectrumSettingsSchema.parse({
|
|
114
|
+
fftSize: 65536,
|
|
115
|
+
displayBinCount: 16384,
|
|
116
|
+
analysisIntervalMs: 20,
|
|
117
|
+
})).toEqual({ fftSize: 65536, displayBinCount: 16384, analysisIntervalMs: 20 });
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
OperatorStatusSchema,
|
|
4
|
+
StrategyStreamSnapshotSchema,
|
|
5
|
+
StrategyTransmissionSchema,
|
|
6
|
+
WSMessageSchema,
|
|
7
|
+
} from '../../index.js';
|
|
8
|
+
|
|
9
|
+
describe('strategy transmission contracts', () => {
|
|
10
|
+
it('carries the current multi-stream transmission set in operator status', () => {
|
|
11
|
+
const currentTransmissions = [
|
|
12
|
+
{ streamId: 'stream-1', text: 'JA1AAA BG5DRB OL32', audioFrequencyHz: 1200 },
|
|
13
|
+
{ streamId: 'stream-2', text: 'JA2BBB BG5DRB R PM95', audioFrequencyHz: 1320 },
|
|
14
|
+
{ streamId: 'stream-3', text: 'JA3CCC BG5DRB RR73', audioFrequencyHz: 1440 },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
expect(StrategyTransmissionSchema.parse(currentTransmissions[0])).toEqual(currentTransmissions[0]);
|
|
18
|
+
expect(OperatorStatusSchema.parse({
|
|
19
|
+
id: 'operator-1',
|
|
20
|
+
isActive: true,
|
|
21
|
+
isTransmitting: true,
|
|
22
|
+
hasTransmitIntent: true,
|
|
23
|
+
currentTransmissions,
|
|
24
|
+
currentSlot: 'parallel',
|
|
25
|
+
context: {
|
|
26
|
+
myCall: 'BG5DRB',
|
|
27
|
+
myGrid: 'OL32',
|
|
28
|
+
targetCalls: ['JA1AAA', 'JA2BBB', 'JA3CCC'],
|
|
29
|
+
targetCall: 'JA1AAA',
|
|
30
|
+
},
|
|
31
|
+
strategy: {
|
|
32
|
+
name: 'ww-digi',
|
|
33
|
+
state: 'parallel',
|
|
34
|
+
availableSlots: ['TX6'],
|
|
35
|
+
},
|
|
36
|
+
})).toMatchObject({
|
|
37
|
+
currentTransmissions,
|
|
38
|
+
context: { targetCalls: ['JA1AAA', 'JA2BBB', 'JA3CCC'] },
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('rejects empty text and invalid audio frequencies', () => {
|
|
43
|
+
expect(StrategyTransmissionSchema.safeParse({
|
|
44
|
+
streamId: 'stream-1',
|
|
45
|
+
text: '',
|
|
46
|
+
audioFrequencyHz: 1200,
|
|
47
|
+
}).success).toBe(false);
|
|
48
|
+
expect(StrategyTransmissionSchema.safeParse({
|
|
49
|
+
streamId: 'stream-1',
|
|
50
|
+
text: 'CQ WW BG5DRB OL32',
|
|
51
|
+
audioFrequencyHz: 5001,
|
|
52
|
+
}).success).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('accepts strategy-defined stream states and a lifecycle-scoped switch command', () => {
|
|
56
|
+
expect(StrategyStreamSnapshotSchema.parse({
|
|
57
|
+
streamId: 'stream-7',
|
|
58
|
+
currentState: 'awaiting-confirmation',
|
|
59
|
+
audioFrequencyHz: 1320,
|
|
60
|
+
qsoLifecycleEpoch: 4,
|
|
61
|
+
stateOptions: [{
|
|
62
|
+
id: 'send-final',
|
|
63
|
+
label: 'stateSendFinal',
|
|
64
|
+
transmitText: 'JA1AAA BG5DRB RR73',
|
|
65
|
+
}],
|
|
66
|
+
}).stateOptions?.[0]?.id).toBe('send-final');
|
|
67
|
+
|
|
68
|
+
expect(WSMessageSchema.parse({
|
|
69
|
+
type: 'setOperatorStreamState',
|
|
70
|
+
timestamp: new Date().toISOString(),
|
|
71
|
+
data: {
|
|
72
|
+
operatorId: 'operator-1',
|
|
73
|
+
streamId: 'stream-7',
|
|
74
|
+
stateId: 'send-final',
|
|
75
|
+
expectedLifecycleEpoch: 4,
|
|
76
|
+
},
|
|
77
|
+
}).type).toBe('setOperatorStreamState');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('accepts plugin-declared actions and lifecycle-scoped action invocations', () => {
|
|
81
|
+
const stream = StrategyStreamSnapshotSchema.parse({
|
|
82
|
+
streamId: 'stream-1',
|
|
83
|
+
currentState: 'recovering',
|
|
84
|
+
audioFrequencyHz: 1500,
|
|
85
|
+
qsoLifecycleEpoch: 7,
|
|
86
|
+
completion: { state: 'committed', recordId: 'qso-1' },
|
|
87
|
+
attentions: [{ id: 'needs-action', tone: 'warning', title: 'attentionTitle', actionIds: ['reply'] }],
|
|
88
|
+
actions: [{
|
|
89
|
+
id: 'reply', label: 'replyLabel', icon: 'paper-plane', tone: 'primary', presentation: 'primary',
|
|
90
|
+
input: { kind: 'audio-frequency', value: 1500, min: 100, max: 5000, step: 10, spectrumPick: true },
|
|
91
|
+
}],
|
|
92
|
+
});
|
|
93
|
+
expect(stream.actions?.[0]?.id).toBe('reply');
|
|
94
|
+
|
|
95
|
+
expect(WSMessageSchema.parse({
|
|
96
|
+
type: 'invokeOperatorStrategyAction',
|
|
97
|
+
timestamp: new Date().toISOString(),
|
|
98
|
+
data: {
|
|
99
|
+
operatorId: 'operator-1',
|
|
100
|
+
target: { kind: 'stream', streamId: 'stream-1', lifecycleEpoch: 7 },
|
|
101
|
+
actionId: 'reply',
|
|
102
|
+
payload: { value: 1600 },
|
|
103
|
+
},
|
|
104
|
+
}).type).toBe('invokeOperatorStrategyAction');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -132,6 +132,7 @@ export const LastFrequencyResponseSchema = z.object({
|
|
|
132
132
|
band: z.string(),
|
|
133
133
|
description: z.string().optional(),
|
|
134
134
|
}).nullable().optional(),
|
|
135
|
+
lastImageFrequency: PresetFrequencySchema.nullable().optional(),
|
|
135
136
|
});
|
|
136
137
|
|
|
137
138
|
/**
|
|
@@ -150,6 +151,15 @@ export const SetFrequencyResponseSchema = z.object({
|
|
|
150
151
|
ctcssToneTenthsHz: z.number().optional(),
|
|
151
152
|
dcsCode: z.number().optional(),
|
|
152
153
|
radioConnected: z.boolean().optional(),
|
|
154
|
+
confirmation: z.enum(['confirmed', 'pending', 'mismatch', 'offline']).optional(),
|
|
155
|
+
observedFrequency: z.number().optional(),
|
|
156
|
+
operationId: z.string().optional(),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
export const SetDdsFrequencyResponseSchema = z.object({
|
|
160
|
+
success: z.literal(true),
|
|
161
|
+
frequency: z.number(),
|
|
162
|
+
receiver: z.number().int().nonnegative(),
|
|
153
163
|
});
|
|
154
164
|
|
|
155
165
|
// ========== 时隙包相关响应 ==========
|
|
@@ -203,6 +213,7 @@ export type DisconnectRadioResponse = z.infer<typeof DisconnectRadioResponseSche
|
|
|
203
213
|
|
|
204
214
|
export type LastFrequencyResponse = z.infer<typeof LastFrequencyResponseSchema>;
|
|
205
215
|
export type SetFrequencyResponse = z.infer<typeof SetFrequencyResponseSchema>;
|
|
216
|
+
export type SetDdsFrequencyResponse = z.infer<typeof SetDdsFrequencyResponseSchema>;
|
|
206
217
|
|
|
207
218
|
export type SlotPacksResponse = z.infer<typeof SlotPacksResponseSchema>;
|
|
208
219
|
export type SlotPackResponse = z.infer<typeof SlotPackResponseSchema>;
|