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    /// 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