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