firefly_hal/
shared.rs

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