1use std::fmt;
9
10use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize, Serializer};
12
13use crate::ShotDetectionMode;
14use crate::{ActorState, MevoConfigEvent, ShotData};
15use crate::{ClubInfo, GameStateSnapshot, PlayerInfo};
16use crate::{
17 FlighthookConfig, GsProSection, MevoSection, MockMonitorSection, RandomClubSection,
18 WebserverSection,
19};
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct FlighthookMessage {
28 #[serde(default)]
29 pub source: String,
30 pub timestamp: DateTime<Utc>,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub raw_payload: Option<RawPayload>,
33 pub event: FlighthookEvent,
34}
35
36#[cfg(not(target_arch = "wasm32"))]
37impl FlighthookMessage {
38 pub fn new(event: impl Into<FlighthookEvent>) -> Self {
41 Self {
42 source: String::new(),
43 timestamp: Utc::now(),
44 raw_payload: None,
45 event: event.into(),
46 }
47 }
48
49 pub fn source(mut self, source: impl Into<String>) -> Self {
50 self.source = source.into();
51 self
52 }
53
54 pub fn raw(mut self, raw: RawPayload) -> Self {
55 self.raw_payload = Some(raw);
56 self
57 }
58
59 pub fn raw_binary(mut self, raw: Vec<u8>) -> Self {
60 self.raw_payload = Some(RawPayload::Binary(raw));
61 self
62 }
63}
64
65impl From<LaunchMonitorEvent> for FlighthookEvent {
70 fn from(event: LaunchMonitorEvent) -> Self {
71 FlighthookEvent::LaunchMonitor(LaunchMonitorRecv { event })
72 }
73}
74
75impl From<GameStateCommandEvent> for FlighthookEvent {
76 fn from(event: GameStateCommandEvent) -> Self {
77 FlighthookEvent::GameStateCommand(GameStateCommand { event })
78 }
79}
80
81impl From<ConfigChanged> for FlighthookEvent {
82 fn from(changed: ConfigChanged) -> Self {
83 FlighthookEvent::ConfigChanged(changed)
84 }
85}
86
87impl From<ActorState> for FlighthookEvent {
88 fn from(state: ActorState) -> Self {
89 FlighthookEvent::ActorStatus(state)
90 }
91}
92
93impl From<ConfigCommand> for FlighthookEvent {
94 fn from(cmd: ConfigCommand) -> Self {
95 FlighthookEvent::ConfigCommand(cmd)
96 }
97}
98
99impl From<ConfigOutcome> for FlighthookEvent {
100 fn from(result: ConfigOutcome) -> Self {
101 FlighthookEvent::ConfigOutcome(result)
102 }
103}
104
105impl From<AlertMessage> for FlighthookEvent {
106 fn from(alert: AlertMessage) -> Self {
107 FlighthookEvent::Alert(alert)
108 }
109}
110
111#[derive(Debug, Clone)]
120pub enum RawPayload {
121 Binary(Vec<u8>),
122 Text(String),
123}
124
125impl Serialize for RawPayload {
126 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
127 match self {
128 RawPayload::Binary(bytes) => {
129 let hex: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
130 serializer.serialize_str(&hex)
131 }
132 RawPayload::Text(s) => serializer.serialize_str(s),
133 }
134 }
135}
136
137impl<'de> Deserialize<'de> for RawPayload {
138 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
139 let s = String::deserialize(deserializer)?;
140 Ok(RawPayload::Text(s))
141 }
142}
143
144impl fmt::Display for RawPayload {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 match self {
147 RawPayload::Binary(bytes) => {
148 for b in bytes {
149 write!(f, "{b:02x}")?;
150 }
151 Ok(())
152 }
153 RawPayload::Text(s) => write!(f, "{s}"),
154 }
155 }
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
164#[serde(tag = "kind", rename_all = "snake_case")]
165pub enum FlighthookEvent {
166 LaunchMonitor(LaunchMonitorRecv),
168 ConfigChanged(ConfigChanged),
170 GameStateCommand(GameStateCommand),
172 GameStateSnapshot(GameStateSnapshot),
174 UserData(UserDataMessage),
176 ActorStatus(ActorState),
178 ConfigCommand(ConfigCommand),
180 ConfigOutcome(ConfigOutcome),
182 Alert(AlertMessage),
184}
185
186impl FlighthookEvent {
187 pub fn is_actor_status_with_telemetry(&self) -> bool {
190 matches!(self, FlighthookEvent::ActorStatus(state) if state.telemetry.contains_key("battery_pct"))
191 }
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct LaunchMonitorRecv {
201 pub event: LaunchMonitorEvent,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
206#[serde(tag = "type", rename_all = "snake_case")]
207pub enum LaunchMonitorEvent {
208 ShotResult { shot: Box<ShotData> },
210 ReadyState { armed: bool, ball_detected: bool },
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct ConfigChanged {
222 pub config: MevoConfigEvent,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct GameStateCommand {
232 pub event: GameStateCommandEvent,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
237#[serde(tag = "type", rename_all = "snake_case")]
238pub enum GameStateCommandEvent {
239 SetPlayerInfo { player_info: PlayerInfo },
240 SetClubInfo { club_info: ClubInfo },
241 SetMode { mode: ShotDetectionMode },
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct UserDataMessage {
251 #[serde(default)]
252 pub integration_type: String,
253 #[serde(default)]
254 pub source_id: String,
255 #[serde(default)]
256 pub data: serde_json::Value,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct ConfigCommand {
270 #[serde(default, skip_serializing_if = "Option::is_none")]
273 pub request_id: Option<String>,
274 pub action: ConfigAction,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize)]
279#[serde(tag = "type", rename_all = "snake_case")]
280pub enum ConfigAction {
281 ReplaceAll {
283 config: FlighthookConfig,
284 },
285 UpsertWebserver {
287 index: String,
288 section: WebserverSection,
289 },
290 UpsertMevo {
291 index: String,
292 section: MevoSection,
293 },
294 UpsertGsPro {
295 index: String,
296 section: GsProSection,
297 },
298 UpsertMockMonitor {
299 index: String,
300 section: MockMonitorSection,
301 },
302 UpsertRandomClub {
303 index: String,
304 section: RandomClubSection,
305 },
306 Remove {
308 id: String,
309 },
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct ConfigOutcome {
320 pub request_id: String,
321 #[serde(default, skip_serializing_if = "Vec::is_empty")]
322 pub restarted: Vec<String>,
323 #[serde(default, skip_serializing_if = "Vec::is_empty")]
324 pub stopped: Vec<String>,
325 #[serde(default, skip_serializing_if = "Vec::is_empty")]
326 pub started: Vec<String>,
327}
328
329#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
335#[serde(rename_all = "snake_case")]
336pub enum AlertLevel {
337 Warn,
338 Error,
339}
340
341impl fmt::Display for AlertLevel {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343 match self {
344 AlertLevel::Warn => write!(f, "warn"),
345 AlertLevel::Error => write!(f, "error"),
346 }
347 }
348}
349
350#[derive(Debug, Clone, Serialize, Deserialize)]
353pub struct AlertMessage {
354 pub level: AlertLevel,
355 pub message: String,
356}