use crate::protocol::{BoardEvent, Command};
use crate::transport::{
BoardError, BoardState, DecodedNotification, Notification, NotificationSource,
decode_notification,
};
pub trait BlockingTransport {
type Error;
fn subscribe(&mut self, source: NotificationSource) -> Result<(), Self::Error>;
fn unsubscribe(&mut self, _source: NotificationSource) -> Result<(), Self::Error> {
Ok(())
}
fn write_command(&mut self, command: &Command) -> Result<(), Self::Error>;
fn next_notification<'a>(
&'a mut self,
buffer: &'a mut [u8],
) -> Result<Notification<'a>, Self::Error>;
fn close(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
pub struct BlockingBoard<T> {
state: BoardState<T>,
}
impl<T> BlockingBoard<T> {
pub const fn new(transport: T) -> Self {
Self {
state: BoardState::new(transport),
}
}
pub const fn transport(&self) -> &T {
&self.state.transport
}
pub fn transport_mut(&mut self) -> &mut T {
&mut self.state.transport
}
pub fn into_transport(self) -> T {
self.state.transport
}
}
impl<T: BlockingTransport> BlockingBoard<T> {
pub fn initialize(&mut self) -> Result<(), BoardError<T::Error>> {
debug_event!(session = "blocking", "initializing board session");
self
.state
.transport
.subscribe(NotificationSource::Position)
.map_err(|error| {
warn_event!(
session = "blocking",
stage = "subscribe_position",
"board initialization failed"
);
BoardError::Transport(error)
})?;
self.send(&Command::enable_realtime_updates())?;
self
.state
.transport
.subscribe(NotificationSource::CommandResponse)
.map_err(|error| {
warn_event!(
session = "blocking",
stage = "subscribe_command_response",
"board initialization failed"
);
BoardError::Transport(error)
})?;
debug_event!(session = "blocking", "board session initialized");
Ok(())
}
pub fn send(&mut self, command: &Command) -> Result<(), BoardError<T::Error>> {
trace_event!(
session = "blocking",
command_len = command.bytes().len(),
write_kind = ?command.write_kind(),
"writing board command"
);
self
.state
.transport
.write_command(command)
.map_err(|error| {
warn_event!(session = "blocking", "board command write failed");
BoardError::Transport(error)
})
}
pub fn next_event(&mut self) -> Result<BoardEvent, BoardError<T::Error>> {
loop {
let notification = self
.state
.transport
.next_notification(&mut self.state.notification_buffer)
.map_err(|error| {
warn_event!(session = "blocking", "notification receive failed");
BoardError::Transport(error)
})?;
trace_event!(
session = "blocking",
source = ?notification.source(),
notification_len = notification.bytes().len(),
"received board notification"
);
match decode_notification(notification)? {
DecodedNotification::Event(event) => return Ok(event),
DecodedNotification::RealtimeUpdatesAcknowledged => {}
}
}
}
pub fn shutdown(&mut self) -> Result<(), BoardError<T::Error>> {
debug_event!(session = "blocking", "shutting down board session");
self
.state
.transport
.unsubscribe(NotificationSource::CommandResponse)
.map_err(|error| {
warn_event!(
session = "blocking",
stage = "unsubscribe_command_response",
"board shutdown failed"
);
BoardError::Transport(error)
})?;
self
.state
.transport
.unsubscribe(NotificationSource::Position)
.map_err(|error| {
warn_event!(
session = "blocking",
stage = "unsubscribe_position",
"board shutdown failed"
);
BoardError::Transport(error)
})?;
self.state.transport.close().map_err(|error| {
warn_event!(
session = "blocking",
stage = "close",
"board shutdown failed"
);
BoardError::Transport(error)
})?;
debug_event!(session = "blocking", "board session stopped");
Ok(())
}
}
#[cfg(test)]
mod tests {
use core::convert::Infallible;
use super::*;
use crate::protocol::{BatteryStatus, WriteKind};
struct MockTransport {
subscriptions: [Option<NotificationSource>; 2],
subscription_count: usize,
written: [u8; 35],
written_len: usize,
write_kind: Option<WriteKind>,
notification_count: usize,
}
impl MockTransport {
const fn new() -> Self {
Self {
subscriptions: [None; 2],
subscription_count: 0,
written: [0; 35],
written_len: 0,
write_kind: None,
notification_count: 0,
}
}
}
impl BlockingTransport for MockTransport {
type Error = Infallible;
fn subscribe(&mut self, source: NotificationSource) -> Result<(), Self::Error> {
self.subscriptions[self.subscription_count] = Some(source);
self.subscription_count += 1;
Ok(())
}
fn write_command(&mut self, command: &Command) -> Result<(), Self::Error> {
self.written[..command.bytes().len()].copy_from_slice(command.bytes());
self.written_len = command.bytes().len();
self.write_kind = Some(command.write_kind());
Ok(())
}
fn next_notification<'a>(
&'a mut self,
buffer: &'a mut [u8],
) -> Result<Notification<'a>, Self::Error> {
let notification: &[u8] = if self.notification_count == 0 {
&[0x23, 0x01, 0x00]
} else {
&[0x41, 0x03, 0x0c, 0x01, 81]
};
self.notification_count += 1;
buffer[..notification.len()].copy_from_slice(notification);
Ok(Notification::new(
NotificationSource::CommandResponse,
&buffer[..notification.len()],
))
}
}
#[test]
fn initializes_and_decodes_events_without_allocating() {
let mut board = BlockingBoard::new(MockTransport::new());
board.initialize().unwrap();
assert_eq!(
board.transport().subscriptions,
[
Some(NotificationSource::Position),
Some(NotificationSource::CommandResponse),
]
);
assert_eq!(
&board.transport().written[..board.transport().written_len],
Command::enable_realtime_updates().bytes()
);
assert_eq!(board.transport().write_kind, Some(WriteKind::WithResponse));
assert_eq!(
board.next_event().unwrap(),
BoardEvent::BatteryStatus(BatteryStatus {
charging: true,
percentage: 81,
})
);
}
}