use crate::{NetworkError, errors::FSError, shared::*};
use alloc::{
boxed::Box,
rc::Rc,
string::{String, ToString},
vec::Vec,
};
use core::{cell::RefCell, marker::PhantomData, ops::ControlFlow, str};
use embedded_hal_bus::spi::ExclusiveDevice;
use embedded_io::Read;
use embedded_sdmmc::{
LfnBuffer, Mode, RawDirectory, RawFile, RawVolume, SdCard, ShortFileName, VolumeIdx,
VolumeManager, filesystem::ToShortFileName,
};
use esp_bootloader_esp_idf::{
ota::Ota,
partitions::{AppPartitionSubType, DataPartitionSubType, PartitionType, read_partition_table},
};
use esp_hal::{
Blocking, delay::Delay, gpio::Output, rng::Rng, spi::master::Spi, uart::Uart,
usb::usb_serial_jtag::UsbSerialJtag,
};
use esp_storage::{FlashStorage, FlashStorageError};
use firefly_types::Encode;
type SdSpi = ExclusiveDevice<Spi<'static, Blocking>, Output<'static>, Delay>;
type SD = SdCard<SdSpi, Delay>;
type VM = VolumeManager<SD, FakeTimesource, 48, 12, 1>;
pub struct DeviceImpl<'a> {
delay: Delay,
volume: RawVolume,
vm: Rc<RefCell<VM>>,
io_uart: Uart<'a, Blocking>,
usb_serial: UsbSerialJtag<'a, Blocking>,
flash: FlashStorage<'a>,
addr: Addr,
rng: Rng,
start: esp_hal::time::Instant,
_life: &'a PhantomData<()>,
}
impl<'a> DeviceImpl<'a> {
pub fn new(
sd_spi: SdSpi,
io_uart: Uart<'a, Blocking>,
usb_serial: UsbSerialJtag<'a, Blocking>,
flash: FlashStorage<'a>,
) -> Result<Self, NetworkError> {
let sdcard = SdCard::new(sd_spi, Delay::new());
let volume_manager: VM = VolumeManager::new_with_limits(sdcard, FakeTimesource {}, 5000);
let volume = match volume_manager.open_volume(VolumeIdx(0)) {
Ok(volume) => volume,
Err(err) => {
return Err(NetworkError::OwnedError(alloc::format!(
"failed to open SD card volume 0: {err:?}"
)));
}
};
let volume = volume.to_raw_volume();
let mut device = Self {
delay: Delay::new(),
volume,
vm: Rc::new(RefCell::new(volume_manager)),
io_uart,
usb_serial,
addr: Default::default(),
rng: Rng::new(),
flash,
start: esp_hal::time::Instant::now(),
_life: &PhantomData,
};
let req = firefly_types::spi::Request::NetLocalAddr;
let raw = device.io_transfer(req)?;
let resp = device.io_decode(&raw)?;
device.addr = match resp {
firefly_types::spi::Response::NetLocalAddr(addr) => addr,
_ => return Err(NetworkError::UnexpectedResp),
};
Ok(device)
}
fn log(&mut self, msg: &str) {
let msg = firefly_types::serial::Response::Log(msg.to_string());
let raw = msg.encode_vec().unwrap();
send_to_serial(&mut self.usb_serial, &raw);
}
pub fn alloc_psram(&self, size: usize) -> Vec<u8, esp_alloc::ExternalMemory> {
Vec::with_capacity_in(size, esp_alloc::ExternalMemory)
}
pub fn get_io_chip_info(&mut self) -> Option<((u8, u8, u8), u8)> {
let req = firefly_types::spi::Request::FirmwareInfo;
let raw = self.io_transfer(req).ok()?;
let resp = self.io_decode(&raw).ok()?;
let firefly_types::spi::Response::FirmwareInfo { version, partition } = resp else {
return None;
};
Some((version, partition))
}
}
fn open_dir(manager: &mut VM, dir: RawDirectory, name: &str) -> Result<RawDirectory, FSError> {
let short_name = get_short_name(manager, dir, name)?;
Ok(manager.open_dir(dir, short_name)?)
}
fn get_short_name(manager: &VM, dir: RawDirectory, name: &str) -> Result<ShortFileName, FSError> {
if let Ok(short_name) = name.to_short_filename() {
return Ok(short_name);
}
let mut result = None;
let mut buf = [0u8; 64];
let mut lfnb = LfnBuffer::new(&mut buf);
manager.iterate_dir_lfn(dir, &mut lfnb, |entry, long_name| {
let Some(long_name) = long_name else {
return ControlFlow::Continue(());
};
if long_name.trim_ascii() == name {
result = Some(entry.name);
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
})?;
let Some(file_name) = result else {
return Err(FSError::NotFound);
};
Ok(file_name)
}
fn open_file(
manager: &VM,
dir: RawDirectory,
file_name: &str,
mode: Mode,
) -> Result<RawFile, FSError> {
let short_name = match get_short_name(manager, dir, file_name) {
Ok(short_name) => short_name,
Err(err) => {
_ = manager.close_dir(dir);
return Err(err);
}
};
let res = manager.open_file_in_dir(dir, short_name, mode);
let file = res?;
Ok(file)
}
impl<'a> Device for DeviceImpl<'a> {
type Dir = DirImpl;
fn now(&self) -> Instant {
Instant {
us: self.start.elapsed().as_micros() as u32,
}
}
fn delay(&self, d: Duration) {
let d = esp_hal::time::Duration::from_micros(d.us() as u64);
self.delay.delay(d);
}
fn read_input(&mut self) -> Option<InputState> {
use firefly_types::spi::*;
let req = Request::ReadInput;
let Ok(raw) = self.io_transfer(req) else {
return None;
};
let Ok(resp) = self.io_decode(&raw) else {
return None;
};
match resp {
Response::Input(pad, buttons) => Some(InputState {
pad: pad.map(format_pad),
buttons: !buttons,
}),
_ => None,
}
}
fn get_name(&mut self) -> Option<&'static str> {
None
}
fn log_debug<D: core::fmt::Display>(&mut self, src: &str, msg: D) {
let msg = alloc::format!("DEBUG({src}): {msg}");
self.log(&msg);
}
fn log_error<D: core::fmt::Display>(&mut self, src: &str, msg: D) {
let msg = alloc::format!("ERROR({src}): {msg}");
self.log(&msg);
}
fn random(&mut self) -> u32 {
self.rng.random()
}
fn open_dir(&mut self, path: &[&str]) -> Result<DirImpl, FSError> {
let mut manager = self.vm.borrow_mut();
let mut dir = manager.open_root_dir(self.volume)?;
for part in path {
let open_res = open_dir(&mut manager, dir, part);
_ = manager.close_dir(dir);
dir = open_res?;
}
Ok(DirImpl {
dir,
vm: self.vm.clone(),
})
}
fn has_headphones(&mut self) -> bool {
false
}
fn get_audio_buffer(&mut self) -> &mut [i16] {
&mut []
}
fn get_battery_status(&mut self) -> Option<BatteryStatus> {
Some(BatteryStatus {
voltage: 50,
connected: true,
full: false,
})
}
fn write_main_flash(&mut self, offset: u32, data: &[u8]) -> Result<(), &'static str> {
let res = self.flash.write(offset, data);
if let Err(err) = res {
let err = match err {
FlashStorageError::IoError => "flash error: IO error",
FlashStorageError::IoTimeout => "flash error: IO timeout",
FlashStorageError::CantUnlock => "flash error: can't unlock for write",
FlashStorageError::NotAligned => "flash error: not aligned",
FlashStorageError::OutOfBounds => "flash error: out of bounds write",
FlashStorageError::OtherCoreRunning => "flash error: other core running",
FlashStorageError::Other(_) => "flash error",
_ => "unknown flash error",
};
return Err(err);
}
Ok(())
}
fn write_io_flash(&mut self, offset: u32, data: &[u8]) -> NetworkResult<()> {
let req = firefly_types::spi::Request::FlashWrite(offset, data);
let resp = self.io_transfer(req)?;
let resp = self.io_decode(&resp)?;
if !matches!(resp, firefly_types::spi::Response::FlashWritten) {
return Err(NetworkError::Error("unexpected response"));
}
Ok(())
}
fn switch_main_partition(&mut self, part: u8) -> Result<(), &'static str> {
let mut buf = [0u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN];
let Ok(parts) = read_partition_table(&mut self.flash, &mut buf) else {
return Err("failed to read partition table");
};
let part_type = PartitionType::Data(DataPartitionSubType::Ota);
let Ok(ota_part) = parts.find_partition(part_type) else {
return Err("cannot read partitions");
};
let Some(ota_part) = ota_part else {
return Err("cannot find OTA data partition");
};
let ota_part = ota_part.as_flash_region(&mut self.flash);
let Ok(mut ota) = Ota::new(ota_part, 2) else {
return Err("OTA partition is invalid");
};
let part = match part {
0 => AppPartitionSubType::Factory,
1 => AppPartitionSubType::Ota0,
2 => AppPartitionSubType::Ota1,
_ => panic!(),
};
let res = ota.set_current_app_partition(part);
if res.is_err() {
return Err("failed to set OTA partition");
}
Ok(())
}
fn switch_io_partition(&mut self, part: u8) -> NetworkResult<()> {
let req = firefly_types::spi::Request::PartitionSwitch(part);
let resp = self.io_transfer(req)?;
let resp = self.io_decode(&resp)?;
if !matches!(resp, firefly_types::spi::Response::PartitionSwitched) {
return Err(NetworkError::Error("unexpected response"));
}
Ok(())
}
}
pub struct DirImpl {
vm: Rc<RefCell<VM>>,
dir: RawDirectory,
}
impl Dir for DirImpl {
type Read = FileR;
type Write = FileW;
fn open_file(&mut self, name: &str) -> Result<Self::Read, FSError> {
let manager = &self.vm.borrow();
let file = open_file(manager, self.dir, name, Mode::ReadOnly)?;
Ok(FileR {
vm: Rc::clone(&self.vm),
file,
})
}
fn create_file(&mut self, name: &str) -> Result<Self::Write, FSError> {
let manager = &self.vm.borrow();
let file = open_file(manager, self.dir, name, Mode::ReadWriteCreateOrTruncate)?;
Ok(FileW {
vm: Rc::clone(&self.vm),
file,
})
}
fn append_file(&mut self, name: &str) -> Result<Self::Write, FSError> {
let manager = &self.vm.borrow();
let file = open_file(manager, self.dir, name, Mode::ReadWriteAppend)?;
Ok(FileW {
vm: Rc::clone(&self.vm),
file,
})
}
fn get_file_size(&mut self, name: &str) -> Result<u32, FSError> {
let manager = &self.vm.borrow();
let file = open_file(manager, self.dir, name, Mode::ReadOnly)?;
let size = manager.file_length(file)?;
_ = manager.close_file(file);
Ok(size)
}
fn remove_file(&mut self, name: &str) -> Result<(), FSError> {
let manager = &self.vm.borrow();
let short_name = match get_short_name(manager, self.dir, name) {
Ok(short_name) => short_name,
Err(err) => {
return Err(err);
}
};
let res = manager.delete_entry_in_dir(self.dir, short_name);
res?;
Ok(())
}
fn create_dir(&mut self, name: &str) -> Result<(), FSError> {
let manager = &self.vm.borrow();
let dir = self.dir.to_directory(manager);
dir.make_dir_in_dir(name)?;
Ok(())
}
fn remove_dir(self) -> Result<(), FSError> {
let manager = &self.vm.borrow();
let dir = self.dir.to_directory(manager);
let mut names = Vec::new();
dir.iterate_dir(|entry| {
let name_str = entry.name.to_string();
if &name_str != "." && &name_str != ".." {
names.push(entry.name);
}
ControlFlow::Continue(())
})?;
for name in names {
dir.delete_entry_in_dir(name)?;
}
Ok(())
}
fn iter_dir<F>(&mut self, mut f: F) -> Result<(), FSError>
where
F: FnMut(crate::EntryKind, &[u8]),
{
let manager = &self.vm.borrow();
let mut buf = [0u8; 64];
let mut lfnb = LfnBuffer::new(&mut buf);
manager.iterate_dir_lfn(self.dir, &mut lfnb, |entry, long_name| {
let base_name = entry.name.base_name();
if base_name.first() == Some(&b'.') {
return ControlFlow::Continue(());
}
let name = match long_name {
Some(long_name) => long_name.trim_ascii().as_bytes(),
None => base_name,
};
let kind = if entry.attributes.is_directory() {
EntryKind::Dir
} else {
EntryKind::File
};
f(kind, name);
ControlFlow::Continue(())
})?;
Ok(())
}
}
impl Drop for DirImpl {
fn drop(&mut self) {
let manager = &self.vm.borrow();
_ = manager.close_dir(self.dir);
}
}
fn format_pad(raw: (u16, u16)) -> Pad {
use micromath::F32;
const X_MIN: u16 = 50;
const Y_MIN: u16 = 50;
const X_MAX: u16 = 2047;
const Y_MAX: u16 = 1535;
let raw_x = raw.0;
let raw_y = raw.1;
let raw_x = raw_x.saturating_sub(X_MIN);
let raw_y = raw_y.saturating_sub(Y_MIN);
let x = F32::from(raw_x * 2) / F32::from(X_MAX - X_MIN) - 1.;
let y = F32::from(raw_y * 2) / F32::from(Y_MAX - Y_MIN) - 1.;
let mut x = x * 1.40;
let mut y = y * 1.25;
let square = x.mul_add(x, y * y); if square >= 1. {
let descale = square.sqrt();
x /= descale;
y /= descale;
}
let x = f32::from(x * 1000.) as i16;
let y = f32::from(y * -1000.) as i16;
Pad { x, y }
}
pub struct FileW {
vm: Rc<RefCell<VM>>,
file: embedded_sdmmc::RawFile,
}
impl embedded_io::ErrorType for FileW {
type Error = embedded_io::ErrorKind;
}
impl embedded_io::Write for FileW {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
let manager = &self.vm.borrow();
match manager.write(self.file, buf) {
Ok(()) => Ok(buf.len()),
Err(_) => Err(embedded_io::ErrorKind::Other),
}
}
fn flush(&mut self) -> Result<(), Self::Error> {
let manager = &self.vm.borrow();
match manager.flush_file(self.file) {
Ok(()) => Ok(()),
Err(_) => Err(embedded_io::ErrorKind::Other),
}
}
}
impl Drop for FileW {
fn drop(&mut self) {
let manager = &self.vm.borrow();
_ = manager.close_file(self.file);
}
}
pub struct FileR {
vm: Rc<RefCell<VM>>,
file: embedded_sdmmc::RawFile,
}
impl embedded_io::ErrorType for FileR {
type Error = embedded_io::ErrorKind;
}
impl embedded_io::Read for FileR {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
let manager = &self.vm.borrow();
match manager.read(self.file, buf) {
Ok(size) => Ok(size),
Err(_) => Err(embedded_io::ErrorKind::Other),
}
}
}
impl embedded_io::Seek for FileR {
fn seek(&mut self, pos: embedded_io::SeekFrom) -> Result<u64, Self::Error> {
let manager = &self.vm.borrow();
use embedded_io::SeekFrom::*;
let res = match pos {
Start(n) => manager.file_seek_from_start(self.file, n as u32),
End(n) => manager.file_seek_from_end(self.file, n as u32),
Current(n) => manager.file_seek_from_current(self.file, n as i32),
};
let res = res.and_then(|_| manager.file_offset(self.file));
match res {
Ok(size) => Ok(size as u64),
Err(_) => Err(embedded_io::ErrorKind::Other),
}
}
}
impl Drop for FileR {
fn drop(&mut self) {
let manager = &self.vm.borrow();
_ = manager.close_file(self.file);
}
}
impl DeviceImpl<'_> {
fn io_transfer(&mut self, req: firefly_types::spi::Request<'_>) -> NetworkResult<Vec<u8>> {
let mut raw = req.encode_vec()?;
let Ok(size) = u8::try_from(raw.len()) else {
return Err(NetworkError::Error("request payload is too big"));
};
self.io_uart.write(&[size])?;
self.io_uart.write(&raw[..])?;
self.io_uart.read(&mut raw[..1])?;
let size = usize::from(raw[0]);
if size == 0 {
return Err(NetworkError::Error("received zero-sized message"));
}
raw.resize(size, 0);
self.io_uart.read_exact(&mut raw[..])?;
Ok(raw)
}
fn io_send(&mut self, req: firefly_types::spi::Request<'_>) -> NetworkResult<()> {
let raw = req.encode_vec()?;
let Ok(size) = u8::try_from(raw.len()) else {
return Err(NetworkError::Error("request payload is too big"));
};
self.io_uart.write(&[size])?;
self.io_uart.write(&raw[..])?;
Ok(())
}
fn io_decode<'b>(&self, raw: &'b [u8]) -> NetworkResult<firefly_types::spi::Response<'b>> {
use firefly_types::spi::Response;
if raw.is_empty() {
return Err(NetworkError::Error("buffer is empty, cannot decode"));
}
let resp = Response::decode(raw)?;
if let Response::Error(err) = resp {
return Err(NetworkError::OwnedError(err.into()));
}
Ok(resp)
}
}
pub type Addr = [u8; 6];
impl Network for DeviceImpl<'_> {
type Addr = Addr;
fn net_start(&mut self) -> NetworkResult<()> {
let req = firefly_types::spi::Request::NetStart;
let raw = self.io_transfer(req)?;
let resp = self.io_decode(&raw)?;
if resp != firefly_types::spi::Response::NetStarted {
return Err(NetworkError::UnexpectedResp);
}
Ok(())
}
fn net_stop(&mut self) -> NetworkResult<()> {
let req = firefly_types::spi::Request::NetStop;
let raw = self.io_transfer(req)?;
let resp = self.io_decode(&raw)?;
if resp != firefly_types::spi::Response::NetStopped {
return Err(NetworkError::UnexpectedResp);
}
Ok(())
}
fn net_local_addr(&self) -> Self::Addr {
self.addr
}
fn net_advertise(&mut self) -> NetworkResult<()> {
let req = firefly_types::spi::Request::NetAdvertise;
let raw = self.io_transfer(req)?;
let resp = self.io_decode(&raw)?;
if resp != firefly_types::spi::Response::NetAdvertised {
return Err(NetworkError::UnexpectedResp);
}
Ok(())
}
fn net_recv(&mut self) -> NetworkResult<Option<(Self::Addr, Box<[u8]>)>> {
let req = firefly_types::spi::Request::NetRecv;
let raw = self.io_transfer(req)?;
let resp = self.io_decode(&raw)?;
use firefly_types::spi::Response::*;
match resp {
NetIncoming(addr, msg) => {
let msg = msg.to_vec().into_boxed_slice();
Ok(Some((addr, msg)))
}
NetNoIncoming => Ok(None),
_ => Err(NetworkError::UnexpectedResp),
}
}
fn net_send(&mut self, addr: Self::Addr, data: &[u8]) -> NetworkResult<()> {
let req = firefly_types::spi::Request::NetSend(addr, data);
self.io_send(req)?;
Ok(())
}
fn net_send_status(
&mut self,
addr: Self::Addr,
) -> NetworkResult<firefly_types::spi::SendStatus> {
let req = firefly_types::spi::Request::NetSendStatus(addr);
let raw = self.io_transfer(req)?;
let resp = self.io_decode(&raw)?;
use firefly_types::spi::Response::*;
match resp {
NetSendStatus(status) => Ok(status),
_ => Err(NetworkError::UnexpectedResp),
}
}
}
impl Serial for DeviceImpl<'_> {
fn serial_start(&mut self) -> NetworkResult<()> {
Ok(())
}
fn serial_stop(&mut self) -> NetworkResult<()> {
Ok(())
}
fn serial_recv(&mut self) -> NetworkResult<Option<Box<[u8]>>> {
let mut buf = Vec::new();
while let Ok(byte) = self.usb_serial.read_byte() {
buf.push(byte);
}
if buf.is_empty() {
return Ok(None);
}
Ok(Some(buf.into_boxed_slice()))
}
fn serial_send(&mut self, data: &[u8]) -> NetworkResult<()> {
send_to_serial(&mut self.usb_serial, data);
Ok(())
}
}
fn send_to_serial(usb: &mut UsbSerialJtag<'_, Blocking>, data: &[u8]) {
let n = cobs::max_encoding_length(data.len());
let mut buf = alloc::vec![0; n];
cobs::encode(data, &mut buf);
for byte in buf {
_ = usb.write_byte_nb(byte);
}
_ = usb.write_byte_nb(0x00);
_ = usb.flush_tx_nb();
}
impl Wifi for DeviceImpl<'_> {
fn wifi_scan(&mut self) -> NetworkResult<[String; 6]> {
use firefly_types::spi::{Request, Response};
let req = Request::WifiScan;
let raw = self.io_transfer(req)?;
let resp = self.io_decode(&raw)?;
let Response::WifiScan(points) = resp else {
return Err(NetworkError::UnexpectedResp);
};
let points = points.map(|s| s.to_string());
Ok(points)
}
fn wifi_connect(&mut self, ssid: &str, pass: &str) -> NetworkResult<()> {
use firefly_types::spi::{Request, Response};
let req = Request::WifiConnect(ssid, pass);
let raw = self.io_transfer(req)?;
let resp = self.io_decode(&raw)?;
match resp {
Response::WifiConnected => Ok(()),
Response::Error(msg) => Err(NetworkError::OwnedError(msg.to_string())),
_ => Err(NetworkError::UnexpectedResp),
}
}
fn wifi_status(&mut self) -> NetworkResult<u8> {
use firefly_types::spi::{Request, Response};
let req = Request::WifiStatus;
let raw = self.io_transfer(req)?;
let resp = self.io_decode(&raw)?;
let Response::WifiStatus(status) = resp else {
return Err(NetworkError::UnexpectedResp);
};
Ok(status)
}
fn wifi_disconnect(&mut self) -> NetworkResult<()> {
use firefly_types::spi::{Request, Response};
let req = Request::WifiDisconnect;
let raw = self.io_transfer(req)?;
let resp = self.io_decode(&raw)?;
if resp != Response::WifiDisconnected {
return Err(NetworkError::UnexpectedResp);
};
Ok(())
}
fn tcp_connect(&mut self, ip: u32, port: u16) -> NetworkResult<()> {
use firefly_types::spi::{Request, Response};
let req = Request::TcpConnect(ip, port);
let raw = self.io_transfer(req)?;
let resp = self.io_decode(&raw)?;
if resp != Response::TcpConnected {
return Err(NetworkError::UnexpectedResp);
};
Ok(())
}
fn tcp_status(&mut self) -> NetworkResult<u8> {
use firefly_types::spi::{Request, Response};
let req = Request::TcpStatus;
let raw = self.io_transfer(req)?;
let resp = self.io_decode(&raw)?;
let Response::TcpStatus(status) = resp else {
return Err(NetworkError::UnexpectedResp);
};
Ok(status)
}
fn tcp_send(&mut self, data: &[u8]) -> NetworkResult<()> {
use firefly_types::spi::{Request, Response};
let req = Request::TcpSend(data);
let raw = self.io_transfer(req)?;
let resp = self.io_decode(&raw)?;
if resp != Response::TcpSent {
return Err(NetworkError::UnexpectedResp);
};
Ok(())
}
fn tcp_recv(&mut self) -> NetworkResult<Box<[u8]>> {
use firefly_types::spi::{Request, Response};
let req = Request::TcpRecv;
let raw = self.io_transfer(req)?;
let resp = self.io_decode(&raw)?;
let Response::TcpChunk(chunk) = resp else {
return Err(NetworkError::UnexpectedResp);
};
let chunk = Vec::from(chunk).into_boxed_slice();
Ok(chunk)
}
fn tcp_close(&mut self) -> NetworkResult<()> {
use firefly_types::spi::{Request, Response};
let req = Request::TcpClose;
let raw = self.io_transfer(req)?;
let resp = self.io_decode(&raw)?;
if resp != Response::TcpClosed {
return Err(NetworkError::UnexpectedResp);
};
Ok(())
}
}
struct FakeTimesource {}
impl embedded_sdmmc::TimeSource for FakeTimesource {
fn get_timestamp(&self) -> embedded_sdmmc::Timestamp {
embedded_sdmmc::Timestamp {
year_since_1970: 0,
zero_indexed_month: 0,
zero_indexed_day: 0,
hours: 0,
minutes: 0,
seconds: 0,
}
}
}