1use postcard::experimental::max_size::MaxSize;
4use serde::{Deserialize, Serialize};
5
6use crate::ble::{BleState, BleStatus};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
10#[cfg_attr(feature = "defmt", derive(defmt::Format))]
11#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
12#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
13pub enum ConnectionType {
14 Usb,
15 Ble,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
22#[cfg_attr(feature = "defmt", derive(defmt::Format))]
23#[derive(Default)]
24#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
25#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
26pub enum UsbState {
27 #[default]
28 Disabled,
29 Enabled,
30 Configured,
31 Suspended,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
38#[cfg_attr(feature = "defmt", derive(defmt::Format))]
39#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
40#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
41pub struct ConnectionStatus {
42 pub usb: UsbState,
43 pub ble: BleStatus,
44 pub preferred: ConnectionType,
46}
47
48impl ConnectionStatus {
49 pub const fn new() -> Self {
50 Self {
51 usb: UsbState::Disabled,
52 ble: BleStatus {
53 profile: 0,
54 state: BleState::Inactive,
55 },
56 preferred: ConnectionType::Usb,
57 }
58 }
59}
60
61impl Default for ConnectionStatus {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl ConnectionStatus {
68 fn usb_ready(&self) -> bool {
69 matches!(self.usb, UsbState::Configured | UsbState::Suspended)
70 }
71
72 fn ble_ready(&self) -> bool {
73 matches!(self.ble.state, BleState::Connected)
74 }
75
76 pub fn decide_active(&self) -> Option<ConnectionType> {
80 match (self.usb_ready(), self.ble_ready()) {
81 (true, false) => Some(ConnectionType::Usb),
82 (false, true) => Some(ConnectionType::Ble),
83 (true, true) => Some(self.preferred),
84 (false, false) => None,
85 }
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::{ConnectionStatus, ConnectionType, UsbState};
92 use crate::ble::{BleState, BleStatus};
93
94 fn status(usb: UsbState, ble_state: BleState, preferred: ConnectionType) -> ConnectionStatus {
95 ConnectionStatus {
96 usb,
97 ble: BleStatus {
98 profile: 0,
99 state: ble_state,
100 },
101 preferred,
102 }
103 }
104
105 #[test]
106 fn decide_active_only_usb_ready() {
107 let s = status(UsbState::Configured, BleState::Inactive, ConnectionType::Ble);
108 assert_eq!(s.decide_active(), Some(ConnectionType::Usb));
109 }
110
111 #[test]
112 fn decide_active_only_ble_ready() {
113 let s = status(UsbState::Disabled, BleState::Connected, ConnectionType::Usb);
114 assert_eq!(s.decide_active(), Some(ConnectionType::Ble));
115 }
116
117 #[test]
118 fn decide_active_both_ready_prefers_preference() {
119 let s = status(UsbState::Configured, BleState::Connected, ConnectionType::Ble);
120 assert_eq!(s.decide_active(), Some(ConnectionType::Ble));
121 let s = status(UsbState::Configured, BleState::Connected, ConnectionType::Usb);
122 assert_eq!(s.decide_active(), Some(ConnectionType::Usb));
123 }
124
125 #[test]
126 fn decide_active_neither_ready_is_none() {
127 let s = status(UsbState::Disabled, BleState::Inactive, ConnectionType::Usb);
128 assert_eq!(s.decide_active(), None);
129 }
130
131 #[test]
132 fn suspended_usb_stays_routable_for_remote_wakeup() {
133 let s = status(UsbState::Suspended, BleState::Advertising, ConnectionType::Ble);
134 assert_eq!(s.decide_active(), Some(ConnectionType::Usb));
135 assert!(s.usb_ready());
136 }
137
138 #[test]
139 fn suspended_usb_with_connected_ble_prefers_preference() {
140 let s = status(UsbState::Suspended, BleState::Connected, ConnectionType::Usb);
141 assert_eq!(s.decide_active(), Some(ConnectionType::Usb));
142 assert!(s.usb_ready());
143 assert!(s.ble_ready());
144
145 let s = status(UsbState::Suspended, BleState::Connected, ConnectionType::Ble);
146 assert_eq!(s.decide_active(), Some(ConnectionType::Ble));
147 assert!(s.usb_ready());
148 assert!(s.ble_ready());
149 }
150}