firefly_hal/shared.rs
1use crate::errors::*;
2use alloc::boxed::Box;
3use alloc::string::String;
4use core::fmt::Display;
5use core::ops::AddAssign;
6use core::ops::Sub;
7use core::ops::SubAssign;
8use firefly_types::spi::SendStatus;
9
10pub const SAMPLE_RATE: u32 = 44_100;
11
12/// A moment in time. Obtained from [Device::now].
13#[derive(Copy, Clone)]
14pub struct Instant {
15 pub us: u32,
16}
17
18impl Sub for Instant {
19 type Output = Duration;
20
21 fn sub(self, rhs: Self) -> Duration {
22 Duration {
23 us: self.us.saturating_sub(rhs.us),
24 }
25 }
26}
27
28/// Difference between two [Instant]'s. Used by [Device::delay].
29#[derive(PartialEq, PartialOrd, Copy, Clone)]
30pub struct Duration {
31 pub(crate) us: u32,
32}
33
34impl Duration {
35 /// Given the desired frames per second, get the duration of a single frame.
36 pub const fn from_fps(fps: u32) -> Self {
37 Self {
38 us: 1_000_000 / fps,
39 }
40 }
41
42 pub const fn from_s(s: u32) -> Self {
43 Self { us: s * 1_000_000 }
44 }
45
46 pub const fn from_ms(ms: u32) -> Self {
47 Self { us: ms * 1000 }
48 }
49
50 pub const fn from_us(us: u32) -> Self {
51 Self { us }
52 }
53
54 pub const fn s(&self) -> u32 {
55 self.us / 1_000_000
56 }
57
58 pub const fn ms(&self) -> u32 {
59 self.us / 1000
60 }
61
62 pub const fn us(&self) -> u32 {
63 self.us
64 }
65
66 pub const fn ns(&self) -> u32 {
67 self.us.saturating_mul(1000)
68 }
69}
70
71impl Sub for Duration {
72 type Output = Self;
73
74 fn sub(self, rhs: Self) -> Self {
75 Self {
76 us: self.us - rhs.us,
77 }
78 }
79}
80
81impl AddAssign for Duration {
82 fn add_assign(&mut self, rhs: Self) {
83 self.us = self.us.saturating_add(rhs.us)
84 }
85}
86
87impl SubAssign for Duration {
88 fn sub_assign(&mut self, rhs: Self) {
89 self.us = self.us.saturating_sub(rhs.us)
90 }
91}
92
93pub trait Device: Network + Serial + Wifi {
94 type Dir: Dir;
95
96 /// The current time.
97 ///
98 /// Should be precise enough for adjusting the delay between frames.
99 ///
100 /// Usually implemented as [rtic_time.Monotonic].
101 /// May also sometimes be implemented as [rtic_monotonic.Monotonic].
102 ///
103 /// [rtic_time.Monotonic]: https://docs.rs/rtic-time/latest/rtic_time/trait.Monotonic.html
104 /// [rtic_monotonic.Monotonic]: https://docs.rs/rtic-monotonic/latest/rtic_monotonic/trait.Monotonic.html
105 fn now(&self) -> Instant;
106
107 /// Suspends the current thread for the given duration.
108 ///
109 /// Should be precise enough for adjusting the delay between frames.
110 ///
111 /// Usually implemented as [embedded_hal.DelayNs].
112 ///
113 /// [embedded_hal.DelayNs]: https://docs.rs/embedded-hal/1.0.0/embedded_hal/delay/trait.DelayNs.html
114 fn delay(&self, d: Duration);
115
116 /// Read gamepad input.
117 fn read_input(&mut self) -> Option<InputState>;
118
119 /// Log a debug message into console.
120 ///
121 /// On hosted environments, it just prints into stdout.
122 /// On embedded systems, use [defmt].
123 ///
124 /// [defmt]: https://defmt.ferrous-systems.com/introduction
125 fn log_debug<D: Display>(&mut self, src: &str, msg: D);
126
127 /// Log an error into console.
128 ///
129 /// On hosted environments, it just prints into stderr.
130 /// On embedded systems, use [defmt].
131 ///
132 /// [defmt]: https://defmt.ferrous-systems.com/introduction
133 fn log_error<D: Display>(&mut self, src: &str, msg: D);
134
135 /// Get a random number.
136 fn random(&mut self) -> u32;
137
138 fn open_dir(&mut self, path: &[&str]) -> Result<Self::Dir, FSError>;
139
140 /// Returns true if headphones are connected.
141 fn has_headphones(&mut self) -> bool;
142
143 /// Get a writable slice of free audio buffer region.
144 fn get_audio_buffer(&mut self) -> &mut [i16];
145
146 fn get_battery_status(&mut self) -> Option<BatteryStatus>;
147}
148
149pub(crate) type NetworkResult<T> = Result<T, NetworkError>;
150
151pub trait Network {
152 /// The type representing the network address. Must be unique.
153 ///
154 /// For emulator, it is IP+port. For the physical device, it is MAC address.
155 type Addr: Ord;
156
157 /// Start accepting incoming network connections from other peers.
158 fn net_start(&mut self) -> NetworkResult<()>;
159
160 /// Stop accepting incoming network connections from other peers.
161 fn net_stop(&mut self) -> NetworkResult<()>;
162
163 /// Network address of the current device as visible to the other peers.
164 ///
165 /// Used to sort all the peers, including the local one, in the same order
166 /// on all devices.
167 fn net_local_addr(&self) -> Self::Addr;
168
169 /// Broadcast device presence to all other devices nearby.
170 fn net_advertise(&mut self) -> NetworkResult<()>;
171
172 /// Get a pending message, if any. Non-blocking.
173 #[expect(clippy::type_complexity)]
174 fn net_recv(&mut self) -> NetworkResult<Option<(Self::Addr, Box<[u8]>)>>;
175
176 /// Send a raw message to the given device. Non-blocking.
177 fn net_send(&mut self, addr: Self::Addr, data: &[u8]) -> NetworkResult<()>;
178
179 /// Send a raw message to the given device. Non-blocking.
180 fn net_send_status(&mut self, addr: Self::Addr) -> NetworkResult<SendStatus>;
181}
182
183pub trait Wifi {
184 fn wifi_scan(&mut self) -> NetworkResult<[String; 6]>;
185 fn wifi_connect(&mut self, ssid: &str, pass: &str) -> NetworkResult<()>;
186 fn wifi_status(&mut self) -> NetworkResult<u8>;
187 fn wifi_disconnect(&mut self) -> NetworkResult<()>;
188 fn tcp_connect(&mut self, ip: u32, port: u16) -> NetworkResult<()>;
189 fn tcp_status(&mut self) -> NetworkResult<u8>;
190 fn tcp_send(&mut self, data: &[u8]) -> NetworkResult<()>;
191 fn tcp_recv(&mut self) -> NetworkResult<Box<[u8]>>;
192 fn tcp_close(&mut self) -> NetworkResult<()>;
193}
194
195pub trait Dir {
196 type Read: embedded_io::Read;
197 type Write: embedded_io::Write;
198
199 /// Open a file for reading.
200 ///
201 /// The file path is given as a slice of path components.
202 /// There are at least 4 components:
203 ///
204 /// 1. the first one is the root directory (either "roms" or "data"),
205 /// 2. the second is the author ID,
206 /// 3. the third is the app ID,
207 /// 4. (optional) directory names if the file is nested,
208 /// 5. and the last is file name.
209 ///
210 /// The runtime ensures that the path is relative and never goes up the tree.
211 ///
212 /// The whole filesystem abstraction (this method and the ones below)
213 /// is designed to work nicely with [embedded_sdmmc] and the stdlib filesystem.
214 ///
215 /// [embedded_sdmmc]: https://github.com/rust-embedded-community/embedded-sdmmc-rs
216 fn open_file(&mut self, name: &str) -> Result<Self::Read, FSError>;
217
218 /// Create a new file and open it for write.
219 ///
220 /// If the file already exists, it will be overwritten.
221 fn create_file(&mut self, name: &str) -> Result<Self::Write, FSError>;
222
223 /// Write data to the end of the file.
224 fn append_file(&mut self, name: &str) -> Result<Self::Write, FSError>;
225
226 /// Get file size in bytes.
227 ///
228 /// None should be returned if file not found.
229 fn get_file_size(&mut self, name: &str) -> Result<u32, FSError>;
230
231 /// Delete the given file if exists.
232 ///
233 /// Returns false only if there is an error.
234 fn remove_file(&mut self, name: &str) -> Result<(), FSError>;
235
236 /// Create a new empty sub-directory.
237 ///
238 /// Returns `DirAlreadyExists` if directory already exists.
239 fn create_dir(&mut self, name: &str) -> Result<(), FSError>;
240
241 /// Remove the directory and all its contents.
242 fn remove_dir(self) -> Result<(), FSError>;
243
244 /// Call the callback for each entry in the given directory.
245 ///
246 /// A better API would be to return an iterator
247 /// but embedded-sdmmc-rs [doesn't support it][1].
248 ///
249 /// [1]: https://github.com/rust-embedded-community/embedded-sdmmc-rs/issues/125
250 fn iter_dir<F>(&mut self, f: F) -> Result<(), FSError>
251 where
252 F: FnMut(EntryKind, &[u8]);
253}
254
255/// Access the USB serial port.
256pub trait Serial {
257 fn serial_start(&mut self) -> NetworkResult<()>;
258 fn serial_stop(&mut self) -> NetworkResult<()>;
259 fn serial_recv(&mut self) -> NetworkResult<Option<Box<[u8]>>>;
260 fn serial_send(&mut self, data: &[u8]) -> NetworkResult<()>;
261}
262
263#[derive(PartialEq, Copy, Clone)]
264pub enum EntryKind {
265 Dir,
266 File,
267}
268
269#[derive(Default, Clone, Debug)]
270pub struct Pad {
271 pub x: i16,
272 pub y: i16,
273}
274
275impl From<(i16, i16)> for Pad {
276 fn from(value: (i16, i16)) -> Self {
277 Self {
278 x: value.0,
279 y: value.1,
280 }
281 }
282}
283
284impl From<Pad> for (i16, i16) {
285 fn from(value: Pad) -> Self {
286 (value.x, value.y)
287 }
288}
289
290#[derive(Default, Clone, Debug)]
291pub struct InputState {
292 pub pad: Option<Pad>,
293 pub buttons: u8,
294}
295
296impl InputState {
297 pub fn s(&self) -> bool {
298 self.buttons & 0b1 > 0
299 }
300
301 pub fn e(&self) -> bool {
302 self.buttons & 0b10 > 0
303 }
304
305 pub fn w(&self) -> bool {
306 self.buttons & 0b100 > 0
307 }
308
309 pub fn n(&self) -> bool {
310 self.buttons & 0b1000 > 0
311 }
312
313 pub fn menu(&self) -> bool {
314 self.buttons & 0b10000 > 0
315 }
316
317 #[must_use]
318 pub fn merge(&self, other: &Self) -> Self {
319 Self {
320 pad: match &self.pad {
321 Some(pad) => Some(pad.clone()),
322 None => other.pad.clone(),
323 },
324 buttons: self.buttons | other.buttons,
325 }
326 }
327}
328
329/// The battery status info.
330///
331/// Contains only stats that can be accessed from the hardware.
332/// It's responsibility of firefly-runtime to calculate State of Charge
333/// and any other metrics it might need.
334pub struct BatteryStatus {
335 /// The current voltage of the battery.
336 ///
337 /// A healthy li-ion battery voltage ranges from 3.0V fully discharged
338 /// to 4.2V fully charged at 25°C. However, the range changes with age,
339 /// temperature, and star alignment (the last one is a joke, probably).
340 ///
341 /// Also, the firefly-hal Device implementation doesn't define the units
342 /// in which voltage is returned. it can be volts, microvolts, or anything
343 /// else. The only promise is that it linearly correlates with voltage.
344 pub voltage: u16,
345
346 /// If true, the device is connected to a charger.
347 ///
348 /// It indicates that the battery is charging, unless it's full.
349 pub connected: bool,
350
351 /// If true, the device is fully charged.
352 pub full: bool,
353}
354
355// (func (param $originalPtr i32)
356// (param $originalSize i32)
357// (param $alignment i32)
358// (param $newSize i32)
359// (result i32))
360
361// sample rate
362// channels
363
364// volume
365// speed
366// play/pause
367// stop
368// play_next