Skip to main content

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    /// Remove the directory and all its contents.
230    fn remove_dir(self) -> Result<(), FSError>;
231
232    /// Call the callback for each entry in the given directory.
233    ///
234    /// A better API would be to return an iterator
235    /// but embedded-sdmmc-rs [doesn't support it][1].
236    ///
237    /// [1]: https://github.com/rust-embedded-community/embedded-sdmmc-rs/issues/125
238    fn iter_dir<F>(&mut self, f: F) -> Result<(), FSError>
239    where
240        F: FnMut(EntryKind, &[u8]);
241}
242
243pub trait Serial {
244    fn start(&mut self) -> NetworkResult<()>;
245    fn stop(&mut self) -> NetworkResult<()>;
246    fn recv(&mut self) -> NetworkResult<Option<Box<[u8]>>>;
247    fn send(&mut self, data: &[u8]) -> NetworkResult<()>;
248}
249
250#[derive(PartialEq, Copy, Clone)]
251pub enum EntryKind {
252    Dir,
253    File,
254}
255
256#[derive(Default, Clone, Debug)]
257pub struct Pad {
258    pub x: i16,
259    pub y: i16,
260}
261
262impl From<(i16, i16)> for Pad {
263    fn from(value: (i16, i16)) -> Self {
264        Self {
265            x: value.0,
266            y: value.1,
267        }
268    }
269}
270
271impl From<Pad> for (i16, i16) {
272    fn from(value: Pad) -> Self {
273        (value.x, value.y)
274    }
275}
276
277#[derive(Default, Clone, Debug)]
278pub struct InputState {
279    pub pad: Option<Pad>,
280    pub buttons: u8,
281}
282
283impl InputState {
284    pub fn s(&self) -> bool {
285        self.buttons & 0b1 > 0
286    }
287
288    pub fn e(&self) -> bool {
289        self.buttons & 0b10 > 0
290    }
291
292    pub fn w(&self) -> bool {
293        self.buttons & 0b100 > 0
294    }
295
296    pub fn n(&self) -> bool {
297        self.buttons & 0b1000 > 0
298    }
299
300    pub fn menu(&self) -> bool {
301        self.buttons & 0b10000 > 0
302    }
303
304    #[must_use]
305    pub fn merge(&self, other: &Self) -> Self {
306        Self {
307            pad: match &self.pad {
308                Some(pad) => Some(pad.clone()),
309                None => other.pad.clone(),
310            },
311            buttons: self.buttons | other.buttons,
312        }
313    }
314}
315
316/// The battery status info.
317///
318/// Contains only stats that can be accessed from the hardware.
319/// It's responsibility of firefly-runtime to calculate State of Charge
320/// and any other metrics it might need.
321pub struct BatteryStatus {
322    /// The current voltage of the battery.
323    ///
324    /// A healthy li-ion battery voltage ranges from 3.0V fully discharged
325    /// to 4.2V fully charged at 25°C. However, the range changes with age,
326    /// temperature, and star alignment (the last one is a joke, probably).
327    ///
328    /// Also, the firefly-hal Device implementation doesn't define the units
329    /// in which voltage is returned. it can be volts, microvolts, or anything
330    /// else. The only promise is that it linearly correlates with voltage.
331    pub voltage: u16,
332
333    /// If true, the device is connected to a charger.
334    ///
335    /// It indicates that the battery is charging, unless it's full.
336    pub connected: bool,
337
338    /// If true, the device is fully charged.
339    pub full: bool,
340}
341
342// (func (param $originalPtr i32)
343//   (param $originalSize i32)
344//   (param $alignment i32)
345//   (param $newSize i32)
346//   (result i32))
347
348// sample rate
349// channels
350
351// volume
352// speed
353// play/pause
354// stop
355// play_next