Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
201 lines
8.3 KiB
Rust
201 lines
8.3 KiB
Rust
#![cfg_attr(not(feature = "std"), no_std)]
|
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
|
#![warn(missing_docs)]
|
|
#![doc = include_str!("../README.md")]
|
|
// disable warning for already-stabilized features.
|
|
// Needed to pass CI, because we deny warnings.
|
|
// We don't immediately remove them to not immediately break older nightlies.
|
|
// When all features are stable, we'll remove them.
|
|
#![cfg_attr(nightly, allow(stable_features, unknown_lints))]
|
|
#![cfg_attr(nightly, feature(async_fn_in_trait, impl_trait_projections))]
|
|
#![allow(async_fn_in_trait)]
|
|
|
|
#[cfg(feature = "alloc")]
|
|
extern crate alloc;
|
|
|
|
mod impls;
|
|
|
|
pub use embedded_io::{
|
|
Error, ErrorKind, ErrorType, ReadExactError, ReadReady, SeekFrom, WriteReady,
|
|
};
|
|
|
|
/// Async reader.
|
|
///
|
|
/// This trait is the `embedded-io-async` equivalent of [`std::io::Read`].
|
|
pub trait Read: ErrorType {
|
|
/// Read some bytes from this source into the specified buffer, returning how many bytes were read.
|
|
///
|
|
/// If no bytes are currently available to read, this function waits until at least one byte is available.
|
|
///
|
|
/// If bytes are available, a non-zero amount of bytes is read to the beginning of `buf`, and the amount
|
|
/// is returned. It is not guaranteed that *all* available bytes are returned, it is possible for the
|
|
/// implementation to read an amount of bytes less than `buf.len()` while there are more bytes immediately
|
|
/// available.
|
|
///
|
|
/// If the reader is at end-of-file (EOF), `Ok(0)` is returned. There is no guarantee that a reader at EOF
|
|
/// will always be so in the future, for example a reader can stop being at EOF if another process appends
|
|
/// more bytes to the underlying file.
|
|
///
|
|
/// If `buf.len() == 0`, `read` returns without waiting, with either `Ok(0)` or an error.
|
|
/// The `Ok(0)` doesn't indicate EOF, unlike when called with a non-empty buffer.
|
|
///
|
|
/// Implementations are encouraged to make this function side-effect-free on cancel (AKA "cancel-safe"), i.e.
|
|
/// guarantee that if you cancel (drop) a `read()` future that hasn't completed yet, the stream's
|
|
/// state hasn't changed (no bytes have been read).
|
|
///
|
|
/// This is not a requirement to allow implementations that read into the user's buffer straight from
|
|
/// the hardware with e.g. DMA.
|
|
///
|
|
/// Implementations should document whether they're actually side-effect-free on cancel or not.
|
|
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error>;
|
|
|
|
/// Read the exact number of bytes required to fill `buf`.
|
|
///
|
|
/// This function calls `read()` in a loop until exactly `buf.len()` bytes have
|
|
/// been read, waiting if needed.
|
|
///
|
|
/// This function is not side-effect-free on cancel (AKA "cancel-safe"), i.e. if you cancel (drop) a returned
|
|
/// future that hasn't completed yet, some bytes might have already been read, which will get lost.
|
|
async fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), ReadExactError<Self::Error>> {
|
|
while !buf.is_empty() {
|
|
match self.read(buf).await {
|
|
Ok(0) => break,
|
|
Ok(n) => buf = &mut buf[n..],
|
|
Err(e) => return Err(ReadExactError::Other(e)),
|
|
}
|
|
}
|
|
if buf.is_empty() {
|
|
Ok(())
|
|
} else {
|
|
Err(ReadExactError::UnexpectedEof)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Async buffered reader.
|
|
///
|
|
/// This trait is the `embedded-io-async` equivalent of [`std::io::BufRead`].
|
|
pub trait BufRead: ErrorType {
|
|
/// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty.
|
|
///
|
|
/// If no bytes are currently available to read, this function waits until at least one byte is available.
|
|
///
|
|
/// If the reader is at end-of-file (EOF), an empty slice is returned. There is no guarantee that a reader at EOF
|
|
/// will always be so in the future, for example a reader can stop being at EOF if another process appends
|
|
/// more bytes to the underlying file.
|
|
async fn fill_buf(&mut self) -> Result<&[u8], Self::Error>;
|
|
|
|
/// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`.
|
|
fn consume(&mut self, amt: usize);
|
|
}
|
|
|
|
/// Async writer.
|
|
///
|
|
/// This trait is the `embedded-io-async` equivalent of [`std::io::Write`].
|
|
pub trait Write: ErrorType {
|
|
/// Write a buffer into this writer, returning how many bytes were written.
|
|
///
|
|
/// If the writer is not currently ready to accept more bytes (for example, its buffer is full),
|
|
/// this function waits until it is ready to accept least one byte.
|
|
///
|
|
/// If it's ready to accept bytes, a non-zero amount of bytes is written from the beginning of `buf`, and the amount
|
|
/// is returned. It is not guaranteed that *all* available buffer space is filled, i.e. it is possible for the
|
|
/// implementation to write an amount of bytes less than `buf.len()` while the writer continues to be
|
|
/// ready to accept more bytes immediately.
|
|
///
|
|
/// Implementations should never return `Ok(0)` when `buf.len() != 0`. Situations where the writer is not
|
|
/// able to accept more bytes and likely never will are better indicated with errors.
|
|
///
|
|
/// If `buf.len() == 0`, `write` returns without waiting, with either `Ok(0)` or an error.
|
|
/// The `Ok(0)` doesn't indicate an error.
|
|
///
|
|
/// Implementations are encouraged to make this function side-effect-free on cancel (AKA "cancel-safe"), i.e.
|
|
/// guarantee that if you cancel (drop) a `write()` future that hasn't completed yet, the stream's
|
|
/// state hasn't changed (no bytes have been written).
|
|
///
|
|
/// This is not a requirement to allow implementations that write from the user's buffer straight to
|
|
/// the hardware with e.g. DMA.
|
|
///
|
|
/// Implementations should document whether they're actually side-effect-free on cancel or not.
|
|
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error>;
|
|
|
|
/// Flush this output stream, ensuring that all intermediately buffered contents reach their destination.
|
|
async fn flush(&mut self) -> Result<(), Self::Error> {
|
|
Ok(())
|
|
}
|
|
|
|
/// Write an entire buffer into this writer.
|
|
///
|
|
/// This function calls `write()` in a loop until exactly `buf.len()` bytes have
|
|
/// been written, waiting if needed.
|
|
///
|
|
/// This function is not side-effect-free on cancel (AKA "cancel-safe"), i.e. if you cancel (drop) a returned
|
|
/// future that hasn't completed yet, some bytes might have already been written.
|
|
async fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
|
|
let mut buf = buf;
|
|
while !buf.is_empty() {
|
|
match self.write(buf).await {
|
|
Ok(0) => panic!("write() returned Ok(0)"),
|
|
Ok(n) => buf = &buf[n..],
|
|
Err(e) => return Err(e),
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Async seek within streams.
|
|
///
|
|
/// This trait is the `embedded-io-async` equivalent of [`std::io::Seek`].
|
|
pub trait Seek: ErrorType {
|
|
/// Seek to an offset, in bytes, in a stream.
|
|
async fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error>;
|
|
|
|
/// Rewind to the beginning of a stream.
|
|
async fn rewind(&mut self) -> Result<(), Self::Error> {
|
|
self.seek(SeekFrom::Start(0)).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns the current seek position from the start of the stream.
|
|
async fn stream_position(&mut self) -> Result<u64, Self::Error> {
|
|
self.seek(SeekFrom::Current(0)).await
|
|
}
|
|
}
|
|
|
|
impl<T: ?Sized + Read> Read for &mut T {
|
|
#[inline]
|
|
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
|
T::read(self, buf).await
|
|
}
|
|
}
|
|
|
|
impl<T: ?Sized + BufRead> BufRead for &mut T {
|
|
async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
|
|
T::fill_buf(self).await
|
|
}
|
|
|
|
fn consume(&mut self, amt: usize) {
|
|
T::consume(self, amt);
|
|
}
|
|
}
|
|
|
|
impl<T: ?Sized + Write> Write for &mut T {
|
|
#[inline]
|
|
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
|
T::write(self, buf).await
|
|
}
|
|
|
|
#[inline]
|
|
async fn flush(&mut self) -> Result<(), Self::Error> {
|
|
T::flush(self).await
|
|
}
|
|
}
|
|
|
|
impl<T: ?Sized + Seek> Seek for &mut T {
|
|
#[inline]
|
|
async fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error> {
|
|
T::seek(self, pos).await
|
|
}
|
|
}
|