@tx5dr/contracts 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/CLAUDE.md +41 -0
  2. package/dist/index.d.ts +55 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +86 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/schema/__tests__/plugin-runtime-log.schema.test.d.ts +2 -0
  7. package/dist/schema/__tests__/plugin-runtime-log.schema.test.d.ts.map +1 -0
  8. package/dist/schema/__tests__/plugin-runtime-log.schema.test.js +78 -0
  9. package/dist/schema/__tests__/plugin-runtime-log.schema.test.js.map +1 -0
  10. package/dist/schema/ability.d.ts +85 -0
  11. package/dist/schema/ability.d.ts.map +1 -0
  12. package/dist/schema/ability.js +119 -0
  13. package/dist/schema/ability.js.map +1 -0
  14. package/dist/schema/api-response.schema.d.ts +1017 -0
  15. package/dist/schema/api-response.schema.d.ts.map +1 -0
  16. package/dist/schema/api-response.schema.js +137 -0
  17. package/dist/schema/api-response.schema.js.map +1 -0
  18. package/dist/schema/audio-sidecar.schema.d.ts +106 -0
  19. package/dist/schema/audio-sidecar.schema.d.ts.map +1 -0
  20. package/dist/schema/audio-sidecar.schema.js +47 -0
  21. package/dist/schema/audio-sidecar.schema.js.map +1 -0
  22. package/dist/schema/audio.schema.d.ts +227 -0
  23. package/dist/schema/audio.schema.d.ts.map +1 -0
  24. package/dist/schema/audio.schema.js +56 -0
  25. package/dist/schema/audio.schema.js.map +1 -0
  26. package/dist/schema/auth.schema.d.ts +738 -0
  27. package/dist/schema/auth.schema.d.ts.map +1 -0
  28. package/dist/schema/auth.schema.js +161 -0
  29. package/dist/schema/auth.schema.js.map +1 -0
  30. package/dist/schema/cycle.schema.d.ts +33 -0
  31. package/dist/schema/cycle.schema.d.ts.map +1 -0
  32. package/dist/schema/cycle.schema.js +23 -0
  33. package/dist/schema/cycle.schema.js.map +1 -0
  34. package/dist/schema/desktop-https.schema.d.ts +146 -0
  35. package/dist/schema/desktop-https.schema.d.ts.map +1 -0
  36. package/dist/schema/desktop-https.schema.js +31 -0
  37. package/dist/schema/desktop-https.schema.js.map +1 -0
  38. package/dist/schema/ft8.schema.d.ts +1107 -0
  39. package/dist/schema/ft8.schema.d.ts.map +1 -0
  40. package/dist/schema/ft8.schema.js +268 -0
  41. package/dist/schema/ft8.schema.js.map +1 -0
  42. package/dist/schema/hello.schema.d.ts +10 -0
  43. package/dist/schema/hello.schema.d.ts.map +1 -0
  44. package/dist/schema/hello.schema.js +5 -0
  45. package/dist/schema/hello.schema.js.map +1 -0
  46. package/dist/schema/logbook.schema.d.ts +1472 -0
  47. package/dist/schema/logbook.schema.d.ts.map +1 -0
  48. package/dist/schema/logbook.schema.js +256 -0
  49. package/dist/schema/logbook.schema.js.map +1 -0
  50. package/dist/schema/mode.schema.d.ts +130 -0
  51. package/dist/schema/mode.schema.d.ts.map +1 -0
  52. package/dist/schema/mode.schema.js +141 -0
  53. package/dist/schema/mode.schema.js.map +1 -0
  54. package/dist/schema/openwebrx.schema.d.ts +352 -0
  55. package/dist/schema/openwebrx.schema.d.ts.map +1 -0
  56. package/dist/schema/openwebrx.schema.js +139 -0
  57. package/dist/schema/openwebrx.schema.js.map +1 -0
  58. package/dist/schema/operator.schema.d.ts +883 -0
  59. package/dist/schema/operator.schema.d.ts.map +1 -0
  60. package/dist/schema/operator.schema.js +97 -0
  61. package/dist/schema/operator.schema.js.map +1 -0
  62. package/dist/schema/plugin.schema.d.ts +1579 -0
  63. package/dist/schema/plugin.schema.d.ts.map +1 -0
  64. package/dist/schema/plugin.schema.js +336 -0
  65. package/dist/schema/plugin.schema.js.map +1 -0
  66. package/dist/schema/process-monitor.schema.d.ts +282 -0
  67. package/dist/schema/process-monitor.schema.d.ts.map +1 -0
  68. package/dist/schema/process-monitor.schema.js +31 -0
  69. package/dist/schema/process-monitor.schema.js.map +1 -0
  70. package/dist/schema/pskreporter.schema.d.ts +185 -0
  71. package/dist/schema/pskreporter.schema.d.ts.map +1 -0
  72. package/dist/schema/pskreporter.schema.js +80 -0
  73. package/dist/schema/pskreporter.schema.js.map +1 -0
  74. package/dist/schema/qso.schema.d.ts +419 -0
  75. package/dist/schema/qso.schema.d.ts.map +1 -0
  76. package/dist/schema/qso.schema.js +137 -0
  77. package/dist/schema/qso.schema.js.map +1 -0
  78. package/dist/schema/radio-capability.schema.d.ts +625 -0
  79. package/dist/schema/radio-capability.schema.d.ts.map +1 -0
  80. package/dist/schema/radio-capability.schema.js +191 -0
  81. package/dist/schema/radio-capability.schema.js.map +1 -0
  82. package/dist/schema/radio-power-support.d.ts +45 -0
  83. package/dist/schema/radio-power-support.d.ts.map +1 -0
  84. package/dist/schema/radio-power-support.js +64 -0
  85. package/dist/schema/radio-power-support.js.map +1 -0
  86. package/dist/schema/radio-power.schema.d.ts +319 -0
  87. package/dist/schema/radio-power.schema.d.ts.map +1 -0
  88. package/dist/schema/radio-power.schema.js +87 -0
  89. package/dist/schema/radio-power.schema.js.map +1 -0
  90. package/dist/schema/radio-profile.schema.d.ts +2694 -0
  91. package/dist/schema/radio-profile.schema.d.ts.map +1 -0
  92. package/dist/schema/radio-profile.schema.js +71 -0
  93. package/dist/schema/radio-profile.schema.js.map +1 -0
  94. package/dist/schema/radio.schema.d.ts +1438 -0
  95. package/dist/schema/radio.schema.d.ts.map +1 -0
  96. package/dist/schema/radio.schema.js +314 -0
  97. package/dist/schema/radio.schema.js.map +1 -0
  98. package/dist/schema/realtime.schema.d.ts +1299 -0
  99. package/dist/schema/realtime.schema.d.ts.map +1 -0
  100. package/dist/schema/realtime.schema.js +184 -0
  101. package/dist/schema/realtime.schema.js.map +1 -0
  102. package/dist/schema/rigctld.schema.d.ts +151 -0
  103. package/dist/schema/rigctld.schema.d.ts.map +1 -0
  104. package/dist/schema/rigctld.schema.js +42 -0
  105. package/dist/schema/rigctld.schema.js.map +1 -0
  106. package/dist/schema/server-message-keys.d.ts +21 -0
  107. package/dist/schema/server-message-keys.d.ts.map +1 -0
  108. package/dist/schema/server-message-keys.js +22 -0
  109. package/dist/schema/server-message-keys.js.map +1 -0
  110. package/dist/schema/slot-info.schema.d.ts +696 -0
  111. package/dist/schema/slot-info.schema.d.ts.map +1 -0
  112. package/dist/schema/slot-info.schema.js +144 -0
  113. package/dist/schema/slot-info.schema.js.map +1 -0
  114. package/dist/schema/spectrum.schema.d.ts +716 -0
  115. package/dist/schema/spectrum.schema.d.ts.map +1 -0
  116. package/dist/schema/spectrum.schema.js +128 -0
  117. package/dist/schema/spectrum.schema.js.map +1 -0
  118. package/dist/schema/station-info.schema.d.ts +202 -0
  119. package/dist/schema/station-info.schema.d.ts.map +1 -0
  120. package/dist/schema/station-info.schema.js +32 -0
  121. package/dist/schema/station-info.schema.js.map +1 -0
  122. package/dist/schema/system.schema.d.ts +113 -0
  123. package/dist/schema/system.schema.d.ts.map +1 -0
  124. package/dist/schema/system.schema.js +95 -0
  125. package/dist/schema/system.schema.js.map +1 -0
  126. package/dist/schema/transmission.schema.d.ts +413 -0
  127. package/dist/schema/transmission.schema.d.ts.map +1 -0
  128. package/dist/schema/transmission.schema.js +78 -0
  129. package/dist/schema/transmission.schema.js.map +1 -0
  130. package/dist/schema/voice.schema.d.ts +38 -0
  131. package/dist/schema/voice.schema.d.ts.map +1 -0
  132. package/dist/schema/voice.schema.js +24 -0
  133. package/dist/schema/voice.schema.js.map +1 -0
  134. package/dist/schema/websocket.schema.d.ts +16916 -0
  135. package/dist/schema/websocket.schema.d.ts.map +1 -0
  136. package/dist/schema/websocket.schema.js +1050 -0
  137. package/dist/schema/websocket.schema.js.map +1 -0
  138. package/dist/utils/grid.d.ts +13 -0
  139. package/dist/utils/grid.d.ts.map +1 -0
  140. package/dist/utils/grid.js +71 -0
  141. package/dist/utils/grid.js.map +1 -0
  142. package/eslint.config.js +15 -0
  143. package/package.json +28 -0
  144. package/src/index.ts +152 -0
  145. package/src/schema/__tests__/plugin-runtime-log.schema.test.ts +90 -0
  146. package/src/schema/ability.ts +205 -0
  147. package/src/schema/api-response.schema.ts +187 -0
  148. package/src/schema/audio-sidecar.schema.ts +53 -0
  149. package/src/schema/audio.schema.ts +73 -0
  150. package/src/schema/auth.schema.ts +224 -0
  151. package/src/schema/cycle.schema.ts +25 -0
  152. package/src/schema/desktop-https.schema.ts +43 -0
  153. package/src/schema/ft8.schema.ts +362 -0
  154. package/src/schema/hello.schema.ts +7 -0
  155. package/src/schema/logbook.schema.ts +310 -0
  156. package/src/schema/mode.schema.ts +158 -0
  157. package/src/schema/openwebrx.schema.ts +160 -0
  158. package/src/schema/operator.schema.ts +112 -0
  159. package/src/schema/plugin.schema.ts +503 -0
  160. package/src/schema/process-monitor.schema.ts +41 -0
  161. package/src/schema/pskreporter.schema.ts +102 -0
  162. package/src/schema/qso.schema.ts +173 -0
  163. package/src/schema/radio-capability.schema.ts +244 -0
  164. package/src/schema/radio-power-support.ts +107 -0
  165. package/src/schema/radio-power.schema.ts +101 -0
  166. package/src/schema/radio-profile.schema.ts +91 -0
  167. package/src/schema/radio.schema.ts +385 -0
  168. package/src/schema/realtime.schema.ts +262 -0
  169. package/src/schema/rigctld.schema.ts +51 -0
  170. package/src/schema/server-message-keys.ts +26 -0
  171. package/src/schema/slot-info.schema.ts +162 -0
  172. package/src/schema/spectrum.schema.ts +166 -0
  173. package/src/schema/station-info.schema.ts +40 -0
  174. package/src/schema/system.schema.ts +121 -0
  175. package/src/schema/transmission.schema.ts +105 -0
  176. package/src/schema/voice.schema.ts +28 -0
  177. package/src/schema/websocket.schema.ts +1480 -0
  178. package/src/utils/grid.ts +94 -0
  179. package/test/mode-schema.test.ts +48 -0
  180. package/tsconfig.json +9 -0
@@ -0,0 +1,158 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * 模式描述符 - 定义 FT8/FT4 等模式的时序参数
5
+ */
6
+ export const ModeDescriptorSchema = z.object({
7
+ /** 模式名称,如 "FT8", "FT4" */
8
+ name: z.string(),
9
+ /** 时隙长度(毫秒),FT8=15000, FT4=7500, VOICE=0 */
10
+ slotMs: z.number().nonnegative(),
11
+ /** 时钟容差(毫秒) */
12
+ toleranceMs: z.number().nonnegative().default(100),
13
+ /**
14
+ * 窗口时机(毫秒)- 必需
15
+ * 使用这些时机作为从时隙结束时间的偏移量
16
+ * 每个窗口都会获取固定长度的解码数据(FT8: 15秒,FT4: 7.5秒)
17
+ * 数组长度决定了子窗口的数量
18
+ * 支持负偏移,可以获取时隙结束前或其他周期的音频数据
19
+ */
20
+ windowTiming: z.array(z.number()),
21
+ /**
22
+ * 发射时机(毫秒)- 从时隙开始的延迟
23
+ * 对齐 WSJT-X 标准:信号在时隙边界后 ~0.5s 开始
24
+ * FT8: 500ms(信号 T+0.5s 起,12.64s 长,T+13.14s 结束)
25
+ * FT4: 500ms(信号 T+0.5s 起,6.0s 长,T+6.5s 结束,留 1.0s 给末端解码)
26
+ */
27
+ transmitTiming: z.number().nonnegative(),
28
+ /**
29
+ * 编码提前量(毫秒)- 在transmitTiming之前多久开始编码
30
+ * 默认400ms,用于补偿编码+混音时间
31
+ */
32
+ encodeAdvance: z.number().nonnegative().default(400)
33
+ });
34
+
35
+ export type ModeDescriptor = z.infer<typeof ModeDescriptorSchema>;
36
+
37
+ /**
38
+ * 预定义的模式
39
+ */
40
+ export const MODES = {
41
+ FT8: {
42
+ name: 'FT8',
43
+ slotMs: 15000,
44
+ toleranceMs: 100,
45
+ windowTiming: [-3200, -1500, -300], // WSJT-X 标准:11.8s / 13.5s / 14.7s 三轮解码
46
+ transmitTiming: 500, // WSJT-X 标准:信号在时隙边界后 ~0.5s 开始
47
+ encodeAdvance: 0 // 编码在 transmitStart 时触发,留出 500ms 给策略决策
48
+ } as ModeDescriptor,
49
+ FT4: {
50
+ name: 'FT4',
51
+ slotMs: 7500,
52
+ toleranceMs: 50,
53
+ // 双 pass 解码:T+6000ms 早 pass(让下一周期 encodeStart 之前能拿到对端消息)+ T+7500ms 末 pass
54
+ windowTiming: [-1500, 0],
55
+ transmitTiming: 500, // WSJT-X 标准:FT4 信号 T+0.5s 起,6.0s 长
56
+ encodeAdvance: 300 // encodeStart 在 T+200ms 触发,留 ~300ms 给编码完成,使 transmitStart 时音频已就绪
57
+ } as ModeDescriptor,
58
+ VOICE: {
59
+ name: 'VOICE',
60
+ slotMs: 0, // 语音模式无时隙概念
61
+ toleranceMs: 0,
62
+ windowTiming: [], // 无解码窗口
63
+ transmitTiming: 0,
64
+ encodeAdvance: 0,
65
+ } as ModeDescriptor,
66
+ } as const;
67
+
68
+ /**
69
+ * 解码窗口预设
70
+ */
71
+ export const DecodeWindowPreset = {
72
+ MAXIMUM: 'maximum',
73
+ BALANCED: 'balanced',
74
+ LIGHTWEIGHT: 'lightweight',
75
+ MINIMUM: 'minimum',
76
+ CUSTOM: 'custom',
77
+ } as const;
78
+
79
+ /**
80
+ * FT8 解码窗口预设映射
81
+ * 偏移量基于时隙结束时间(T+15000ms),即 offset = 实际触发时间 - 15000
82
+ * FT8 信号:T+500ms 开始,T+13140ms 结束(12.64s)
83
+ * WSJT-X 标准三轮:T+11.8s(-3200) / T+13.5s(-1500) / T+14.7s(-300)
84
+ */
85
+ export const FT8_WINDOW_PRESETS: Record<string, number[]> = {
86
+ maximum: [-3200, -1500, -800, -300, -150], // 5 轮
87
+ balanced: [-3200, -1500, -300], // 3 轮:WSJT-X 标准时序
88
+ lightweight: [-3200, -300], // 2 轮:首尾两轮
89
+ minimum: [-300], // 1 轮:信号结束后最终解码
90
+ };
91
+
92
+ /**
93
+ * FT4 解码窗口预设映射
94
+ * 偏移量基于时隙结束时间(T+7500ms)
95
+ * FT4 信号:T+500ms 开始,T+6500ms 结束(6.0s)
96
+ * 双 pass 设计对齐 WSJT-X/JTDX:早 pass 让下一周期 encodeStart 前能用上结果
97
+ */
98
+ export const FT4_WINDOW_PRESETS: Record<string, number[]> = {
99
+ maximum: [-2000, -1000, 0], // 3 轮:T+5.5s / T+6.5s / T+7.5s
100
+ balanced: [-1500, 0], // 2 轮:T+6.0s(早 pass)+ T+7.5s(末 pass)
101
+ lightweight: [0], // 1 轮:仅末端
102
+ };
103
+
104
+ /**
105
+ * 解码窗口设置 Schema
106
+ */
107
+ export const DecodeWindowSettingsSchema = z.object({
108
+ ft8: z.object({
109
+ preset: z.enum(['maximum', 'balanced', 'lightweight', 'minimum', 'custom']).default('balanced'),
110
+ customWindowTiming: z.array(z.number().int().min(-5000).max(1000)).optional(),
111
+ }).optional(),
112
+ ft4: z.object({
113
+ preset: z.enum(['maximum', 'balanced', 'lightweight', 'custom']).default('balanced'),
114
+ customWindowTiming: z.array(z.number().int().min(-5000).max(1000)).optional(),
115
+ }).optional(),
116
+ });
117
+
118
+ export type DecodeWindowSettings = z.infer<typeof DecodeWindowSettingsSchema>;
119
+
120
+ export const DEFAULT_DECODE_WINDOW_SETTINGS: DecodeWindowSettings = {
121
+ ft8: {
122
+ preset: 'balanced',
123
+ },
124
+ ft4: {
125
+ preset: 'balanced',
126
+ },
127
+ };
128
+
129
+ /**
130
+ * 根据模式名和设置解析实际的 windowTiming
131
+ * 返回 null 表示使用 MODES 中的默认值
132
+ */
133
+ export function resolveWindowTiming(
134
+ modeName: string,
135
+ settings?: DecodeWindowSettings
136
+ ): number[] | null {
137
+ if (!settings) return null;
138
+
139
+ const modeKey = modeName.toUpperCase();
140
+
141
+ if (modeKey === 'FT8' && settings.ft8) {
142
+ const { preset, customWindowTiming } = settings.ft8;
143
+ if (preset === 'custom' && customWindowTiming && customWindowTiming.length > 0) {
144
+ return [...customWindowTiming].sort((a, b) => a - b);
145
+ }
146
+ return FT8_WINDOW_PRESETS[preset] ?? null;
147
+ }
148
+
149
+ if (modeKey === 'FT4' && settings.ft4) {
150
+ const { preset, customWindowTiming } = settings.ft4;
151
+ if (preset === 'custom' && customWindowTiming && customWindowTiming.length > 0) {
152
+ return [...customWindowTiming].sort((a, b) => a - b);
153
+ }
154
+ return FT4_WINDOW_PRESETS[preset] ?? null;
155
+ }
156
+
157
+ return null;
158
+ }
@@ -0,0 +1,160 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * OpenWebRX SDR Profile 频率覆盖范围(持久化缓存)
5
+ * Profile API 只返回 {id, name},覆盖范围需切换到该 profile 后从 config 获取。
6
+ * 缓存后避免重复探测(每次探测需 11s 冷却以规避 bot 检测)。
7
+ */
8
+ export const OpenWebRXProfileCoverageSchema = z.object({
9
+ profileId: z.string(),
10
+ profileName: z.string(),
11
+ centerFreq: z.number(),
12
+ sampRate: z.number(),
13
+ lastUpdated: z.number(),
14
+ });
15
+
16
+ /**
17
+ * OpenWebRX SDR 站点配置 Schema
18
+ */
19
+ export const OpenWebRXStationConfigSchema = z.object({
20
+ /** 站点唯一ID */
21
+ id: z.string(),
22
+ /** 用户定义的站点名称 */
23
+ name: z.string().min(1),
24
+ /** WebSocket URL,如 "ws://host:8073" 或 "wss://..." */
25
+ url: z.string().url(),
26
+ /** 站点描述 */
27
+ description: z.string().optional(),
28
+ /** 已知的 Profile 频率覆盖范围缓存(持久化,重启后无需重新探测) */
29
+ profileCoverages: z.array(OpenWebRXProfileCoverageSchema).optional(),
30
+ });
31
+
32
+ /**
33
+ * OpenWebRX SDR Profile 信息(远端服务器返回)
34
+ */
35
+ export const OpenWebRXProfileSchema = z.object({
36
+ /** Profile ID,格式为 "sdr_id|profile_id" */
37
+ id: z.string(),
38
+ /** 显示名称 */
39
+ name: z.string(),
40
+ });
41
+
42
+ /**
43
+ * OpenWebRX 连接测试结果
44
+ */
45
+ export const OpenWebRXTestResultSchema = z.object({
46
+ success: z.boolean(),
47
+ /** 服务器版本 */
48
+ serverVersion: z.string().optional(),
49
+ /** 可用的 SDR Profile 列表 */
50
+ profiles: z.array(OpenWebRXProfileSchema).optional(),
51
+ /** 错误信息 */
52
+ error: z.string().optional(),
53
+ });
54
+
55
+ /**
56
+ * OpenWebRX 试听状态
57
+ */
58
+ export const OpenWebRXListenStatusSchema = z.object({
59
+ /** 试听会话ID,用于生成 LiveKit preview 房间 */
60
+ previewSessionId: z.string().optional(),
61
+ /** 站点ID */
62
+ stationId: z.string(),
63
+ /** 是否已连接 */
64
+ connected: z.boolean(),
65
+ /** 服务器版本 */
66
+ serverVersion: z.string().optional(),
67
+ /** 可用 Profile 列表 */
68
+ profiles: z.array(OpenWebRXProfileSchema),
69
+ /** 当前选中的 Profile ID */
70
+ currentProfileId: z.string().optional(),
71
+ /** 当前中心频率 (Hz) */
72
+ centerFreq: z.number().optional(),
73
+ /** 当前采样率 (Hz) */
74
+ sampleRate: z.number().optional(),
75
+ /** 当前 tune 频率 (Hz) */
76
+ frequency: z.number().optional(),
77
+ /** 当前调制模式 */
78
+ modulation: z.string().optional(),
79
+ /** S-Meter 读数 (dBFS) */
80
+ smeterDb: z.number().optional(),
81
+ /** 是否正在试听 */
82
+ isListening: z.boolean(),
83
+ /** 错误信息 */
84
+ error: z.string().optional(),
85
+ });
86
+
87
+ /**
88
+ * OpenWebRX 试听启动参数
89
+ */
90
+ export const OpenWebRXListenStartSchema = z.object({
91
+ /** 站点ID */
92
+ stationId: z.string(),
93
+ /** 指定 Profile ID(可选,不指定则使用默认) */
94
+ profileId: z.string().optional(),
95
+ /** 频率 (Hz) */
96
+ frequency: z.number().optional(),
97
+ /** 调制模式 */
98
+ modulation: z.string().optional(),
99
+ });
100
+
101
+ /**
102
+ * OpenWebRX 试听调整参数
103
+ */
104
+ export const OpenWebRXListenTuneSchema = z.object({
105
+ /** 切换到的 Profile ID */
106
+ profileId: z.string().optional(),
107
+ /** 频率 (Hz) */
108
+ frequency: z.number().optional(),
109
+ /** 调制模式 */
110
+ modulation: z.string().optional(),
111
+ /** 带通滤波器低端 (Hz) */
112
+ bandpassLow: z.number().optional(),
113
+ /** 带通滤波器高端 (Hz) */
114
+ bandpassHigh: z.number().optional(),
115
+ });
116
+
117
+ /**
118
+ * Server → Client: 请求管理员手动选择 SDR Profile
119
+ * 当引擎自动匹配(缓存 + 名称启发式)全部失败时发出
120
+ */
121
+ export const OpenWebRXProfileSelectRequestSchema = z.object({
122
+ requestId: z.string(),
123
+ targetFrequency: z.number(),
124
+ profiles: z.array(z.object({ id: z.string(), name: z.string() })),
125
+ currentProfileId: z.string().optional(),
126
+ });
127
+
128
+ /**
129
+ * Client → Server: 用户选择了 Profile 并请求验证
130
+ */
131
+ export const OpenWebRXProfileSelectResponseSchema = z.object({
132
+ requestId: z.string(),
133
+ profileId: z.string(),
134
+ targetFrequency: z.number(),
135
+ });
136
+
137
+ /**
138
+ * Server → Client: Profile 验证结果
139
+ */
140
+ export const OpenWebRXProfileVerifyResultSchema = z.object({
141
+ requestId: z.string(),
142
+ success: z.boolean(),
143
+ profileId: z.string(),
144
+ profileName: z.string().optional(),
145
+ centerFreq: z.number().optional(),
146
+ sampRate: z.number().optional(),
147
+ error: z.string().optional(),
148
+ });
149
+
150
+ // 导出类型
151
+ export type OpenWebRXStationConfig = z.infer<typeof OpenWebRXStationConfigSchema>;
152
+ export type OpenWebRXProfile = z.infer<typeof OpenWebRXProfileSchema>;
153
+ export type OpenWebRXProfileCoverage = z.infer<typeof OpenWebRXProfileCoverageSchema>;
154
+ export type OpenWebRXTestResult = z.infer<typeof OpenWebRXTestResultSchema>;
155
+ export type OpenWebRXListenStatus = z.infer<typeof OpenWebRXListenStatusSchema>;
156
+ export type OpenWebRXListenStart = z.infer<typeof OpenWebRXListenStartSchema>;
157
+ export type OpenWebRXListenTune = z.infer<typeof OpenWebRXListenTuneSchema>;
158
+ export type OpenWebRXProfileSelectRequest = z.infer<typeof OpenWebRXProfileSelectRequestSchema>;
159
+ export type OpenWebRXProfileSelectResponse = z.infer<typeof OpenWebRXProfileSelectResponseSchema>;
160
+ export type OpenWebRXProfileVerifyResult = z.infer<typeof OpenWebRXProfileVerifyResultSchema>;
@@ -0,0 +1,112 @@
1
+ import { z } from 'zod';
2
+ import { ModeDescriptorSchema } from './mode.schema.js';
3
+
4
+ // 操作员配置 Schema (重命名以避免冲突)
5
+ export const RadioOperatorConfigSchema = z.object({
6
+ id: z.string().min(1, '操作员ID不能为空'),
7
+ createdByTokenId: z.string().optional(), // 创建者 Token ID(仅用于审计,不参与权限判断)
8
+ myCallsign: z.string().min(1, '呼号不能为空').max(10, '呼号不能超过10个字符'),
9
+ myGrid: z.string().min(4, '网格坐标至少4位').max(8, '网格坐标不能超过8位').optional(),
10
+ frequency: z.number().min(0).max(1000000000, '频率必须在0-1GHz之间').default(1000), // 音频偏移频率(Hz),默认1000Hz,创建时自动分配不重复值
11
+ transmitCycles: z.array(z.number().min(0).max(1)).default([0]), // 0=偶数周期,1=奇数周期
12
+ mode: ModeDescriptorSchema.optional(),
13
+ logBookId: z.string().optional(), // 连接的日志本ID,如果未指定则使用默认日志本
14
+ });
15
+
16
+ // 创建操作员请求(客户端不能指定 id 和 createdByTokenId,由服务端自动设置)
17
+ export const CreateRadioOperatorRequestSchema = RadioOperatorConfigSchema.omit({
18
+ id: true,
19
+ createdByTokenId: true,
20
+ });
21
+
22
+ // 更新操作员请求(客户端不能修改 id 和 createdByTokenId)
23
+ export const UpdateRadioOperatorRequestSchema = RadioOperatorConfigSchema.partial().omit({
24
+ id: true,
25
+ createdByTokenId: true,
26
+ });
27
+
28
+ // 操作员列表响应
29
+ export const RadioOperatorListResponseSchema = z.object({
30
+ success: z.boolean(),
31
+ data: z.array(RadioOperatorConfigSchema),
32
+ });
33
+
34
+ // 操作员详情响应
35
+ export const RadioOperatorDetailResponseSchema = z.object({
36
+ success: z.boolean(),
37
+ data: RadioOperatorConfigSchema,
38
+ });
39
+
40
+ // 操作员操作响应
41
+ export const RadioOperatorActionResponseSchema = z.object({
42
+ success: z.boolean(),
43
+ message: z.string(),
44
+ data: RadioOperatorConfigSchema.optional(),
45
+ });
46
+
47
+ // 操作员状态响应(从websocket.schema.ts导入)
48
+ export const RadioOperatorStatusResponseSchema = z.object({
49
+ success: z.boolean(),
50
+ data: z.object({
51
+ id: z.string(),
52
+ isActive: z.boolean(),
53
+ isTransmitting: z.boolean(),
54
+ currentSlot: z.string().optional(),
55
+ context: z.object({
56
+ myCall: z.string(),
57
+ myGrid: z.string(),
58
+ targetCall: z.string(),
59
+ targetGrid: z.string().optional(),
60
+ frequency: z.number().optional(),
61
+ reportSent: z.number().optional(),
62
+ reportReceived: z.number().optional(),
63
+ }),
64
+ strategy: z.object({
65
+ name: z.string(),
66
+ state: z.string(),
67
+ availableSlots: z.array(z.string()),
68
+ }),
69
+ runtime: z.object({
70
+ currentState: z.string(),
71
+ slots: z.object({
72
+ TX1: z.string().optional(),
73
+ TX2: z.string().optional(),
74
+ TX3: z.string().optional(),
75
+ TX4: z.string().optional(),
76
+ TX5: z.string().optional(),
77
+ TX6: z.string().optional(),
78
+ }).optional(),
79
+ context: z.object({
80
+ targetCallsign: z.string().optional(),
81
+ targetGrid: z.string().optional(),
82
+ reportSent: z.number().optional(),
83
+ reportReceived: z.number().optional(),
84
+ actualFrequency: z.number().optional(),
85
+ }).optional(),
86
+ availableSlots: z.array(z.string()).optional(),
87
+ }).optional(),
88
+ cycleInfo: z.object({
89
+ currentCycle: z.number(),
90
+ isTransmitCycle: z.boolean(),
91
+ cycleProgress: z.number().min(0).max(1),
92
+ }).optional(),
93
+ slots: z.object({
94
+ TX1: z.string().optional(),
95
+ TX2: z.string().optional(),
96
+ TX3: z.string().optional(),
97
+ TX4: z.string().optional(),
98
+ TX5: z.string().optional(),
99
+ TX6: z.string().optional(),
100
+ }).optional(),
101
+ transmitCycles: z.array(z.number()).optional(),
102
+ }),
103
+ });
104
+
105
+ // TypeScript 类型导出
106
+ export type RadioOperatorConfig = z.infer<typeof RadioOperatorConfigSchema>;
107
+ export type CreateRadioOperatorRequest = z.infer<typeof CreateRadioOperatorRequestSchema>;
108
+ export type UpdateRadioOperatorRequest = z.infer<typeof UpdateRadioOperatorRequestSchema>;
109
+ export type RadioOperatorListResponse = z.infer<typeof RadioOperatorListResponseSchema>;
110
+ export type RadioOperatorDetailResponse = z.infer<typeof RadioOperatorDetailResponseSchema>;
111
+ export type RadioOperatorActionResponse = z.infer<typeof RadioOperatorActionResponseSchema>;
112
+ export type RadioOperatorStatusResponse = z.infer<typeof RadioOperatorStatusResponseSchema>;