Skip to main content

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    /// Get device name override.
120    ///
121    /// Used by emulator in multiplayer to give different names
122    /// to multiple emulators running on the save vfs.
123    fn get_name(&mut self) -> Option<&'static str>;
124
125    /// Log a debug message into console.
126    ///
127    /// On hosted environments, it just prints into stdout.
128    /// On embedded systems, use [defmt].
129    ///
130    /// [defmt]: https://defmt.ferrous-systems.com/introduction
131    fn log_debug<D: Display>(&mut self, src: &str, msg: D);
132
133    /// Log an error into console.
134    ///
135    /// On hosted environments, it just prints into stderr.
136    /// On embedded systems, use [defmt].
137    ///
138    /// [defmt]: https://defmt.ferrous-systems.com/introduction
139    fn log_error<D: Display>(&mut self, src: &str, msg: D);
140
141    /// Get a random number.
142    fn random(&mut self) -> u32;
143
144    fn open_dir(&mut self, path: &[&str]) -> Result<Self::Dir, FSError>;
145
146    /// Returns true if headphones are connected.
147    fn has_headphones(&mut self) -> bool;
148
149    /// Get a writable slice of free audio buffer region.
150    fn get_audio_buffer(&mut self) -> &mut [i16];
151
152    fn get_battery_status(&mut self) -> Option<BatteryStatus>;
153}
154
155pub(crate) type NetworkResult<T> = Result<T, NetworkError>;
156
157pub trait Network {
158    /// The type representing the network address. Must be unique.
159    ///
160    /// For emulator, it is IP+port. For the physical device, it is MAC address.
161    type Addr: Ord;
162
163    /// Start accepting incoming network connections from other peers.
164    fn net_start(&mut self) -> NetworkResult<()>;
165
166    /// Stop accepting incoming network connections from other peers.
167    fn net_stop(&mut self) -> NetworkResult<()>;
168
169    /// Network address of the current device as visible to the other peers.
170    ///
171    /// Used to sort all the peers, including the local one, in the same order
172    /// on all devices.
173    fn net_local_addr(&self) -> Self::Addr;
174
175    /// Broadcast device presence to all other devices nearby.
176    fn net_advertise(&mut self) -> NetworkResult<()>;
177
178    /// Get a pending message, if any. Non-blocking.
179    #[expect(clippy::type_complexity)]
180    fn net_recv(&mut self) -> NetworkResult<Option<(Self::Addr, Box<[u8]>)>>;
181
182    /// Send a raw message to the given device. Non-blocking.
183    fn net_send(&mut self, addr: Self::Addr, data: &[u8]) -> NetworkResult<()>;
184
185    /// Send a raw message to the given device. Non-blocking.
186    fn net_send_status(&mut self, addr: Self::Addr) -> NetworkResult<SendStatus>;
187}
188
189pub trait Wifi {
190    fn wifi_scan(&mut self) -> NetworkResult<[String; 6]>;
191    fn wifi_connect(&mut self, ssid: &str, pass: &str) -> NetworkResult<()>;
192    fn wifi_status(&mut self) -> NetworkResult<u8>;
193    fn wifi_disconnect(&mut self) -> NetworkResult<()>;
194    fn tcp_connect(&mut self, ip: u32, port: u16) -> NetworkResult<()>;
195    fn tcp_status(&mut self) -> NetworkResult<u8>;
196    fn tcp_send(&mut self, data: &[u8]) -> NetworkResult<()>;
197    fn tcp_recv(&mut self) -> NetworkResult<Box<[u8]>>;
198    fn tcp_close(&mut self) -> NetworkResult<()>;
199}
200
201pub trait Dir {
202    type Read: embedded_io::Read + embedded_io::Seek;
203    type Write: embedded_io::Write;
204
205    /// Open a file for reading.
206    ///
207    /// The file path is given as a slice of path components.
208    /// There are at least 4 components:
209    ///
210    /// 1. the first one is the root directory (either "roms" or "data"),
211    /// 2. the second is the author ID,
212    /// 3. the third is the app ID,
213    /// 4. (optional) directory names if the file is nested,
214    /// 5. and the last is file name.
215    ///
216    /// The runtime ensures that the path is relative and never goes up the tree.
217    ///
218    /// The whole filesystem abstraction (this method and the ones below)
219    /// is designed to work nicely with [embedded_sdmmc] and the stdlib filesystem.
220    ///
221    /// [embedded_sdmmc]: https://github.com/rust-embedded-community/embedded-sdmmc-rs
222    fn open_file(&mut self, name: &str) -> Result<Self::Read, FSError>;
223
224    /// Create a new file and open it for write.
225    ///
226    /// If the file already exists, it will be overwritten.
227    fn create_file(&mut self, name: &str) -> Result<Self::Write, FSError>;
228
229    /// Write data to the end of the file.
230    fn append_file(&mut self, name: &str) -> Result<Self::Write, FSError>;
231
232    /// Get file size in bytes.
233    ///
234    /// None should be returned if file not found.
235    fn get_file_size(&mut self, name: &str) -> Result<u32, FSError>;
236
237    /// Delete the given file if exists.
238    ///
239    /// Returns false only if there is an error.
240    fn remove_file(&mut self, name: &str) -> Result<(), FSError>;
241
242    /// Create a new empty sub-directory.
243    ///
244    /// Returns `DirAlreadyExists` if directory already exists.
245    fn create_dir(&mut self, name: &str) -> Result<(), FSError>;
246
247    /// Remove the directory and all its contents.
248    fn remove_dir(self) -> Result<(), FSError>;
249
250    /// Call the callback for each entry in the given directory.
251    ///
252    /// A better API would be to return an iterator
253    /// but embedded-sdmmc-rs [doesn't support it][1].
254    ///
255    /// [1]: https://github.com/rust-embedded-community/embedded-sdmmc-rs/issues/125
256    fn iter_dir<F>(&mut self, f: F) -> Result<(), FSError>
257    where
258        F: FnMut(EntryKind, &[u8]);
259}
260
261/// Access the USB serial port.
262pub trait Serial {
263    fn serial_start(&mut self) -> NetworkResult<()>;
264    fn serial_stop(&mut self) -> NetworkResult<()>;
265    fn serial_recv(&mut self) -> NetworkResult<Option<Box<[u8]>>>;
266    fn serial_send(&mut self, data: &[u8]) -> NetworkResult<()>;
267}
268
269#[derive(PartialEq, Copy, Clone)]
270pub enum EntryKind {
271    Dir,
272    File,
273}
274
275#[derive(Default, Clone, Debug)]
276pub struct Pad {
277    pub x: i16,
278    pub y: i16,
279}
280
281impl From<(i16, i16)> for Pad {
282    fn from(value: (i16, i16)) -> Self {
283        Self {
284            x: value.0,
285            y: value.1,
286        }
287    }
288}
289
290impl From<Pad> for (i16, i16) {
291    fn from(value: Pad) -> Self {
292        (value.x, value.y)
293    }
294}
295
296#[derive(Default, Clone, Debug)]
297pub struct InputState {
298    pub pad: Option<Pad>,
299    pub buttons: u8,
300}
301
302impl InputState {
303    pub fn s(&self) -> bool {
304        self.buttons & 0b1 > 0
305    }
306
307    pub fn e(&self) -> bool {
308        self.buttons & 0b10 > 0
309    }
310
311    pub fn w(&self) -> bool {
312        self.buttons & 0b100 > 0
313    }
314
315    pub fn n(&self) -> bool {
316        self.buttons & 0b1000 > 0
317    }
318
319    pub fn menu(&self) -> bool {
320        self.buttons & 0b10000 > 0
321    }
322
323    pub fn rotate(&mut self) {
324        if let Some(pad) = self.pad.as_mut() {
325            pad.x = -pad.x;
326            pad.y = -pad.y;
327        };
328        let mut btns = u8::from(self.menu());
329        btns = (btns << 1) | u8::from(self.s());
330        btns = (btns << 1) | u8::from(self.e());
331        btns = (btns << 1) | u8::from(self.w());
332        btns = (btns << 1) | u8::from(self.n());
333        self.buttons = btns;
334    }
335
336    #[must_use]
337    pub fn merge(&self, other: &Self) -> Self {
338        Self {
339            pad: match &self.pad {
340                Some(pad) => Some(pad.clone()),
341                None => other.pad.clone(),
342            },
343            buttons: self.buttons | other.buttons,
344        }
345    }
346}
347
348/// The battery status info.
349///
350/// Contains only stats that can be accessed from the hardware.
351/// It's responsibility of firefly-runtime to calculate State of Charge
352/// and any other metrics it might need.
353#[derive(PartialEq, Eq, Clone, Copy)]
354pub struct BatteryStatus {
355    /// The current voltage of the battery.
356    ///
357    /// A healthy li-ion battery voltage ranges from 3.0V fully discharged
358    /// to 4.2V fully charged at 25°C. However, the range changes with age,
359    /// temperature, and star alignment (the last one is a joke, probably).
360    ///
361    /// Also, the firefly-hal Device implementation doesn't define the units
362    /// in which voltage is returned. it can be volts, microvolts, or anything
363    /// else. The only promise is that it linearly correlates with voltage.
364    pub voltage: u16,
365
366    /// If true, the device is connected to a charger.
367    ///
368    /// It indicates that the battery is charging, unless it's full.
369    pub connected: bool,
370
371    /// If true, the device is fully charged.
372    pub full: bool,
373}
374
375// (func (param $originalPtr i32)
376//   (param $originalSize i32)
377//   (param $alignment i32)
378//   (param $newSize i32)
379//   (result i32))
380
381// sample rate
382// channels
383
384// volume
385// speed
386// play/pause
387// stop
388// play_next