@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,1480 @@
1
+ import { z } from 'zod';
2
+ import { FT8DecodeSchema } from './ft8.schema.js';
3
+ import { SlotPackSchema, SlotInfoSchema } from './slot-info.schema.js';
4
+ import {
5
+ PluginDataPayloadSchema,
6
+ PluginLogEntrySchema,
7
+ PluginRuntimeLogEntrySchema,
8
+ PluginRuntimeLogHistoryPayloadSchema,
9
+ PluginSystemSnapshotSchema,
10
+ PluginStatusSchema,
11
+ PluginUserActionPayloadSchema,
12
+ } from './plugin.schema.js';
13
+ import type {
14
+ PluginStatus,
15
+ PluginDataPayload,
16
+ PluginLogEntry,
17
+ PluginRuntimeLogEntry,
18
+ PluginRuntimeLogHistoryPayload,
19
+ PluginUserActionPayload,
20
+ PluginSystemSnapshot,
21
+ } from './plugin.schema.js';
22
+ import { ModeDescriptorSchema } from './mode.schema.js';
23
+ import { QSORecordSchema, TargetSelectionPriorityModeSchema } from './qso.schema.js';
24
+ import { LogBookStatisticsSchema } from './logbook.schema.js';
25
+ import { RadioInfoSchema, HamlibConfigSchema, TunerStatusSchema, TunerCapabilitiesSchema, RadioConnectionStatusSchema, ReconnectProgressSchema, CoreRadioCapabilitiesSchema, CoreCapabilityDiagnosticsSchema } from './radio.schema.js';
26
+ import { RadioProfileSchema, ProfileChangedEventSchema } from './radio-profile.schema.js';
27
+ import { UserRole } from './auth.schema.js';
28
+ import type { VoicePTTLock } from './voice.schema.js';
29
+ import { CapabilityListSchema, CapabilityStateSchema, WriteCapabilityPayloadSchema } from './radio-capability.schema.js';
30
+ import { RadioPowerStateEventSchema } from './radio-power.schema.js';
31
+ import { AudioSidecarStatusPayloadSchema } from './audio-sidecar.schema.js';
32
+ import { SpectrumCapabilitiesSchema, SpectrumFrameSchema, SpectrumKindSchema, SpectrumSessionControlActionSchema, SpectrumSessionControlIdSchema, SpectrumSessionStateSchema } from './spectrum.schema.js';
33
+ import type { RealtimeSettingsResponseData } from './realtime.schema.js';
34
+ import { ClockStatusSummarySchema } from './system.schema.js';
35
+
36
+ // WebSocket消息类型枚举
37
+ export enum WSMessageType {
38
+ // ===== 基础连接管理 =====
39
+ PING = 'ping',
40
+ PONG = 'pong',
41
+ ERROR = 'error',
42
+ /** 服务端通知客户端:此连接已被同一 clientInstance 的新连接替换,客户端不应重连 */
43
+ CONNECTION_REPLACED = 'connectionReplaced',
44
+
45
+ // ===== 引擎控制 =====
46
+ START_ENGINE = 'startEngine',
47
+ STOP_ENGINE = 'stopEngine',
48
+ GET_STATUS = 'getStatus',
49
+ SET_MODE = 'setMode',
50
+
51
+ // ===== 引擎事件 =====
52
+ MODE_CHANGED = 'modeChanged',
53
+ SLOT_START = 'slotStart',
54
+ SUB_WINDOW = 'subWindow',
55
+ SLOT_PACK_UPDATED = 'slotPackUpdated',
56
+ SPECTRUM_CAPABILITIES = 'spectrumCapabilities',
57
+ SUBSCRIBE_SPECTRUM = 'subscribeSpectrum',
58
+ SPECTRUM_FRAME = 'spectrumFrame',
59
+ SPECTRUM_SESSION_STATE_CHANGED = 'spectrumSessionStateChanged',
60
+ INVOKE_SPECTRUM_CONTROL = 'invokeSpectrumControl',
61
+ DECODE_ERROR = 'decodeError',
62
+ SYSTEM_STATUS = 'systemStatus',
63
+ CLIENT_COUNT_CHANGED = 'clientCountChanged',
64
+ CLOCK_STATUS_CHANGED = 'clockStatusChanged',
65
+ RIGCTLD_STATUS = 'rigctldStatus',
66
+
67
+ // ===== 电台操作员管理 =====
68
+ GET_OPERATORS = 'getOperators',
69
+ OPERATORS_LIST = 'operatorsList',
70
+ OPERATOR_STATUS_UPDATE = 'operatorStatusUpdate',
71
+ SET_OPERATOR_CONTEXT = 'setOperatorContext',
72
+ SET_OPERATOR_RUNTIME_STATE = 'setOperatorRuntimeState',
73
+ SET_OPERATOR_RUNTIME_SLOT_CONTENT = 'setOperatorRuntimeSlotContent',
74
+ SET_OPERATOR_TRANSMIT_CYCLES = 'setOperatorTransmitCycles',
75
+ START_OPERATOR = 'startOperator',
76
+ STOP_OPERATOR = 'stopOperator',
77
+ OPERATOR_REQUEST_CALL = 'operatorRequestCall',
78
+
79
+ // ===== 客户端操作员过滤 =====
80
+ SET_CLIENT_ENABLED_OPERATORS = 'setClientEnabledOperators',
81
+ SET_CLIENT_SELECTED_OPERATOR = 'setClientSelectedOperator',
82
+
83
+ // ===== 握手协议 =====
84
+ CLIENT_HANDSHAKE = 'clientHandshake',
85
+ SERVER_HANDSHAKE_COMPLETE = 'serverHandshakeComplete',
86
+ SLOT_PACKS_RESET = 'slotPacksReset',
87
+
88
+ // ===== 发射日志 =====
89
+ TRANSMISSION_LOG = 'transmissionLog',
90
+
91
+ // ===== 音量控制 =====
92
+ SET_VOLUME_GAIN = 'setVolumeGain',
93
+ SET_VOLUME_GAIN_DB = 'setVolumeGainDb',
94
+ VOLUME_GAIN_CHANGED = 'volumeGainChanged',
95
+
96
+ // ===== 通联日志 =====
97
+ QSO_RECORD_ADDED = 'qsoRecordAdded',
98
+ QSO_RECORD_UPDATED = 'qsoRecordUpdated',
99
+ LOGBOOK_UPDATED = 'logbookUpdated',
100
+ // 仅通知的日志本变更事件(专用于日志本WS)
101
+ LOGBOOK_CHANGE_NOTICE = 'logbookChangeNotice',
102
+
103
+ // ===== 电台连接管理 =====
104
+ RADIO_STATUS_CHANGED = 'radioStatusChanged',
105
+ RADIO_ERROR = 'radioError',
106
+ RADIO_MANUAL_RECONNECT = 'radioManualReconnect',
107
+ RADIO_DISCONNECTED_DURING_TRANSMISSION = 'radioDisconnectedDuringTransmission',
108
+
109
+ // ===== 频率管理 =====
110
+ FREQUENCY_CHANGED = 'frequencyChanged',
111
+ // ===== PTT状态管理 =====
112
+ PTT_STATUS_CHANGED = 'pttStatusChanged',
113
+ FORCE_STOP_TRANSMISSION = 'forceStopTransmission',
114
+ REMOVE_OPERATOR_FROM_TRANSMISSION = 'removeOperatorFromTransmission',
115
+
116
+ // ===== 电台数值表 =====
117
+ METER_DATA = 'meterData',
118
+
119
+ // ===== 极简文本消息 =====
120
+ TEXT_MESSAGE = 'textMessage',
121
+
122
+ // ===== 天线调谐器(已迁移至统一能力系统,枚举值保留以避免运行时错误,不再广播)=====
123
+ /** @deprecated Use RADIO_CAPABILITY_CHANGED with id='tuner_switch' instead */
124
+ TUNER_STATUS_CHANGED = 'tunerStatusChanged',
125
+
126
+ // ===== 统一电台控制能力系统 =====
127
+ /** 连接时下发能力快照(server → client) */
128
+ RADIO_CAPABILITY_LIST = 'radioCapabilityList',
129
+ /** 单个能力值变化(server → client) */
130
+ RADIO_CAPABILITY_CHANGED = 'radioCapabilityChanged',
131
+ /** 客户端写入能力值(client → server) */
132
+ WRITE_RADIO_CAPABILITY = 'writeRadioCapability',
133
+ /** 客户端请求刷新所有能力值(client → server) */
134
+ REFRESH_RADIO_CAPABILITIES = 'refreshRadioCapabilities',
135
+
136
+ // ===== 电台重连控制 =====
137
+ RADIO_STOP_RECONNECT = 'radioStopReconnect',
138
+
139
+ // ===== 音频旁路(sidecar) =====
140
+ /** 音频 sidecar 状态变化(server → client) */
141
+ AUDIO_SIDECAR_STATUS_CHANGED = 'audioSidecarStatusChanged',
142
+ /** 客户端请求立即重试音频启动(client → server) */
143
+ AUDIO_RETRY_NOW = 'audioRetryNow',
144
+
145
+ // ===== 电台电源管理 =====
146
+ /** 电源操作进度事件(server → client) */
147
+ RADIO_POWER_STATE = 'radioPowerState',
148
+
149
+ // ===== Profile 管理 =====
150
+ PROFILE_CHANGED = 'profileChanged',
151
+ PROFILE_LIST_UPDATED = 'profileListUpdated',
152
+ REALTIME_SETTINGS_CHANGED = 'realtimeSettingsChanged',
153
+
154
+ // ===== 认证 =====
155
+ AUTH_REQUIRED = 'authRequired',
156
+ AUTH_TOKEN = 'authToken',
157
+ AUTH_PUBLIC_VIEWER = 'authPublicViewer',
158
+ AUTH_RESULT = 'authResult',
159
+ AUTH_EXPIRED = 'authExpired',
160
+
161
+ // ===== 语音模式 =====
162
+ VOICE_PTT_REQUEST = 'voicePttRequest',
163
+ VOICE_PTT_RELEASE = 'voicePttRelease',
164
+ VOICE_PTT_LOCK_CHANGED = 'voicePttLockChanged',
165
+ VOICE_SET_RADIO_MODE = 'voiceSetRadioMode',
166
+ VOICE_RADIO_MODE_CHANGED = 'voiceRadioModeChanged',
167
+
168
+ // ===== 进程监控 =====
169
+ PROCESS_SNAPSHOT = 'processSnapshot',
170
+ PROCESS_SNAPSHOT_HISTORY = 'processSnapshotHistory',
171
+
172
+ // ===== 插件系统 =====
173
+ PLUGIN_LIST = 'pluginList',
174
+ PLUGIN_STATUS_CHANGED = 'pluginStatusChanged',
175
+ PLUGIN_DATA = 'pluginData',
176
+ PLUGIN_LOG = 'pluginLog',
177
+ PLUGIN_RUNTIME_LOG = 'pluginRuntimeLog',
178
+ GET_PLUGIN_RUNTIME_LOG_HISTORY = 'getPluginRuntimeLogHistory',
179
+ PLUGIN_RUNTIME_LOG_HISTORY = 'pluginRuntimeLogHistory',
180
+ PLUGIN_USER_ACTION = 'pluginUserAction',
181
+ PLUGIN_PAGE_PUSH = 'pluginPagePush',
182
+
183
+ // ===== OpenWebRX SDR =====
184
+ OPENWEBRX_LISTEN_STATUS = 'openwebrxListenStatus',
185
+ OPENWEBRX_PROFILE_SELECT_REQUEST = 'openwebrxProfileSelectRequest',
186
+ OPENWEBRX_PROFILE_SELECT_RESPONSE = 'openwebrxProfileSelectResponse',
187
+ OPENWEBRX_PROFILE_VERIFY_RESULT = 'openwebrxProfileVerifyResult',
188
+ OPENWEBRX_CLIENT_COUNT = 'openwebrxClientCount',
189
+ OPENWEBRX_COOLDOWN_NOTICE = 'openwebrxCooldownNotice',
190
+ }
191
+
192
+ // ===== 共享数据类型Schema定义 =====
193
+
194
+ // 系统状态数据结构
195
+ export const SystemStatusSchema = z.object({
196
+ isRunning: z.boolean(),
197
+ isDecoding: z.boolean(),
198
+ currentMode: ModeDescriptorSchema,
199
+ currentTime: z.number(),
200
+ nextSlotIn: z.number(),
201
+ audioStarted: z.boolean(),
202
+ radioConnected: z.boolean().optional(),
203
+ radioConnectionHealth: z.object({
204
+ connectionHealthy: z.boolean(),
205
+ }).optional(),
206
+ /** 引擎模式:digital(FT8/FT4)或 voice(语音通联) */
207
+ engineMode: z.enum(['digital', 'voice']).default('digital'),
208
+ /** 当前电台调制模式(语音模式下使用,如 USB/LSB/FM/AM) */
209
+ currentRadioMode: z.string().optional(),
210
+ /** 引擎状态机当前状态 */
211
+ engineState: z.enum(['idle', 'waking', 'starting', 'running', 'stopping']).optional(),
212
+ /** 引擎状态机上下文的精简投影 */
213
+ engineContext: z.object({
214
+ error: z.string().optional(),
215
+ startedResources: z.array(z.string()).optional(),
216
+ forcedStop: z.boolean().optional(),
217
+ }).nullable().optional(),
218
+ });
219
+
220
+ // 子窗口信息数据结构
221
+ export const SubWindowInfoSchema = z.object({
222
+ slotInfo: SlotInfoSchema,
223
+ windowIdx: z.number(),
224
+ });
225
+
226
+ // 解码错误信息数据结构
227
+ export const DecodeErrorInfoSchema = z.object({
228
+ error: z.object({
229
+ message: z.string(),
230
+ stack: z.string().optional(),
231
+ }),
232
+ request: z.object({
233
+ slotId: z.string(),
234
+ windowIdx: z.number(),
235
+ }),
236
+ });
237
+
238
+ // 频率状态数据结构
239
+ export const FrequencyStateSchema = z.object({
240
+ frequency: z.number(),
241
+ mode: z.string(),
242
+ band: z.string(),
243
+ description: z.string(),
244
+ radioMode: z.string().optional(),
245
+ radioConnected: z.boolean(),
246
+ source: z.enum(['program', 'radio']).optional(),
247
+ });
248
+
249
+ export const SpectrumControlInvocationSchema = z.object({
250
+ id: SpectrumSessionControlIdSchema,
251
+ action: SpectrumSessionControlActionSchema,
252
+ });
253
+ export type SpectrumControlInvocation = z.infer<typeof SpectrumControlInvocationSchema>;
254
+
255
+ // PTT状态数据结构
256
+ export const PTTStatusSchema = z.object({
257
+ isTransmitting: z.boolean(),
258
+ operatorIds: z.array(z.string()),
259
+ });
260
+
261
+ export const OperatorRuntimeSlotSchema = z.enum(['TX1', 'TX2', 'TX3', 'TX4', 'TX5', 'TX6']);
262
+ export type OperatorRuntimeSlot = z.infer<typeof OperatorRuntimeSlotSchema>;
263
+
264
+ export const StrategyRuntimeContextSchema = z.object({
265
+ targetCallsign: z.string().optional(),
266
+ targetGrid: z.string().optional(),
267
+ reportSent: z.number().optional(),
268
+ reportReceived: z.number().optional(),
269
+ actualFrequency: z.number().optional(),
270
+ });
271
+ export type StrategyRuntimeContext = z.infer<typeof StrategyRuntimeContextSchema>;
272
+
273
+ export const StrategyRuntimeSnapshotSchema = z.object({
274
+ currentState: z.string(),
275
+ slots: z.object({
276
+ TX1: z.string().optional(),
277
+ TX2: z.string().optional(),
278
+ TX3: z.string().optional(),
279
+ TX4: z.string().optional(),
280
+ TX5: z.string().optional(),
281
+ TX6: z.string().optional(),
282
+ }).optional(),
283
+ context: StrategyRuntimeContextSchema.optional(),
284
+ availableSlots: z.array(z.string()).optional(),
285
+ });
286
+ export type StrategyRuntimeSnapshot = z.infer<typeof StrategyRuntimeSnapshotSchema>;
287
+
288
+ export const LevelMeterDisplayStyleSchema = z.enum([
289
+ 's-meter-dbm',
290
+ 's-meter',
291
+ 'db-over-s9',
292
+ ]);
293
+ export type LevelMeterDisplayStyle = z.infer<typeof LevelMeterDisplayStyleSchema>;
294
+
295
+ /**
296
+ * Meter level reading
297
+ *
298
+ * Depending on the radio/vendor strategy, the same structure can represent:
299
+ * - branded S-meter output with optional dBm estimate
300
+ * - branded S-meter output without dBm display
301
+ * - generic Hamlib STRENGTH output shown as dB relative to S9
302
+ */
303
+ export const LevelMeterReadingSchema = z.object({
304
+ /** Raw meter reading from the radio or normalized backend value */
305
+ raw: z.number(),
306
+ /** Percentage (0-100%) */
307
+ percent: z.number(),
308
+ /** S-unit value (0-9+), supports decimal (e.g., 4.5 = S4.5) */
309
+ sUnits: z.number(),
310
+ /** dB above S9 (only when >S9, e.g., 20 means S9+20dB) */
311
+ dbAboveS9: z.number().optional(),
312
+ /** Estimated absolute signal level in dBm (may be approximate depending on strategy) */
313
+ dBm: z.number(),
314
+ /** Human-readable formatted string (e.g., "S4", "S9+20dB", "-24 dB@S9") */
315
+ formatted: z.string(),
316
+ /** Preferred frontend presentation style for this meter reading */
317
+ displayStyle: LevelMeterDisplayStyleSchema,
318
+ });
319
+
320
+ export type LevelMeterReading = z.infer<typeof LevelMeterReadingSchema>;
321
+
322
+ // 电台数值表能力检测
323
+ export const MeterCapabilitiesSchema = z.object({
324
+ strength: z.boolean(), // 接收信号强度 (STRENGTH)
325
+ swr: z.boolean(), // 驻波比 (SWR)
326
+ alc: z.boolean(), // 自动电平控制 (ALC)
327
+ power: z.boolean(), // 射频功率 (RFPOWER_METER / RFPOWER_METER_WATTS)
328
+ powerWatts: z.boolean(), // 是否支持绝对瓦数读取 (RFPOWER_METER_WATTS)
329
+ });
330
+ export type MeterCapabilities = z.infer<typeof MeterCapabilitiesSchema>;
331
+
332
+ // 电台数值表数据结构
333
+ export const MeterDataSchema = z.object({
334
+ swr: z.object({
335
+ raw: z.number(),
336
+ swr: z.number(),
337
+ alert: z.boolean(),
338
+ }).nullable(),
339
+ alc: z.object({
340
+ raw: z.number(),
341
+ percent: z.number(),
342
+ alert: z.boolean(),
343
+ }).nullable(),
344
+ level: LevelMeterReadingSchema.nullable(),
345
+ power: z.object({
346
+ raw: z.number(),
347
+ percent: z.number(),
348
+ watts: z.number().nullable(), // 绝对瓦数(仅当电台支持 RFPOWER_METER_WATTS 时非 null)
349
+ maxWatts: z.number().nullable(), // 当前频率+模式对应的量程瓦数
350
+ }).nullable(),
351
+ });
352
+
353
+ // ===== 导出共享类型 =====
354
+ export type SystemStatus = z.infer<typeof SystemStatusSchema>;
355
+ export type SubWindowInfo = z.infer<typeof SubWindowInfoSchema>;
356
+ export type DecodeErrorInfo = z.infer<typeof DecodeErrorInfoSchema>;
357
+ export type FrequencyState = z.infer<typeof FrequencyStateSchema>;
358
+ export type PTTStatus = z.infer<typeof PTTStatusSchema>;
359
+ export type MeterData = z.infer<typeof MeterDataSchema>;
360
+
361
+ // ===== WebSocket消息Schema定义 =====
362
+
363
+ // WebSocket基础消息结构
364
+ export const WSBaseMessageSchema = z.object({
365
+ type: z.nativeEnum(WSMessageType),
366
+ timestamp: z.string(),
367
+ id: z.string().optional(),
368
+ });
369
+
370
+ // 通用消息
371
+
372
+ export const WSPingMessageSchema = WSBaseMessageSchema.extend({
373
+ type: z.literal(WSMessageType.PING),
374
+ data: z.object({}).optional(),
375
+ });
376
+
377
+ export const WSPongMessageSchema = WSBaseMessageSchema.extend({
378
+ type: z.literal(WSMessageType.PONG),
379
+ data: z.object({}).optional(),
380
+ });
381
+
382
+ // 服务端到客户端消息
383
+ export const WSModeChangedMessageSchema = WSBaseMessageSchema.extend({
384
+ type: z.literal(WSMessageType.MODE_CHANGED),
385
+ data: ModeDescriptorSchema,
386
+ });
387
+
388
+ export const WSSlotStartMessageSchema = WSBaseMessageSchema.extend({
389
+ type: z.literal(WSMessageType.SLOT_START),
390
+ data: SlotInfoSchema,
391
+ });
392
+
393
+ export const WSSubWindowMessageSchema = WSBaseMessageSchema.extend({
394
+ type: z.literal(WSMessageType.SUB_WINDOW),
395
+ data: SubWindowInfoSchema,
396
+ });
397
+
398
+ export const WSSlotPackUpdatedMessageSchema = WSBaseMessageSchema.extend({
399
+ type: z.literal(WSMessageType.SLOT_PACK_UPDATED),
400
+ data: SlotPackSchema,
401
+ });
402
+
403
+ export const WSSpectrumCapabilitiesMessageSchema = WSBaseMessageSchema.extend({
404
+ type: z.literal(WSMessageType.SPECTRUM_CAPABILITIES),
405
+ data: SpectrumCapabilitiesSchema,
406
+ });
407
+
408
+ export const WSSubscribeSpectrumMessageSchema = WSBaseMessageSchema.extend({
409
+ type: z.literal(WSMessageType.SUBSCRIBE_SPECTRUM),
410
+ data: z.object({
411
+ kind: SpectrumKindSchema.nullable(),
412
+ }),
413
+ });
414
+
415
+ export const WSSpectrumFrameMessageSchema = WSBaseMessageSchema.extend({
416
+ type: z.literal(WSMessageType.SPECTRUM_FRAME),
417
+ data: SpectrumFrameSchema,
418
+ });
419
+
420
+ export const WSSpectrumSessionStateChangedMessageSchema = WSBaseMessageSchema.extend({
421
+ type: z.literal(WSMessageType.SPECTRUM_SESSION_STATE_CHANGED),
422
+ data: SpectrumSessionStateSchema,
423
+ });
424
+
425
+ export const WSInvokeSpectrumControlMessageSchema = WSBaseMessageSchema.extend({
426
+ type: z.literal(WSMessageType.INVOKE_SPECTRUM_CONTROL),
427
+ data: SpectrumControlInvocationSchema,
428
+ });
429
+
430
+ export const WSDecodeErrorMessageSchema = WSBaseMessageSchema.extend({
431
+ type: z.literal(WSMessageType.DECODE_ERROR),
432
+ data: DecodeErrorInfoSchema,
433
+ });
434
+
435
+ export const WSSystemStatusMessageSchema = WSBaseMessageSchema.extend({
436
+ type: z.literal(WSMessageType.SYSTEM_STATUS),
437
+ data: SystemStatusSchema,
438
+ });
439
+
440
+ export const WSClientCountChangedMessageSchema = WSBaseMessageSchema.extend({
441
+ type: z.literal(WSMessageType.CLIENT_COUNT_CHANGED),
442
+ data: z.object({
443
+ count: z.number(),
444
+ timestamp: z.number(),
445
+ }),
446
+ });
447
+
448
+ export const WSClockStatusChangedMessageSchema = WSBaseMessageSchema.extend({
449
+ type: z.literal(WSMessageType.CLOCK_STATUS_CHANGED),
450
+ data: ClockStatusSummarySchema,
451
+ });
452
+
453
+ export const WSErrorMessageSchema = WSBaseMessageSchema.extend({
454
+ type: z.literal(WSMessageType.ERROR),
455
+ data: z.object({
456
+ message: z.string(),
457
+ userMessage: z.string().optional(),
458
+ /** 前端 i18n 翻译键(优先用于 Toast 副标题本地化) */
459
+ userMessageKey: z.string().optional(),
460
+ userMessageParams: z.record(z.union([z.string(), z.number()])).optional(),
461
+ code: z.string().optional(),
462
+ severity: z.enum(['info', 'warning', 'error', 'critical']).optional(),
463
+ suggestions: z.array(z.string()).optional(),
464
+ timestamp: z.union([z.string(), z.number()]).optional(),
465
+ context: z.record(z.unknown()).optional(),
466
+ details: z.any().optional(),
467
+ }),
468
+ });
469
+
470
+ // 日志本轻量变更通知(仅包含标识信息)
471
+ export const WSLogbookChangeNoticeMessageSchema = WSBaseMessageSchema.extend({
472
+ type: z.literal(WSMessageType.LOGBOOK_CHANGE_NOTICE),
473
+ data: z.object({
474
+ logBookId: z.string(),
475
+ operatorId: z.string().optional(),
476
+ }),
477
+ });
478
+
479
+ // 客户端到服务端消息
480
+ export const WSStartEngineMessageSchema = WSBaseMessageSchema.extend({
481
+ type: z.literal(WSMessageType.START_ENGINE),
482
+ data: z.object({}).optional(),
483
+ });
484
+
485
+ export const WSStopEngineMessageSchema = WSBaseMessageSchema.extend({
486
+ type: z.literal(WSMessageType.STOP_ENGINE),
487
+ data: z.object({}).optional(),
488
+ });
489
+
490
+ export const WSSetModeMessageSchema = WSBaseMessageSchema.extend({
491
+ type: z.literal(WSMessageType.SET_MODE),
492
+ data: z.object({
493
+ mode: ModeDescriptorSchema,
494
+ }),
495
+ });
496
+
497
+ export const WSGetStatusMessageSchema = WSBaseMessageSchema.extend({
498
+ type: z.literal(WSMessageType.GET_STATUS),
499
+ data: z.object({}).optional(),
500
+ });
501
+
502
+ // ===== 电台操作员相关Schema =====
503
+
504
+ /**
505
+ * 电台操作员状态信息
506
+ */
507
+ export const OperatorStatusSchema = z.object({
508
+ id: z.string(),
509
+ isActive: z.boolean(),
510
+ isTransmitting: z.boolean(), // 是否正在发射(发射开关状态)
511
+ isInActivePTT: z.boolean().optional(), // 该操作员的音频是否正在被实际播放
512
+ currentSlot: z.string().optional(),
513
+ context: z.object({
514
+ myCall: z.string(),
515
+ myGrid: z.string(),
516
+ targetCall: z.string(),
517
+ targetGrid: z.string().optional(),
518
+ frequency: z.number().optional(),
519
+ reportSent: z.number().optional(), // 改为number类型
520
+ reportReceived: z.number().optional(), // 改为number类型
521
+ }),
522
+ strategy: z.object({
523
+ name: z.string(),
524
+ state: z.string(),
525
+ availableSlots: z.array(z.string()),
526
+ }),
527
+ runtime: StrategyRuntimeSnapshotSchema.optional(),
528
+ cycleInfo: z.object({
529
+ currentCycle: z.number(),
530
+ isTransmitCycle: z.boolean(),
531
+ cycleProgress: z.number().min(0).max(1), // 0-1 表示周期进度百分比
532
+ }).optional(),
533
+ // TX1-TX6 时隙内容
534
+ slots: z.object({
535
+ TX1: z.string().optional(),
536
+ TX2: z.string().optional(),
537
+ TX3: z.string().optional(),
538
+ TX4: z.string().optional(),
539
+ TX5: z.string().optional(),
540
+ TX6: z.string().optional(),
541
+ }).optional(),
542
+ // 发射周期配置
543
+ transmitCycles: z.array(z.number()).optional(),
544
+ });
545
+
546
+ export type OperatorStatus = z.infer<typeof OperatorStatusSchema>;
547
+
548
+ /**
549
+ * 获取操作员列表消息
550
+ */
551
+ export const WSGetOperatorsMessageSchema = WSBaseMessageSchema.extend({
552
+ type: z.literal(WSMessageType.GET_OPERATORS),
553
+ });
554
+
555
+ /**
556
+ * 操作员列表响应消息
557
+ */
558
+ export const WSOperatorsListMessageSchema = WSBaseMessageSchema.extend({
559
+ type: z.literal(WSMessageType.OPERATORS_LIST),
560
+ data: z.object({
561
+ operators: z.array(OperatorStatusSchema),
562
+ }),
563
+ });
564
+
565
+ /**
566
+ * 操作员状态更新消息
567
+ */
568
+ export const WSOperatorStatusUpdateMessageSchema = WSBaseMessageSchema.extend({
569
+ type: z.literal(WSMessageType.OPERATOR_STATUS_UPDATE),
570
+ data: OperatorStatusSchema,
571
+ });
572
+
573
+ /**
574
+ * 设置操作员上下文消息
575
+ */
576
+ export const WSSetOperatorContextMessageSchema = WSBaseMessageSchema.extend({
577
+ type: z.literal(WSMessageType.SET_OPERATOR_CONTEXT),
578
+ data: z.object({
579
+ operatorId: z.string(),
580
+ context: z.object({
581
+ myCall: z.string().optional(),
582
+ myGrid: z.string().optional(),
583
+ targetCallsign: z.string().optional(),
584
+ targetGrid: z.string().optional(),
585
+ frequency: z.number().optional(),
586
+ reportSent: z.number().optional(),
587
+ reportReceived: z.number().optional(),
588
+ }),
589
+ }),
590
+ });
591
+
592
+ /**
593
+ * 设置操作员运行时状态消息
594
+ */
595
+ export const WSSetOperatorRuntimeStateMessageSchema = WSBaseMessageSchema.extend({
596
+ type: z.literal(WSMessageType.SET_OPERATOR_RUNTIME_STATE),
597
+ data: z.object({
598
+ operatorId: z.string(),
599
+ state: OperatorRuntimeSlotSchema,
600
+ }),
601
+ });
602
+
603
+ /**
604
+ * 设置操作员运行时槽位内容消息
605
+ */
606
+ export const WSSetOperatorRuntimeSlotContentMessageSchema = WSBaseMessageSchema.extend({
607
+ type: z.literal(WSMessageType.SET_OPERATOR_RUNTIME_SLOT_CONTENT),
608
+ data: z.object({
609
+ operatorId: z.string(),
610
+ slot: OperatorRuntimeSlotSchema,
611
+ content: z.string(),
612
+ }),
613
+ });
614
+
615
+ /**
616
+ * 设置操作员发射周期消息
617
+ */
618
+ export const WSSetOperatorTransmitCyclesMessageSchema = WSBaseMessageSchema.extend({
619
+ type: z.literal(WSMessageType.SET_OPERATOR_TRANSMIT_CYCLES),
620
+ data: z.object({
621
+ operatorId: z.string(),
622
+ transmitCycles: z.array(z.number().min(0).max(1)),
623
+ }),
624
+ });
625
+
626
+ /**
627
+ * 启动操作员消息
628
+ */
629
+ export const WSStartOperatorMessageSchema = WSBaseMessageSchema.extend({
630
+ type: z.literal(WSMessageType.START_OPERATOR),
631
+ data: z.object({
632
+ operatorId: z.string(),
633
+ }),
634
+ });
635
+
636
+ /**
637
+ * 停止操作员消息
638
+ */
639
+ export const WSStopOperatorMessageSchema = WSBaseMessageSchema.extend({
640
+ type: z.literal(WSMessageType.STOP_OPERATOR),
641
+ data: z.object({
642
+ operatorId: z.string(),
643
+ }),
644
+ });
645
+
646
+ /**
647
+ * 手动双击消息行时附带的原始解码快照
648
+ */
649
+ export const WSSelectedFrameSchema = z.object({
650
+ message: z.string(),
651
+ snr: z.number(),
652
+ dt: z.number(),
653
+ freq: z.number(),
654
+ slotStartMs: z.number(),
655
+ });
656
+
657
+ /**
658
+ * 操作员请求手动呼叫消息
659
+ */
660
+ export const WSOperatorRequestCallMessageSchema = WSBaseMessageSchema.extend({
661
+ type: z.literal(WSMessageType.OPERATOR_REQUEST_CALL),
662
+ data: z.object({
663
+ operatorId: z.string(),
664
+ callsign: z.string(),
665
+ selectedFrame: WSSelectedFrameSchema.optional(),
666
+ }),
667
+ });
668
+
669
+ /**
670
+ * 设置客户端启用的操作员列表消息
671
+ */
672
+ export const WSSetClientEnabledOperatorsMessageSchema = WSBaseMessageSchema.extend({
673
+ type: z.literal(WSMessageType.SET_CLIENT_ENABLED_OPERATORS),
674
+ data: z.object({
675
+ enabledOperatorIds: z.array(z.string()),
676
+ }),
677
+ });
678
+
679
+ /**
680
+ * 设置客户端当前选中操作员消息
681
+ */
682
+ export const WSSetClientSelectedOperatorMessageSchema = WSBaseMessageSchema.extend({
683
+ type: z.literal(WSMessageType.SET_CLIENT_SELECTED_OPERATOR),
684
+ data: z.object({
685
+ selectedOperatorId: z.string().nullable(),
686
+ }),
687
+ });
688
+
689
+ /**
690
+ * 客户端握手消息
691
+ */
692
+ export const WSClientHandshakeMessageSchema = WSBaseMessageSchema.extend({
693
+ type: z.literal(WSMessageType.CLIENT_HANDSHAKE),
694
+ data: z.object({
695
+ enabledOperatorIds: z.array(z.string()).nullable(), // null表示新客户端,数组表示已配置的偏好
696
+ selectedOperatorId: z.string().nullable().optional(),
697
+ clientInstanceId: z.string().min(1),
698
+ clientVersion: z.string().optional(),
699
+ clientCapabilities: z.array(z.string()).optional(),
700
+ }),
701
+ });
702
+
703
+ /**
704
+ * 服务器握手完成消息
705
+ */
706
+ export const WSServerHandshakeCompleteMessageSchema = WSBaseMessageSchema.extend({
707
+ type: z.literal(WSMessageType.SERVER_HANDSHAKE_COMPLETE),
708
+ data: z.object({
709
+ serverVersion: z.string().optional(),
710
+ supportedFeatures: z.array(z.string()).optional(),
711
+ finalEnabledOperatorIds: z.array(z.string()).optional(),
712
+ finalSelectedOperatorId: z.string().nullable().optional(),
713
+ }),
714
+ });
715
+
716
+ export const WSSlotPacksResetMessageSchema = WSBaseMessageSchema.extend({
717
+ type: z.literal(WSMessageType.SLOT_PACKS_RESET),
718
+ data: z.object({
719
+ phase: z.enum(['start', 'complete']),
720
+ }),
721
+ });
722
+
723
+ // 导出类型
724
+ export type WSGetOperatorsMessage = z.infer<typeof WSGetOperatorsMessageSchema>;
725
+ export type WSOperatorsListMessage = z.infer<typeof WSOperatorsListMessageSchema>;
726
+ export type WSOperatorStatusUpdateMessage = z.infer<typeof WSOperatorStatusUpdateMessageSchema>;
727
+ export type WSSetOperatorContextMessage = z.infer<typeof WSSetOperatorContextMessageSchema>;
728
+ export type WSSetOperatorRuntimeStateMessage = z.infer<typeof WSSetOperatorRuntimeStateMessageSchema>;
729
+ export type WSSetOperatorRuntimeSlotContentMessage = z.infer<typeof WSSetOperatorRuntimeSlotContentMessageSchema>;
730
+ export type WSSetOperatorTransmitCyclesMessage = z.infer<typeof WSSetOperatorTransmitCyclesMessageSchema>;
731
+ export type WSStartOperatorMessage = z.infer<typeof WSStartOperatorMessageSchema>;
732
+ export type WSStopOperatorMessage = z.infer<typeof WSStopOperatorMessageSchema>;
733
+ export type WSSelectedFrame = z.infer<typeof WSSelectedFrameSchema>;
734
+ export type WSOperatorRequestCallMessage = z.infer<typeof WSOperatorRequestCallMessageSchema>;
735
+ export type WSSetClientEnabledOperatorsMessage = z.infer<typeof WSSetClientEnabledOperatorsMessageSchema>;
736
+ export type WSSetClientSelectedOperatorMessage = z.infer<typeof WSSetClientSelectedOperatorMessageSchema>;
737
+ export type WSClientHandshakeMessage = z.infer<typeof WSClientHandshakeMessageSchema>;
738
+ export type WSServerHandshakeCompleteMessage = z.infer<typeof WSServerHandshakeCompleteMessageSchema>;
739
+ export type WSSlotPacksResetMessage = z.infer<typeof WSSlotPacksResetMessageSchema>;
740
+
741
+ /**
742
+ * 发射日志消息
743
+ */
744
+ export const WSTransmissionLogMessageSchema = WSBaseMessageSchema.extend({
745
+ type: z.literal(WSMessageType.TRANSMISSION_LOG),
746
+ data: z.object({
747
+ operatorId: z.string(),
748
+ time: z.string(),
749
+ message: z.string(),
750
+ frequency: z.number(),
751
+ slotStartMs: z.number()
752
+ }),
753
+ });
754
+
755
+ export type WSTransmissionLogMessage = z.infer<typeof WSTransmissionLogMessageSchema>;
756
+
757
+ /**
758
+ * 设置音量增益消息(线性单位)
759
+ */
760
+ export const WSSetVolumeGainMessageSchema = WSBaseMessageSchema.extend({
761
+ type: z.literal(WSMessageType.SET_VOLUME_GAIN),
762
+ data: z.object({
763
+ gain: z.number().min(0.001).max(10),
764
+ }),
765
+ });
766
+
767
+ export type WSSetVolumeGainMessage = z.infer<typeof WSSetVolumeGainMessageSchema>;
768
+
769
+ /**
770
+ * 设置音量增益消息(dB单位)
771
+ */
772
+ export const WSSetVolumeGainDbMessageSchema = WSBaseMessageSchema.extend({
773
+ type: z.literal(WSMessageType.SET_VOLUME_GAIN_DB),
774
+ data: z.object({
775
+ gainDb: z.number().min(-60).max(20),
776
+ }),
777
+ });
778
+
779
+ export type WSSetVolumeGainDbMessage = z.infer<typeof WSSetVolumeGainDbMessageSchema>;
780
+
781
+ /**
782
+ * QSO记录添加消息(服务端到客户端)
783
+ */
784
+ export const WSQSORecordAddedMessageSchema = WSBaseMessageSchema.extend({
785
+ type: z.literal(WSMessageType.QSO_RECORD_ADDED),
786
+ data: z.object({
787
+ operatorId: z.string(),
788
+ logBookId: z.string(),
789
+ qsoRecord: QSORecordSchema,
790
+ }),
791
+ });
792
+
793
+ export type WSQSORecordAddedMessage = z.infer<typeof WSQSORecordAddedMessageSchema>;
794
+
795
+ /**
796
+ * QSO记录更新消息(服务端到客户端)
797
+ */
798
+ export const WSQSORecordUpdatedMessageSchema = WSBaseMessageSchema.extend({
799
+ type: z.literal(WSMessageType.QSO_RECORD_UPDATED),
800
+ data: z.object({
801
+ operatorId: z.string(),
802
+ logBookId: z.string(),
803
+ qsoRecord: QSORecordSchema,
804
+ }),
805
+ });
806
+
807
+ export type WSQSORecordUpdatedMessage = z.infer<typeof WSQSORecordUpdatedMessageSchema>;
808
+
809
+ /**
810
+ * 日志本更新消息(服务端到客户端)
811
+ */
812
+ export const WSLogbookUpdatedMessageSchema = WSBaseMessageSchema.extend({
813
+ type: z.literal(WSMessageType.LOGBOOK_UPDATED),
814
+ data: z.object({
815
+ logBookId: z.string(),
816
+ statistics: LogBookStatisticsSchema,
817
+ }),
818
+ });
819
+
820
+ export type WSLogbookUpdatedMessage = z.infer<typeof WSLogbookUpdatedMessageSchema>;
821
+
822
+ /**
823
+ * 电台状态变化消息
824
+ */
825
+ export const WSRadioStatusChangedMessageSchema = WSBaseMessageSchema.extend({
826
+ type: z.literal(WSMessageType.RADIO_STATUS_CHANGED),
827
+ data: z.object({
828
+ connected: z.boolean(),
829
+ status: RadioConnectionStatusSchema, // 精细化连接状态(必填)
830
+ radioInfo: RadioInfoSchema.nullable(), // 电台信息(连接时有值,断开时为null)
831
+ radioConfig: HamlibConfigSchema.optional(), // 电台配置(保持当前配置)
832
+ reason: z.string().optional(),
833
+ message: z.string().optional(), // 用户友好的消息
834
+ recommendation: z.string().optional(), // 操作建议
835
+ reconnectProgress: ReconnectProgressSchema.optional(), // 重连进度
836
+ connectionHealth: z.object({
837
+ connectionHealthy: z.boolean(),
838
+ }).optional(),
839
+ coreCapabilities: CoreRadioCapabilitiesSchema.optional(),
840
+ coreCapabilityDiagnostics: CoreCapabilityDiagnosticsSchema.optional(),
841
+ meterCapabilities: MeterCapabilitiesSchema.optional(), // 电台数值表能力(connected=true 时应始终携带)
842
+ /** @deprecated Tuner capability is now in radioCapabilityList event. Kept for backward compat. */
843
+ tunerCapabilities: TunerCapabilitiesSchema.optional(),
844
+ }),
845
+ });
846
+
847
+ export type WSRadioStatusChangedMessage = z.infer<typeof WSRadioStatusChangedMessageSchema>;
848
+
849
+
850
+ /**
851
+ * 电台错误事件数据(专用错误频道)
852
+ * 包含完整的错误信息、解决建议、Profile 关联等
853
+ */
854
+ export const RadioErrorEventDataSchema = z.object({
855
+ /** 技术错误消息(精简摘要;完整原始内容在 context.rawHamlibTrace) */
856
+ message: z.string(),
857
+ /** 用户友好的错误消息(兜底英文文案) */
858
+ userMessage: z.string(),
859
+ /** 前端 i18n 翻译键,格式如 'radio:error.xxx';存在时前端优先使用 t() */
860
+ userMessageKey: z.string().optional(),
861
+ /** i18n 参数(与 userMessageKey 配合) */
862
+ userMessageParams: z.record(z.union([z.string(), z.number()])).optional(),
863
+ /** 解决建议列表 */
864
+ suggestions: z.array(z.string()).default([]),
865
+ /** 错误代码(RadioErrorCode) */
866
+ code: z.string().optional(),
867
+ /** 严重程度 */
868
+ severity: z.enum(['info', 'warning', 'error', 'critical']).default('error'),
869
+ /** ISO 时间戳(服务端生成) */
870
+ timestamp: z.string(),
871
+ /** 错误堆栈(仅非生产环境) */
872
+ stack: z.string().optional(),
873
+ /** 错误上下文 */
874
+ context: z.record(z.unknown()).optional(),
875
+ /** 连接健康状态 */
876
+ connectionHealth: z.object({
877
+ connectionHealthy: z.boolean(),
878
+ }).optional(),
879
+ /** 关联的 Profile ID */
880
+ profileId: z.string().nullable(),
881
+ /** 关联的 Profile 名称 */
882
+ profileName: z.string().nullable(),
883
+ });
884
+
885
+ export type RadioErrorEventData = z.infer<typeof RadioErrorEventDataSchema>;
886
+
887
+ /**
888
+ * 电台错误消息
889
+ */
890
+ export const WSRadioErrorMessageSchema = WSBaseMessageSchema.extend({
891
+ type: z.literal(WSMessageType.RADIO_ERROR),
892
+ data: RadioErrorEventDataSchema,
893
+ });
894
+
895
+ export type WSRadioErrorMessage = z.infer<typeof WSRadioErrorMessageSchema>;
896
+
897
+ /**
898
+ * 手动重连电台消息(客户端到服务端)
899
+ */
900
+ export const WSRadioManualReconnectMessageSchema = WSBaseMessageSchema.extend({
901
+ type: z.literal(WSMessageType.RADIO_MANUAL_RECONNECT),
902
+ data: z.object({}).optional(),
903
+ });
904
+
905
+ export type WSRadioManualReconnectMessage = z.infer<typeof WSRadioManualReconnectMessageSchema>;
906
+
907
+ /**
908
+ * 电台发射中断开连接消息
909
+ */
910
+ export const WSRadioDisconnectedDuringTransmissionMessageSchema = WSBaseMessageSchema.extend({
911
+ type: z.literal(WSMessageType.RADIO_DISCONNECTED_DURING_TRANSMISSION),
912
+ data: z.object({
913
+ reason: z.string(),
914
+ message: z.string(),
915
+ recommendation: z.string(),
916
+ }),
917
+ });
918
+
919
+ export type WSRadioDisconnectedDuringTransmissionMessage = z.infer<typeof WSRadioDisconnectedDuringTransmissionMessageSchema>;
920
+
921
+ /**
922
+ * 停止电台重连消息(客户端到服务端)
923
+ */
924
+ export const WSRadioStopReconnectMessageSchema = WSBaseMessageSchema.extend({
925
+ type: z.literal(WSMessageType.RADIO_STOP_RECONNECT),
926
+ data: z.object({}).optional(),
927
+ });
928
+
929
+ export type WSRadioStopReconnectMessage = z.infer<typeof WSRadioStopReconnectMessageSchema>;
930
+
931
+ /**
932
+ * 音频 sidecar 状态变化消息(服务端→客户端)
933
+ */
934
+ export const WSAudioSidecarStatusChangedMessageSchema = WSBaseMessageSchema.extend({
935
+ type: z.literal(WSMessageType.AUDIO_SIDECAR_STATUS_CHANGED),
936
+ data: AudioSidecarStatusPayloadSchema,
937
+ });
938
+
939
+ export type WSAudioSidecarStatusChangedMessage = z.infer<typeof WSAudioSidecarStatusChangedMessageSchema>;
940
+
941
+ /**
942
+ * 音频立即重试命令(客户端→服务端)
943
+ */
944
+ export const WSAudioRetryNowMessageSchema = WSBaseMessageSchema.extend({
945
+ type: z.literal(WSMessageType.AUDIO_RETRY_NOW),
946
+ data: z.object({}).optional(),
947
+ });
948
+
949
+ export type WSAudioRetryNowMessage = z.infer<typeof WSAudioRetryNowMessageSchema>;
950
+
951
+ /**
952
+ * 电源操作进度事件(服务端→客户端)
953
+ */
954
+ export const WSRadioPowerStateMessageSchema = WSBaseMessageSchema.extend({
955
+ type: z.literal(WSMessageType.RADIO_POWER_STATE),
956
+ data: RadioPowerStateEventSchema,
957
+ });
958
+
959
+ export type WSRadioPowerStateMessage = z.infer<typeof WSRadioPowerStateMessageSchema>;
960
+
961
+ /**
962
+ * 频率变化消息(服务端到客户端)
963
+ */
964
+ export const WSFrequencyChangedMessageSchema = WSBaseMessageSchema.extend({
965
+ type: z.literal(WSMessageType.FREQUENCY_CHANGED),
966
+ data: FrequencyStateSchema,
967
+ });
968
+
969
+ export type WSFrequencyChangedMessage = z.infer<typeof WSFrequencyChangedMessageSchema>;
970
+
971
+ export const WSSpectrumSessionStateChangedOutboundMessageSchema = WSBaseMessageSchema.extend({
972
+ type: z.literal(WSMessageType.SPECTRUM_SESSION_STATE_CHANGED),
973
+ data: SpectrumSessionStateSchema,
974
+ });
975
+
976
+ export type WSSpectrumSessionStateChangedOutboundMessage = z.infer<typeof WSSpectrumSessionStateChangedOutboundMessageSchema>;
977
+
978
+ /**
979
+ * PTT状态变化消息(服务端到客户端)
980
+ */
981
+ export const WSPTTStatusChangedMessageSchema = WSBaseMessageSchema.extend({
982
+ type: z.literal(WSMessageType.PTT_STATUS_CHANGED),
983
+ data: PTTStatusSchema,
984
+ });
985
+
986
+ export type WSPTTStatusChangedMessage = z.infer<typeof WSPTTStatusChangedMessageSchema>;
987
+
988
+ /**
989
+ * 强制停止发射消息(客户端到服务端)
990
+ * 立即停止PTT并清空音频播放队列
991
+ */
992
+ export const WSForceStopTransmissionMessageSchema = WSBaseMessageSchema.extend({
993
+ type: z.literal(WSMessageType.FORCE_STOP_TRANSMISSION),
994
+ data: z.object({}).optional(),
995
+ });
996
+
997
+ export type WSForceStopTransmissionMessage = z.infer<typeof WSForceStopTransmissionMessageSchema>;
998
+
999
+ /**
1000
+ * 从当前发射中移除单个操作员消息(客户端到服务端)
1001
+ * 移除该操作员的音频并重混音,如果是最后一个操作员则停止PTT
1002
+ */
1003
+ export const WSRemoveOperatorFromTransmissionMessageSchema = WSBaseMessageSchema.extend({
1004
+ type: z.literal(WSMessageType.REMOVE_OPERATOR_FROM_TRANSMISSION),
1005
+ data: z.object({
1006
+ operatorId: z.string(),
1007
+ }),
1008
+ });
1009
+
1010
+ export type WSRemoveOperatorFromTransmissionMessage = z.infer<typeof WSRemoveOperatorFromTransmissionMessageSchema>;
1011
+
1012
+ /**
1013
+ * 电台数值表数据消息(服务端到客户端)
1014
+ */
1015
+ export const WSMeterDataMessageSchema = WSBaseMessageSchema.extend({
1016
+ type: z.literal(WSMessageType.METER_DATA),
1017
+ data: MeterDataSchema,
1018
+ });
1019
+
1020
+ export type WSMeterDataMessage = z.infer<typeof WSMeterDataMessageSchema>;
1021
+
1022
+ /**
1023
+ * 文本消息(Toast通知)
1024
+ * 用于向客户端推送提示信息
1025
+ */
1026
+ export const WSTextMessageSchema = WSBaseMessageSchema.extend({
1027
+ type: z.literal(WSMessageType.TEXT_MESSAGE),
1028
+ data: z.object({
1029
+ title: z.string(),
1030
+ text: z.string(),
1031
+ color: z.enum(['success', 'warning', 'danger', 'default']).optional(),
1032
+ timeout: z.number().nullable().optional(), // null 表示需要手动关闭,number 表示自动关闭的毫秒数
1033
+ /** i18n key,前端有此字段时优先翻译显示 */
1034
+ key: z.string().optional(),
1035
+ /** i18n 插值参数 */
1036
+ params: z.record(z.string()).optional(),
1037
+ }),
1038
+ });
1039
+
1040
+ export type WSTextMessage = z.infer<typeof WSTextMessageSchema>;
1041
+
1042
+ // ===== 统一电台控制能力消息 =====
1043
+
1044
+ /**
1045
+ * 能力列表快照(连接成功后推送,server → client)
1046
+ */
1047
+ export const WSRadioCapabilityListMessageSchema = WSBaseMessageSchema.extend({
1048
+ type: z.literal(WSMessageType.RADIO_CAPABILITY_LIST),
1049
+ data: CapabilityListSchema,
1050
+ });
1051
+
1052
+ export type WSRadioCapabilityListMessage = z.infer<typeof WSRadioCapabilityListMessageSchema>;
1053
+
1054
+ /**
1055
+ * 单个能力值变化通知(server → client)
1056
+ */
1057
+ export const WSRadioCapabilityChangedMessageSchema = WSBaseMessageSchema.extend({
1058
+ type: z.literal(WSMessageType.RADIO_CAPABILITY_CHANGED),
1059
+ data: CapabilityStateSchema,
1060
+ });
1061
+
1062
+ export type WSRadioCapabilityChangedMessage = z.infer<typeof WSRadioCapabilityChangedMessageSchema>;
1063
+
1064
+ /**
1065
+ * 客户端写入能力值命令(client → server)
1066
+ * 权限:execute:RadioControl
1067
+ */
1068
+ export const WSWriteRadioCapabilityMessageSchema = WSBaseMessageSchema.extend({
1069
+ type: z.literal(WSMessageType.WRITE_RADIO_CAPABILITY),
1070
+ data: WriteCapabilityPayloadSchema,
1071
+ });
1072
+
1073
+ export type WSWriteRadioCapabilityMessage = z.infer<typeof WSWriteRadioCapabilityMessageSchema>;
1074
+
1075
+ // ===== 认证相关消息 =====
1076
+
1077
+ /**
1078
+ * 服务端要求认证(连接建立后发送)
1079
+ */
1080
+ export const WSAuthRequiredMessageSchema = WSBaseMessageSchema.extend({
1081
+ type: z.literal(WSMessageType.AUTH_REQUIRED),
1082
+ data: z.object({
1083
+ allowPublicViewing: z.boolean(),
1084
+ }),
1085
+ });
1086
+
1087
+ export type WSAuthRequiredMessage = z.infer<typeof WSAuthRequiredMessageSchema>;
1088
+
1089
+ /**
1090
+ * 客户端发送 JWT 进行认证(登录或权限升级)
1091
+ */
1092
+ export const WSAuthTokenMessageSchema = WSBaseMessageSchema.extend({
1093
+ type: z.literal(WSMessageType.AUTH_TOKEN),
1094
+ data: z.object({
1095
+ jwt: z.string(),
1096
+ }),
1097
+ });
1098
+
1099
+ export type WSAuthTokenMessage = z.infer<typeof WSAuthTokenMessageSchema>;
1100
+
1101
+ /**
1102
+ * 客户端选择公开观察者模式
1103
+ */
1104
+ export const WSAuthPublicViewerMessageSchema = WSBaseMessageSchema.extend({
1105
+ type: z.literal(WSMessageType.AUTH_PUBLIC_VIEWER),
1106
+ data: z.object({}).optional(),
1107
+ });
1108
+
1109
+ export type WSAuthPublicViewerMessage = z.infer<typeof WSAuthPublicViewerMessageSchema>;
1110
+
1111
+ /**
1112
+ * 认证结果(服务端到客户端)
1113
+ */
1114
+ export const WSAuthResultMessageSchema = WSBaseMessageSchema.extend({
1115
+ type: z.literal(WSMessageType.AUTH_RESULT),
1116
+ data: z.object({
1117
+ success: z.boolean(),
1118
+ role: z.nativeEnum(UserRole).optional(),
1119
+ label: z.string().optional(),
1120
+ operatorIds: z.array(z.string()).optional(),
1121
+ error: z.string().optional(),
1122
+ }),
1123
+ });
1124
+
1125
+ export type WSAuthResultMessage = z.infer<typeof WSAuthResultMessageSchema>;
1126
+
1127
+ /**
1128
+ * JWT 过期通知(服务端到客户端)
1129
+ */
1130
+ export const WSAuthExpiredMessageSchema = WSBaseMessageSchema.extend({
1131
+ type: z.literal(WSMessageType.AUTH_EXPIRED),
1132
+ data: z.object({
1133
+ reason: z.string().optional(),
1134
+ }),
1135
+ });
1136
+
1137
+ export type WSAuthExpiredMessage = z.infer<typeof WSAuthExpiredMessageSchema>;
1138
+
1139
+ export const WSPluginListMessageSchema = WSBaseMessageSchema.extend({
1140
+ type: z.literal(WSMessageType.PLUGIN_LIST),
1141
+ data: PluginSystemSnapshotSchema,
1142
+ });
1143
+ export type WSPluginListMessage = z.infer<typeof WSPluginListMessageSchema>;
1144
+
1145
+ export const WSPluginStatusChangedMessageSchema = WSBaseMessageSchema.extend({
1146
+ type: z.literal(WSMessageType.PLUGIN_STATUS_CHANGED),
1147
+ data: z.object({
1148
+ generation: z.number().int().nonnegative(),
1149
+ plugin: PluginStatusSchema,
1150
+ }),
1151
+ });
1152
+ export type WSPluginStatusChangedMessage = z.infer<typeof WSPluginStatusChangedMessageSchema>;
1153
+
1154
+ export const WSPluginDataMessageSchema = WSBaseMessageSchema.extend({
1155
+ type: z.literal(WSMessageType.PLUGIN_DATA),
1156
+ data: PluginDataPayloadSchema,
1157
+ });
1158
+ export type WSPluginDataMessage = z.infer<typeof WSPluginDataMessageSchema>;
1159
+
1160
+ export const WSPluginLogMessageSchema = WSBaseMessageSchema.extend({
1161
+ type: z.literal(WSMessageType.PLUGIN_LOG),
1162
+ data: PluginLogEntrySchema,
1163
+ });
1164
+ export type WSPluginLogMessage = z.infer<typeof WSPluginLogMessageSchema>;
1165
+
1166
+ export const WSPluginRuntimeLogMessageSchema = WSBaseMessageSchema.extend({
1167
+ type: z.literal(WSMessageType.PLUGIN_RUNTIME_LOG),
1168
+ data: PluginRuntimeLogEntrySchema,
1169
+ });
1170
+ export type WSPluginRuntimeLogMessage = z.infer<typeof WSPluginRuntimeLogMessageSchema>;
1171
+
1172
+ export const WSGetPluginRuntimeLogHistoryMessageSchema = WSBaseMessageSchema.extend({
1173
+ type: z.literal(WSMessageType.GET_PLUGIN_RUNTIME_LOG_HISTORY),
1174
+ data: z.object({
1175
+ limit: z.number().int().positive().max(5000).optional(),
1176
+ }).optional(),
1177
+ });
1178
+ export type WSGetPluginRuntimeLogHistoryMessage = z.infer<typeof WSGetPluginRuntimeLogHistoryMessageSchema>;
1179
+
1180
+ export const WSPluginRuntimeLogHistoryMessageSchema = WSBaseMessageSchema.extend({
1181
+ type: z.literal(WSMessageType.PLUGIN_RUNTIME_LOG_HISTORY),
1182
+ data: PluginRuntimeLogHistoryPayloadSchema,
1183
+ });
1184
+ export type WSPluginRuntimeLogHistoryMessage = z.infer<typeof WSPluginRuntimeLogHistoryMessageSchema>;
1185
+
1186
+ export const WSPluginUserActionMessageSchema = WSBaseMessageSchema.extend({
1187
+ type: z.literal(WSMessageType.PLUGIN_USER_ACTION),
1188
+ data: PluginUserActionPayloadSchema,
1189
+ });
1190
+ export type WSPluginUserActionMessage = z.infer<typeof WSPluginUserActionMessageSchema>;
1191
+
1192
+ // 联合所有WebSocket消息类型
1193
+ export const WSMessageSchema = z.discriminatedUnion('type', [
1194
+ WSPingMessageSchema,
1195
+ WSPongMessageSchema,
1196
+ // 服务端到客户端
1197
+ WSModeChangedMessageSchema,
1198
+ WSSlotStartMessageSchema,
1199
+ WSSubWindowMessageSchema,
1200
+ WSSlotPackUpdatedMessageSchema,
1201
+ WSSpectrumCapabilitiesMessageSchema,
1202
+ WSSubscribeSpectrumMessageSchema,
1203
+ WSSpectrumFrameMessageSchema,
1204
+ WSSpectrumSessionStateChangedMessageSchema,
1205
+ WSInvokeSpectrumControlMessageSchema,
1206
+ WSDecodeErrorMessageSchema,
1207
+ WSSystemStatusMessageSchema,
1208
+ WSClientCountChangedMessageSchema,
1209
+ WSClockStatusChangedMessageSchema,
1210
+ WSErrorMessageSchema,
1211
+ WSLogbookChangeNoticeMessageSchema,
1212
+ WSTransmissionLogMessageSchema,
1213
+
1214
+ // 客户端到服务端
1215
+ WSStartEngineMessageSchema,
1216
+ WSStopEngineMessageSchema,
1217
+ WSSetModeMessageSchema,
1218
+ WSGetStatusMessageSchema,
1219
+
1220
+ // 操作员相关消息
1221
+ WSGetOperatorsMessageSchema,
1222
+ WSOperatorsListMessageSchema,
1223
+ WSOperatorStatusUpdateMessageSchema,
1224
+ WSSetOperatorContextMessageSchema,
1225
+ WSSetOperatorRuntimeStateMessageSchema,
1226
+ WSSetOperatorRuntimeSlotContentMessageSchema,
1227
+ WSSetOperatorTransmitCyclesMessageSchema,
1228
+ WSStartOperatorMessageSchema,
1229
+ WSStopOperatorMessageSchema,
1230
+ WSOperatorRequestCallMessageSchema,
1231
+
1232
+ // 音量控制消息
1233
+ WSSetVolumeGainMessageSchema,
1234
+ WSSetVolumeGainDbMessageSchema,
1235
+
1236
+ // 通联日志消息
1237
+ WSQSORecordAddedMessageSchema,
1238
+ WSQSORecordUpdatedMessageSchema,
1239
+ WSLogbookUpdatedMessageSchema,
1240
+
1241
+ // 客户端启用操作员列表消息
1242
+ WSSetClientEnabledOperatorsMessageSchema,
1243
+ WSSetClientSelectedOperatorMessageSchema,
1244
+
1245
+ // 握手消息
1246
+ WSClientHandshakeMessageSchema,
1247
+ WSServerHandshakeCompleteMessageSchema,
1248
+ WSSlotPacksResetMessageSchema,
1249
+
1250
+ // 电台连接管理消息
1251
+ WSRadioStatusChangedMessageSchema,
1252
+ WSRadioErrorMessageSchema,
1253
+ WSRadioManualReconnectMessageSchema,
1254
+ WSRadioStopReconnectMessageSchema,
1255
+ WSRadioDisconnectedDuringTransmissionMessageSchema,
1256
+ WSRadioPowerStateMessageSchema,
1257
+
1258
+ // 音频 sidecar 消息
1259
+ WSAudioSidecarStatusChangedMessageSchema,
1260
+ WSAudioRetryNowMessageSchema,
1261
+
1262
+ // 频率管理消息
1263
+ WSFrequencyChangedMessageSchema,
1264
+
1265
+ // PTT状态管理消息
1266
+ WSPTTStatusChangedMessageSchema,
1267
+ WSForceStopTransmissionMessageSchema,
1268
+ WSRemoveOperatorFromTransmissionMessageSchema,
1269
+
1270
+ // 电台数值表消息
1271
+ WSMeterDataMessageSchema,
1272
+
1273
+ // 文本消息(Toast通知)
1274
+ WSTextMessageSchema,
1275
+
1276
+ // 认证消息
1277
+ WSAuthRequiredMessageSchema,
1278
+ WSAuthTokenMessageSchema,
1279
+ WSAuthPublicViewerMessageSchema,
1280
+ WSAuthResultMessageSchema,
1281
+ WSAuthExpiredMessageSchema,
1282
+
1283
+ // 插件系统消息
1284
+ WSPluginListMessageSchema,
1285
+ WSPluginStatusChangedMessageSchema,
1286
+ WSPluginDataMessageSchema,
1287
+ WSPluginLogMessageSchema,
1288
+ WSPluginRuntimeLogMessageSchema,
1289
+ WSGetPluginRuntimeLogHistoryMessageSchema,
1290
+ WSPluginRuntimeLogHistoryMessageSchema,
1291
+ WSPluginUserActionMessageSchema,
1292
+ ]);
1293
+
1294
+ // ===== 导出消息类型 =====
1295
+ export type WSMessage = z.infer<typeof WSMessageSchema>;
1296
+
1297
+ // 具体消息类型
1298
+ export type WSPingMessage = z.infer<typeof WSPingMessageSchema>;
1299
+ export type WSPongMessage = z.infer<typeof WSPongMessageSchema>;
1300
+
1301
+ export type WSModeChangedMessage = z.infer<typeof WSModeChangedMessageSchema>;
1302
+ export type WSSlotStartMessage = z.infer<typeof WSSlotStartMessageSchema>;
1303
+ export type WSSubWindowMessage = z.infer<typeof WSSubWindowMessageSchema>;
1304
+ export type WSSlotPackUpdatedMessage = z.infer<typeof WSSlotPackUpdatedMessageSchema>;
1305
+ export type WSSpectrumCapabilitiesMessage = z.infer<typeof WSSpectrumCapabilitiesMessageSchema>;
1306
+ export type WSSubscribeSpectrumMessage = z.infer<typeof WSSubscribeSpectrumMessageSchema>;
1307
+ export type WSSpectrumFrameMessage = z.infer<typeof WSSpectrumFrameMessageSchema>;
1308
+ export type WSSpectrumSessionStateChangedMessage = z.infer<typeof WSSpectrumSessionStateChangedMessageSchema>;
1309
+ export type WSInvokeSpectrumControlMessage = z.infer<typeof WSInvokeSpectrumControlMessageSchema>;
1310
+ export type WSDecodeErrorMessage = z.infer<typeof WSDecodeErrorMessageSchema>;
1311
+ export type WSSystemStatusMessage = z.infer<typeof WSSystemStatusMessageSchema>;
1312
+ export type WSClientCountChangedMessage = z.infer<typeof WSClientCountChangedMessageSchema>;
1313
+ export type WSClockStatusChangedMessage = z.infer<typeof WSClockStatusChangedMessageSchema>;
1314
+ export type WSErrorMessage = z.infer<typeof WSErrorMessageSchema>;
1315
+
1316
+ export type WSStartEngineMessage = z.infer<typeof WSStartEngineMessageSchema>;
1317
+ export type WSStopEngineMessage = z.infer<typeof WSStopEngineMessageSchema>;
1318
+ export type WSSetModeMessage = z.infer<typeof WSSetModeMessageSchema>;
1319
+ export type WSGetStatusMessage = z.infer<typeof WSGetStatusMessageSchema>;
1320
+
1321
+ export const TransmitRequestSchema = z.object({
1322
+ operatorId: z.string(),
1323
+ transmission: z.string(),
1324
+ /** 是否覆盖同一操作员在同一时隙的现有 TX 帧(自动重决策时为 true) */
1325
+ replaceExisting: z.boolean().optional(),
1326
+ });
1327
+
1328
+ export type TransmitRequest = z.infer<typeof TransmitRequestSchema>;
1329
+
1330
+ // ===== 前端应用事件接口 =====
1331
+
1332
+ /**
1333
+ * 发射完成事件信息
1334
+ */
1335
+ export const TransmissionCompleteInfoSchema = z.object({
1336
+ operatorId: z.string(),
1337
+ success: z.boolean(),
1338
+ duration: z.number().optional(),
1339
+ error: z.string().optional(),
1340
+ mixedWith: z.array(z.string()).optional(), // 与其他操作员混音的ID列表
1341
+ });
1342
+
1343
+ export type TransmissionCompleteInfo = z.infer<typeof TransmissionCompleteInfoSchema>;
1344
+
1345
+ /**
1346
+ * 数字无线电引擎事件接口
1347
+ * 定义了前端应用层面的事件类型,基于底层WebSocket事件
1348
+ */
1349
+ export interface DigitalRadioEngineEvents {
1350
+ // 模式和状态事件
1351
+ modeChanged: (mode: z.infer<typeof ModeDescriptorSchema>) => void;
1352
+
1353
+ // 时隙和窗口事件
1354
+ slotStart: (slotInfo: z.infer<typeof SlotInfoSchema>, lastSlotPack: z.infer<typeof SlotPackSchema> | null) => void;
1355
+ subWindow: (windowInfo: SubWindowInfo) => void;
1356
+
1357
+ // 数据更新事件
1358
+ slotPackUpdated: (slotPack: z.infer<typeof SlotPackSchema>) => void;
1359
+ slotPacksReset: (data: z.infer<typeof WSSlotPacksResetMessageSchema>['data']) => void;
1360
+ spectrumCapabilities: (data: z.infer<typeof SpectrumCapabilitiesSchema>) => void;
1361
+ spectrumFrame: (data: z.infer<typeof SpectrumFrameSchema>) => void;
1362
+ spectrumSessionStateChanged: (data: z.infer<typeof SpectrumSessionStateSchema>) => void;
1363
+
1364
+ // 发射相关事件
1365
+ requestTransmit: (request: TransmitRequest) => void;
1366
+ transmissionComplete: (info: TransmissionCompleteInfo) => void;
1367
+ transmissionLog: (data: {
1368
+ operatorId: string;
1369
+ time: string;
1370
+ message: string;
1371
+ frequency: number;
1372
+ slotStartMs: number;
1373
+ replaceExisting?: boolean;
1374
+ }) => void;
1375
+
1376
+ // 操作员事件
1377
+ operatorsList: (data: { operators: OperatorStatus[] }) => void;
1378
+ operatorStatusUpdate: (operatorStatus: OperatorStatus) => void;
1379
+
1380
+ // 错误和状态事件
1381
+ decodeError: (errorInfo: DecodeErrorInfo) => void;
1382
+ systemStatus: (status: SystemStatus) => void;
1383
+ clientCountChanged: (data: { count: number; timestamp: number }) => void;
1384
+ clockStatusChanged: (data: import('./system.schema.js').ClockStatusSummary) => void;
1385
+
1386
+ // 连接事件
1387
+ connected: () => void;
1388
+ reconnecting: (data: { attempt: number; delayMs: number }) => void;
1389
+ disconnected: () => void;
1390
+ handshakeComplete: (data: z.infer<typeof WSServerHandshakeCompleteMessageSchema>['data']) => void;
1391
+ connectionReplaced: (data: { reason: string }) => void;
1392
+ error: (error: Error) => void;
1393
+
1394
+ // 音量控制事件
1395
+ volumeGainChanged: (data: { gain: number; gainDb: number } | number) => void;
1396
+
1397
+ // 频率控制事件
1398
+ frequencyChanged: (data: FrequencyState) => void;
1399
+
1400
+ // PTT状态控制事件
1401
+ pttStatusChanged: (data: PTTStatus) => void;
1402
+
1403
+ // 电台数值表事件
1404
+ meterData: (data: MeterData) => void;
1405
+
1406
+ // QSO 日志事件
1407
+ qsoRecordAdded: (data: { operatorId: string; logBookId: string; qsoRecord: z.infer<typeof QSORecordSchema> }) => void;
1408
+ qsoRecordUpdated: (data: { operatorId: string; logBookId: string; qsoRecord: z.infer<typeof QSORecordSchema> }) => void;
1409
+ logbookUpdated: (data: { logBookId: string; statistics: z.infer<typeof LogBookStatisticsSchema>; operatorId?: string }) => void;
1410
+ textMessage: (data: z.infer<typeof WSTextMessageSchema>['data']) => void;
1411
+
1412
+ /** @deprecated Tuner state is now delivered via radioCapabilityChanged event (id='tuner_switch') */
1413
+ tunerStatusChanged: (status: z.infer<typeof TunerStatusSchema>) => void;
1414
+
1415
+ // 统一电台控制能力事件
1416
+ radioCapabilityList: (data: z.infer<typeof CapabilityListSchema>) => void;
1417
+ radioCapabilityChanged: (data: z.infer<typeof CapabilityStateSchema>) => void;
1418
+
1419
+ // 电台连接状态事件
1420
+ radioStatusChanged: (data: z.infer<typeof WSRadioStatusChangedMessageSchema>['data']) => void;
1421
+ radioError: (data: z.infer<typeof RadioErrorEventDataSchema>) => void;
1422
+ radioDisconnectedDuringTransmission: (data: z.infer<typeof WSRadioDisconnectedDuringTransmissionMessageSchema>['data']) => void;
1423
+ radioPowerState: (data: import('./radio-power.schema.js').RadioPowerStateEvent) => void;
1424
+
1425
+ // 音频 sidecar 事件
1426
+ audioSidecarStatusChanged: (data: z.infer<typeof AudioSidecarStatusPayloadSchema>) => void;
1427
+
1428
+ // Profile 管理事件
1429
+ profileChanged: (data: z.infer<typeof ProfileChangedEventSchema>) => void;
1430
+ profileListUpdated: (data: { profiles: z.infer<typeof RadioProfileSchema>[]; activeProfileId: string | null }) => void;
1431
+ realtimeSettingsChanged: (data: RealtimeSettingsResponseData) => void;
1432
+
1433
+ // 认证事件
1434
+ authRequired: (data: { allowPublicViewing: boolean }) => void;
1435
+ authResult: (data: { success: boolean; role?: UserRole; label?: string; operatorIds?: string[]; error?: string }) => void;
1436
+ authExpired: (data: { reason?: string }) => void;
1437
+
1438
+ // 语音模式事件
1439
+ voicePttLockChanged: (data: VoicePTTLock) => void;
1440
+ voiceRadioModeChanged: (data: { radioMode: string }) => void;
1441
+
1442
+ // 进程监控事件
1443
+ processSnapshot: (data: import('./process-monitor.schema.js').ProcessSnapshot) => void;
1444
+ processSnapshotHistory: (data: import('./process-monitor.schema.js').ProcessSnapshotHistory) => void;
1445
+
1446
+ // OpenWebRX SDR 事件
1447
+ openwebrxProfileSelectRequest: (data: import('./openwebrx.schema.js').OpenWebRXProfileSelectRequest) => void;
1448
+ openwebrxProfileVerifyResult: (data: import('./openwebrx.schema.js').OpenWebRXProfileVerifyResult) => void;
1449
+ openwebrxClientCount: (data: { count: number }) => void;
1450
+ openwebrxCooldownNotice: (data: { waitMs: number }) => void;
1451
+ realtimeConnectivityIssue: (data: import('./realtime.schema.js').RealtimeConnectivityIssue) => void;
1452
+
1453
+ // 插件系统事件
1454
+ pluginList: (data: PluginSystemSnapshot) => void;
1455
+ pluginStatusChanged: (data: { generation: number; plugin: PluginStatus }) => void;
1456
+ pluginData: (data: PluginDataPayload) => void;
1457
+ pluginLog: (data: PluginLogEntry) => void;
1458
+ pluginRuntimeLog: (data: PluginRuntimeLogEntry) => void;
1459
+ pluginRuntimeLogHistory: (data: PluginRuntimeLogHistoryPayload) => void;
1460
+ rigctldStatus: (data: import('./rigctld.schema.js').RigctldStatus) => void;
1461
+ pluginPagePush: (data: {
1462
+ pluginName: string;
1463
+ pageId: string;
1464
+ pageSessionId: string;
1465
+ action: string;
1466
+ data?: unknown;
1467
+ }) => void;
1468
+
1469
+ // 内部子系统间通信事件(不通过 WebSocket 广播,用于 engine emitter 内部编排)
1470
+ encodeStart: (slotInfo: z.infer<typeof SlotInfoSchema>) => void;
1471
+ transmitStart: (slotInfo: z.infer<typeof SlotInfoSchema>) => void;
1472
+ timingWarning: (data: { title: string; text: string }) => void;
1473
+ operatorSlotChanged: (data: { operatorId: string; slot: string }) => void;
1474
+ operatorSlotContentChanged: (data: { operatorId: string; slot: string; content: string }) => void;
1475
+ operatorFrequencyChanged: (data: { operatorId: string; frequency: number }) => void;
1476
+ operatorTransmitCyclesChanged: (data: { operatorId: string; transmitCycles: number[] }) => void;
1477
+ recordQSO: (data: { operatorId: string; qsoRecord: z.infer<typeof QSORecordSchema> }) => void;
1478
+ checkHasWorkedCallsign: (data: { operatorId: string; callsign: string; requestId: string }) => void;
1479
+ hasWorkedCallsignResponse: (data: { requestId: string; hasWorked: boolean }) => void;
1480
+ }