use postcard::experimental::max_size::MaxSize;
use serde::{Deserialize, Serialize};
use crate::ble::{BleState, BleStatus};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub enum ConnectionType {
Usb,
Ble,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Default)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub enum UsbState {
#[default]
Disabled,
Enabled,
Configured,
Suspended,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub struct ConnectionStatus {
pub usb: UsbState,
pub ble: BleStatus,
pub preferred: ConnectionType,
}
impl ConnectionStatus {
pub const fn new() -> Self {
Self {
usb: UsbState::Disabled,
ble: BleStatus {
profile: 0,
state: BleState::Inactive,
},
preferred: ConnectionType::Usb,
}
}
}
impl Default for ConnectionStatus {
fn default() -> Self {
Self::new()
}
}
impl ConnectionStatus {
fn usb_ready(&self) -> bool {
matches!(self.usb, UsbState::Configured | UsbState::Suspended)
}
fn ble_ready(&self) -> bool {
matches!(self.ble.state, BleState::Connected)
}
pub fn decide_active(&self) -> Option<ConnectionType> {
match (self.usb_ready(), self.ble_ready()) {
(true, false) => Some(ConnectionType::Usb),
(false, true) => Some(ConnectionType::Ble),
(true, true) => Some(self.preferred),
(false, false) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::{ConnectionStatus, ConnectionType, UsbState};
use crate::ble::{BleState, BleStatus};
fn status(usb: UsbState, ble_state: BleState, preferred: ConnectionType) -> ConnectionStatus {
ConnectionStatus {
usb,
ble: BleStatus {
profile: 0,
state: ble_state,
},
preferred,
}
}
#[test]
fn decide_active_only_usb_ready() {
let s = status(UsbState::Configured, BleState::Inactive, ConnectionType::Ble);
assert_eq!(s.decide_active(), Some(ConnectionType::Usb));
}
#[test]
fn decide_active_only_ble_ready() {
let s = status(UsbState::Disabled, BleState::Connected, ConnectionType::Usb);
assert_eq!(s.decide_active(), Some(ConnectionType::Ble));
}
#[test]
fn decide_active_both_ready_prefers_preference() {
let s = status(UsbState::Configured, BleState::Connected, ConnectionType::Ble);
assert_eq!(s.decide_active(), Some(ConnectionType::Ble));
let s = status(UsbState::Configured, BleState::Connected, ConnectionType::Usb);
assert_eq!(s.decide_active(), Some(ConnectionType::Usb));
}
#[test]
fn decide_active_neither_ready_is_none() {
let s = status(UsbState::Disabled, BleState::Inactive, ConnectionType::Usb);
assert_eq!(s.decide_active(), None);
}
#[test]
fn suspended_usb_stays_routable_for_remote_wakeup() {
let s = status(UsbState::Suspended, BleState::Advertising, ConnectionType::Ble);
assert_eq!(s.decide_active(), Some(ConnectionType::Usb));
assert!(s.usb_ready());
}
#[test]
fn suspended_usb_with_connected_ble_prefers_preference() {
let s = status(UsbState::Suspended, BleState::Connected, ConnectionType::Usb);
assert_eq!(s.decide_active(), Some(ConnectionType::Usb));
assert!(s.usb_ready());
assert!(s.ble_ready());
let s = status(UsbState::Suspended, BleState::Connected, ConnectionType::Ble);
assert_eq!(s.decide_active(), Some(ConnectionType::Ble));
assert!(s.usb_ready());
assert!(s.ble_ready());
}
}