use std::net::IpAddr;
use std::sync::{Mutex, PoisonError};
use rusty_esp_core::hal::{Kv, Rng};
use rusty_esp_core::pcm::PcmFormat;
use rusty_esp_core::time::Micros;
use crate::error::{Error, Result};
use crate::radar::Presence;
use crate::{cam, mic};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Jpeg {
pub bytes: Vec<u8>,
pub width: u16,
pub height: u16,
pub timestamp: Micros,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Pcm {
pub format: PcmFormat,
pub bytes: Vec<u8>,
pub timestamp: Micros,
}
impl Pcm {
#[must_use]
pub fn frames(&self) -> usize {
let frame = self.format.sample.bytes() * usize::from(self.format.channels);
self.bytes.len().checked_div(frame).unwrap_or(0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResetReason {
PowerOn,
External,
Software,
Panic,
TaskWatchdog,
InterruptWatchdog,
Brownout,
DeepSleep,
Other,
}
impl ResetReason {
#[must_use]
pub const fn wire_tag(self) -> &'static str {
match self {
ResetReason::PowerOn => "power-on",
ResetReason::External => "external",
ResetReason::Software => "software",
ResetReason::Panic => "panic",
ResetReason::TaskWatchdog => "task-watchdog",
ResetReason::InterruptWatchdog => "interrupt-watchdog",
ResetReason::Brownout => "brownout",
ResetReason::DeepSleep => "deep-sleep",
ResetReason::Other => "other",
}
}
#[must_use]
pub const fn is_crash(self) -> bool {
matches!(
self,
ResetReason::Panic
| ResetReason::TaskWatchdog
| ResetReason::InterruptWatchdog
| ResetReason::Brownout
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BootRecord {
pub reason: ResetReason,
pub boots: u32,
pub crashes: u32,
}
pub trait Board: Send {
fn wifi_begin(&mut self, ssid: &str, psk: &str) -> Result<()>;
fn local_ip(&self) -> Option<IpAddr>;
fn cam_begin(&mut self, config: &cam::Config) -> Result<()>;
fn cam_grab(&mut self) -> Result<Option<Jpeg>>;
fn mic_begin(&mut self, config: &mic::Config) -> Result<()>;
fn mic_read(&mut self) -> Result<Option<Pcm>>;
fn millis(&self) -> u64;
fn wifi_host(&mut self, _ssid: &str, _psk: &str) -> Result<()> {
Err(Error::Missing("access-point mode"))
}
fn take_kv(&mut self) -> Option<Box<dyn Kv + Send>> {
None
}
fn advertise(
&mut self,
_hostname: &str,
_instance: &str,
_service_type: &str,
_port: u16,
_txt: &[(String, String)],
) -> Result<()> {
Ok(())
}
fn advertise_txt(&mut self, _service_type: &str, _txt: &[(String, String)]) -> Result<()> {
Ok(())
}
#[cfg(feature = "mesh")]
fn ota_sink(&mut self) -> Option<Box<dyn rusty_esp_iroh_core::ota::OtaSink + Send>> {
None
}
fn ota_running_valid(&mut self) -> Result<()> {
Ok(())
}
fn liveness_begin(&mut self, _timeout_s: u32) -> Result<()> {
Ok(())
}
fn liveness_feed(&mut self) -> Result<()> {
Ok(())
}
fn boot_record(&mut self) -> Result<Option<BootRecord>> {
Ok(None)
}
fn prepare_async(&mut self, _max_fds: usize) -> Result<()> {
Ok(())
}
fn take_rng(&mut self) -> Option<Box<dyn Rng + Send>> {
None
}
fn provision_begin(&mut self, _name: &str) -> Result<()> {
Err(Error::Missing("BLE provisioning on this board"))
}
fn provision_poll(&mut self) -> Result<Option<(String, String)>> {
Ok(None)
}
fn provision_report(&mut self, _joined: bool) -> Result<()> {
Ok(())
}
fn store_settings(&mut self, _ssid: &str, _psk: &str) -> Result<()> {
Err(Error::Missing("a settings store on this board"))
}
fn restart(&mut self) -> Result<()> {
Err(Error::Missing("a way to restart this board"))
}
fn radar_begin(&mut self, _config: &crate::radar::Config) -> Result<()> {
Err(Error::Missing("a presence sensor on this board"))
}
fn radar_read(&mut self) -> Result<Option<Presence>> {
Ok(None)
}
}
static BOARD: Mutex<Option<Box<dyn Board>>> = Mutex::new(None);
pub fn install(board: impl Board + 'static) {
*BOARD.lock().unwrap_or_else(PoisonError::into_inner) = Some(Box::new(board));
}
pub fn uninstall() {
*BOARD.lock().unwrap_or_else(PoisonError::into_inner) = None;
}
#[must_use]
pub fn installed() -> bool {
BOARD
.lock()
.unwrap_or_else(PoisonError::into_inner)
.is_some()
}
pub(crate) fn with<R>(f: impl FnOnce(&mut dyn Board) -> Result<R>) -> Result<R> {
let mut guard = BOARD.lock().unwrap_or_else(PoisonError::into_inner);
match guard.as_mut() {
Some(board) => f(board.as_mut()),
None => Err(Error::NoBoard),
}
}
#[must_use]
pub fn millis() -> u64 {
with(|b| Ok(b.millis())).unwrap_or(0)
}