1use alloc::string::String;
4use alloc::vec::Vec;
5use core::fmt;
6use core::str::FromStr;
7
8use serde::{Deserialize, Serialize};
9
10pub type IrCode = u32;
13
14#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
17pub enum Frame {
18 Request(Request),
19 Response(Response),
20 Event(Event),
21 Log(Log),
22}
23
24#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
25pub struct Request {
26 pub id: u32,
28 pub command: Command,
29}
30
31#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
32pub enum Command {
33 Ping,
34 GetInfo,
35 GetStatus,
36 ListKeymap {
38 offset: u16,
39 },
40 SetMapping(Mapping),
42 RemoveMapping {
43 ir: IrCode,
44 },
45 ClearKeymap,
47 ResetKeymap,
49 Learn {
53 key: HidKey,
54 timeout_ms: u32,
55 },
56 CancelLearn,
57 SendHid {
59 key: HidKey,
60 },
61 SendIr {
63 code: IrCode,
64 },
65 SetMonitor {
67 enabled: bool,
68 },
69 GetBuildInfo,
71}
72
73#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
74pub struct Response {
75 pub id: u32,
76 pub result: Result<Reply, String>,
77}
78
79#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
80pub enum Reply {
81 Ok,
83 Pong,
84 Info(DeviceInfo),
85 Status(Status),
86 Keymap(KeymapPage),
87 BuildInfo(BuildInfo),
88}
89
90#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
92pub enum Event {
93 IrReceived {
95 code: IrCode,
97 repeat: bool,
98 key: Option<HidKey>,
100 },
101 Learned(Mapping),
102 LearnTimedOut,
103 BleConnection {
104 connected: bool,
105 },
106 LearnFailed {
108 reason: String,
109 },
110}
111
112#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
113pub struct Log {
114 pub level: LogLevel,
115 pub message: String,
117}
118
119#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
120pub enum LogLevel {
121 Error,
122 Warn,
123 Info,
124 Debug,
125 Verbose,
126}
127
128#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
129pub struct DeviceInfo {
130 pub protocol_version: u16,
131 pub firmware_version: String,
132 pub max_mappings: u16,
133}
134
135#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
138pub struct BuildInfo {
139 pub git: String,
141 pub components: Vec<Component>,
143}
144
145#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
146pub struct Component {
147 pub name: String,
148 pub version: String,
149}
150
151#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
152pub struct Status {
153 pub uptime_ms: u64,
154 pub ble_connected: bool,
155 pub mappings: u16,
156 pub monitor: bool,
157 pub learning: Option<HidKey>,
159 pub battery: Option<Battery>,
162}
163
164#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
165pub struct Battery {
166 pub millivolts: u16,
168 pub percent: Option<u8>,
170}
171
172#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
173pub struct Mapping {
174 pub ir: IrCode,
175 pub key: HidKey,
176}
177
178#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
179pub struct KeymapPage {
180 pub offset: u16,
181 pub total: u16,
182 pub entries: Vec<Mapping>,
183}
184
185#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
191#[serde(rename_all = "kebab-case")]
192pub enum HidKey {
193 Up,
194 Down,
195 Left,
196 Right,
197 Select,
198 Back,
199 Home,
200 Menu,
201 #[serde(rename = "vol-up")]
202 VolumeUp,
203 #[serde(rename = "vol-down")]
204 VolumeDown,
205 Mute,
206 PlayPause,
207 #[serde(rename = "next")]
208 NextTrack,
209 #[serde(rename = "prev")]
210 PrevTrack,
211 Power,
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
216pub enum HidUsage {
217 Keyboard(u8),
219 Consumer(u16),
221}
222
223impl HidKey {
224 pub const ALL: [HidKey; 15] = [
225 HidKey::Up,
226 HidKey::Down,
227 HidKey::Left,
228 HidKey::Right,
229 HidKey::Select,
230 HidKey::Back,
231 HidKey::Home,
232 HidKey::Menu,
233 HidKey::VolumeUp,
234 HidKey::VolumeDown,
235 HidKey::Mute,
236 HidKey::PlayPause,
237 HidKey::NextTrack,
238 HidKey::PrevTrack,
239 HidKey::Power,
240 ];
241
242 #[must_use]
244 pub const fn name(self) -> &'static str {
245 match self {
246 HidKey::Up => "up",
247 HidKey::Down => "down",
248 HidKey::Left => "left",
249 HidKey::Right => "right",
250 HidKey::Select => "select",
251 HidKey::Back => "back",
252 HidKey::Home => "home",
253 HidKey::Menu => "menu",
254 HidKey::VolumeUp => "vol-up",
255 HidKey::VolumeDown => "vol-down",
256 HidKey::Mute => "mute",
257 HidKey::PlayPause => "play-pause",
258 HidKey::NextTrack => "next",
259 HidKey::PrevTrack => "prev",
260 HidKey::Power => "power",
261 }
262 }
263
264 #[must_use]
267 pub const fn usage(self) -> HidUsage {
268 match self {
269 HidKey::Up => HidUsage::Keyboard(0x52),
270 HidKey::Down => HidUsage::Keyboard(0x51),
271 HidKey::Left => HidUsage::Keyboard(0x50),
272 HidKey::Right => HidUsage::Keyboard(0x4F),
273 HidKey::Select => HidUsage::Keyboard(0x28),
274 HidKey::Back => HidUsage::Consumer(0x0224),
275 HidKey::Home => HidUsage::Consumer(0x0223),
276 HidKey::Menu => HidUsage::Consumer(0x0040),
277 HidKey::VolumeUp => HidUsage::Consumer(0x00E9),
278 HidKey::VolumeDown => HidUsage::Consumer(0x00EA),
279 HidKey::Mute => HidUsage::Consumer(0x00E2),
280 HidKey::PlayPause => HidUsage::Consumer(0x00CD),
281 HidKey::NextTrack => HidUsage::Consumer(0x00B5),
282 HidKey::PrevTrack => HidUsage::Consumer(0x00B6),
283 HidKey::Power => HidUsage::Consumer(0x0030),
284 }
285 }
286}
287
288impl fmt::Display for HidKey {
289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 f.write_str(self.name())
291 }
292}
293
294#[derive(Debug, Clone, PartialEq, Eq)]
295pub struct UnknownKey;
296
297impl fmt::Display for UnknownKey {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 f.write_str("unknown key name")
300 }
301}
302
303impl core::error::Error for UnknownKey {}
304
305impl FromStr for HidKey {
306 type Err = UnknownKey;
307
308 fn from_str(s: &str) -> Result<Self, Self::Err> {
310 HidKey::ALL
311 .into_iter()
312 .find(|key| {
313 let name = key.name().as_bytes();
314 name.len() == s.len()
315 && name
316 .iter()
317 .zip(s.bytes())
318 .all(|(&n, c)| n == c.to_ascii_lowercase() || (n == b'-' && c == b'_'))
319 })
320 .ok_or(UnknownKey)
321 }
322}