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