use std::error::Error;
use std::mem;
use log::{debug, info, warn};
use flipdot_core::{Address, ChunkCount, Message, Offset, Operation, Page, PageFlipStyle, SignBus, SignType, State};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VirtualSignBus<'a> {
signs: Vec<VirtualSign<'a>>,
}
impl<'a> VirtualSignBus<'a> {
pub fn new<I>(signs: I) -> Self
where
I: IntoIterator<Item = VirtualSign<'a>>,
{
VirtualSignBus {
signs: signs.into_iter().collect(),
}
}
pub fn sign(&self, index: usize) -> &VirtualSign<'a> {
&self.signs[index]
}
}
impl SignBus for VirtualSignBus<'_> {
fn process_message<'a>(&mut self, message: Message<'_>) -> Result<Option<Message<'a>>, Box<dyn Error + Send + Sync>> {
debug!("Bus message: {}", message);
for sign in &mut self.signs {
let response = sign.process_message(&message);
if let Some(response_message) = response {
debug!(" Vsign {:04X}: {}", sign.address().0, response_message);
return Ok(Some(response_message));
}
}
Ok(None)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VirtualSign<'a> {
address: Address,
flip_style: PageFlipStyle,
state: State,
pages: Vec<Page<'a>>,
pending_data: Vec<u8>,
data_chunks: u16,
width: u32,
height: u32,
sign_type: Option<SignType>,
}
impl VirtualSign<'_> {
pub fn new(address: Address, flip_style: PageFlipStyle) -> Self {
VirtualSign {
address,
flip_style,
state: State::Unconfigured,
pages: vec![],
pending_data: vec![],
data_chunks: 0,
width: 0,
height: 0,
sign_type: None,
}
}
pub fn address(&self) -> Address {
self.address
}
pub fn state(&self) -> State {
self.state
}
pub fn sign_type(&self) -> Option<SignType> {
self.sign_type
}
pub fn pages(&self) -> &[Page<'_>] {
&self.pages
}
pub fn process_message<'a>(&mut self, message: &Message<'_>) -> Option<Message<'a>> {
match *message {
Message::Hello(address) | Message::QueryState(address) if address == self.address => Some(self.query_state()),
Message::RequestOperation(address, Operation::ReceiveConfig) if address == self.address => self.receive_config(),
Message::SendData(offset, ref data) => self.send_data(offset, data.get()),
Message::DataChunksSent(chunks) => self.data_chunks_sent(chunks),
Message::RequestOperation(address, Operation::ReceivePixels) if address == self.address => self.receive_pixels(),
Message::PixelsComplete(address) if address == self.address => self.pixels_complete(),
Message::RequestOperation(address, Operation::ShowLoadedPage) if address == self.address => self.show_loaded_page(),
Message::RequestOperation(address, Operation::LoadNextPage) if address == self.address => self.load_next_page(),
Message::RequestOperation(address, Operation::StartReset) if address == self.address => Some(self.start_reset()),
Message::RequestOperation(address, Operation::FinishReset) if address == self.address => self.finish_reset(),
Message::Goodbye(address) if address == self.address => self.goodbye(),
_ => None,
}
}
fn query_state<'a>(&mut self) -> Message<'a> {
let state = self.state;
match state {
State::PageLoadInProgress => self.state = State::PageLoaded,
State::PageShowInProgress => self.state = State::PageShown,
_ => {}
};
Message::ReportState(self.address, state)
}
fn receive_config<'a>(&mut self) -> Option<Message<'a>> {
match self.state {
State::Unconfigured | State::ConfigFailed => {
self.state = State::ConfigInProgress;
Some(Message::AckOperation(self.address, Operation::ReceiveConfig))
}
_ => None,
}
}
fn send_data<'a>(&mut self, offset: Offset, data: &[u8]) -> Option<Message<'a>> {
if self.state == State::ConfigInProgress && offset == Offset(0) && data.len() == 16 {
let (kind, width, height) = match data[0] {
0x04 => ("Max3000", data[5..9].iter().sum(), data[4]),
0x08 => ("Horizon", data[7], data[5]),
_ => return None,
};
info!(
"Vsign {:04X} configuration: {} x {} {} sign",
self.address.0, width, height, kind
);
self.sign_type = SignType::from_bytes(data).ok();
match self.sign_type {
Some(sign_type) => info!("Vsign {:04X} matches known type: {:?}", self.address.0, sign_type),
None => warn!("Please report unknown configuration {:?}", data),
}
self.width = u32::from(width);
self.height = u32::from(height);
self.data_chunks += 1;
} else if self.state == State::PixelsInProgress {
if offset == Offset(0) {
self.flush_pixels();
}
self.pending_data.extend_from_slice(data);
self.data_chunks += 1;
}
None
}
fn data_chunks_sent<'a>(&mut self, chunks: ChunkCount) -> Option<Message<'a>> {
if ChunkCount(self.data_chunks) == chunks {
match self.state {
State::ConfigInProgress => self.state = State::ConfigReceived,
State::PixelsInProgress => self.state = State::PixelsReceived,
_ => {}
}
} else {
match self.state {
State::ConfigInProgress => self.state = State::ConfigFailed,
State::PixelsInProgress => self.state = State::PixelsFailed,
_ => {}
}
}
self.flush_pixels();
self.data_chunks = 0;
None
}
fn receive_pixels<'a>(&mut self) -> Option<Message<'a>> {
match self.state {
State::ConfigReceived
| State::PixelsFailed
| State::PageLoaded
| State::PageLoadInProgress
| State::PageShown
| State::PageShowInProgress
| State::ShowingPages => {
self.state = State::PixelsInProgress;
self.pages.clear();
Some(Message::AckOperation(self.address, Operation::ReceivePixels))
}
_ => None,
}
}
fn pixels_complete<'a>(&mut self) -> Option<Message<'a>> {
if self.state == State::PixelsReceived {
self.state = match self.flip_style {
PageFlipStyle::Automatic => State::ShowingPages,
PageFlipStyle::Manual => State::PageLoaded,
};
for page in &self.pages {
info!(
"Vsign {:04X} Page {} ({} x {})\n{}",
self.address.0,
page.id(),
page.width(),
page.height(),
page
);
}
}
None
}
fn show_loaded_page<'a>(&mut self) -> Option<Message<'a>> {
if self.state == State::PageLoaded {
self.state = State::PageShowInProgress;
Some(Message::AckOperation(self.address, Operation::ShowLoadedPage))
} else {
None
}
}
fn load_next_page<'a>(&mut self) -> Option<Message<'a>> {
if self.state == State::PageShown {
self.state = State::PageLoadInProgress;
Some(Message::AckOperation(self.address, Operation::LoadNextPage))
} else {
None
}
}
fn start_reset<'a>(&mut self) -> Message<'a> {
self.state = State::ReadyToReset;
Message::AckOperation(self.address, Operation::StartReset)
}
fn finish_reset<'a>(&mut self) -> Option<Message<'a>> {
if self.state == State::ReadyToReset {
self.reset();
Some(Message::AckOperation(self.address, Operation::FinishReset))
} else {
None
}
}
fn goodbye<'a>(&mut self) -> Option<Message<'a>> {
self.reset();
None
}
fn flush_pixels(&mut self) {
if !self.pending_data.is_empty() {
let data = mem::take(&mut self.pending_data);
if self.width > 0 && self.height > 0 {
let page = Page::from_bytes(self.width, self.height, data).expect("Error loading page");
self.pages.push(page);
}
}
}
fn reset(&mut self) {
self.state = State::Unconfigured;
self.pages.clear();
self.pending_data.clear();
self.data_chunks = 0;
self.width = 0;
self.height = 0;
self.sign_type = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
use flipdot_core::{Data, PageId};
use test_case::test_case;
#[test_case(PageFlipStyle::Automatic ; "automatic page flip")]
#[test_case(PageFlipStyle::Manual ; "manual page flip")]
fn normal_behavior(flip_style: PageFlipStyle) {
let mut page1 = Page::new(PageId(0), 90, 7);
for x in 0..page1.width() {
for y in 0..page1.height() {
page1.set_pixel(x, y, x % 2 == y % 2);
}
}
let mut page2 = Page::new(PageId(1), 90, 7);
for x in 0..page2.width() {
for y in 0..page2.height() {
page2.set_pixel(x, y, x % 2 != y % 2);
}
}
let mut sign = VirtualSign::new(Address(3), flip_style);
assert_eq!(Address(3), sign.address());
assert_eq!(None, sign.sign_type());
assert_eq!(0, sign.pages().len());
assert_eq!(0, sign.width);
assert_eq!(0, sign.height);
let response = sign.process_message(&Message::Hello(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::Unconfigured)), response);
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::ReceiveConfig));
assert_eq!(Some(Message::AckOperation(Address(3), Operation::ReceiveConfig)), response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::ConfigInProgress)), response);
let response = sign.process_message(&Message::SendData(
Offset(0x00),
Data::try_new(SignType::Max3000Side90x7.to_bytes()).unwrap(),
));
assert_eq!(None, response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::ConfigInProgress)), response);
let response = sign.process_message(&Message::DataChunksSent(ChunkCount(1)));
assert_eq!(None, response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::ConfigReceived)), response);
assert_eq!(Some(SignType::Max3000Side90x7), sign.sign_type());
assert_eq!(90, sign.width);
assert_eq!(7, sign.height);
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::ReceivePixels));
assert_eq!(Some(Message::AckOperation(Address(3), Operation::ReceivePixels)), response);
assert_eq!(0, sign.pages().len());
let mut chunks_sent = 0;
for (i, chunk) in page1.as_bytes().chunks(16).enumerate() {
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::PixelsInProgress)), response);
let response = sign.process_message(&Message::SendData(Offset((i * 16) as u16), Data::try_new(chunk).unwrap()));
assert_eq!(None, response);
chunks_sent += 1;
}
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::PixelsInProgress)), response);
let response = sign.process_message(&Message::DataChunksSent(ChunkCount(chunks_sent)));
assert_eq!(None, response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::PixelsReceived)), response);
let response = sign.process_message(&Message::PixelsComplete(Address(3)));
assert_eq!(None, response);
assert_eq!(&[page1], sign.pages());
let response = sign.process_message(&Message::QueryState(Address(3)));
match flip_style {
PageFlipStyle::Automatic => {
assert_eq!(Some(Message::ReportState(Address(3), State::ShowingPages)), response);
}
PageFlipStyle::Manual => {
assert_eq!(Some(Message::ReportState(Address(3), State::PageLoaded)), response);
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::ShowLoadedPage));
assert_eq!(Some(Message::AckOperation(Address(3), Operation::ShowLoadedPage)), response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::PageShowInProgress)), response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::PageShown)), response);
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::LoadNextPage));
assert_eq!(Some(Message::AckOperation(Address(3), Operation::LoadNextPage)), response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::PageLoadInProgress)), response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::PageLoaded)), response);
}
}
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::ReceivePixels));
assert_eq!(Some(Message::AckOperation(Address(3), Operation::ReceivePixels)), response);
assert_eq!(0, sign.pages().len());
let mut chunks_sent = 0;
for (i, chunk) in page2.as_bytes().chunks(16).enumerate() {
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::PixelsInProgress)), response);
let response = sign.process_message(&Message::SendData(Offset((i * 16) as u16), Data::try_new(chunk).unwrap()));
assert_eq!(None, response);
chunks_sent += 1;
}
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::PixelsInProgress)), response);
let response = sign.process_message(&Message::DataChunksSent(ChunkCount(chunks_sent)));
assert_eq!(None, response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::PixelsReceived)), response);
let response = sign.process_message(&Message::PixelsComplete(Address(3)));
assert_eq!(None, response);
assert_eq!(&[page2], sign.pages());
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::StartReset));
assert_eq!(Some(Message::AckOperation(Address(3), Operation::StartReset)), response);
let response = sign.process_message(&Message::Hello(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::ReadyToReset)), response);
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::FinishReset));
assert_eq!(Some(Message::AckOperation(Address(3), Operation::FinishReset)), response);
let response = sign.process_message(&Message::Hello(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::Unconfigured)), response);
assert_eq!(Address(3), sign.address());
assert_eq!(None, sign.sign_type());
assert_eq!(0, sign.pages().len());
assert_eq!(0, sign.width);
assert_eq!(0, sign.height);
let response = sign.process_message(&Message::Hello(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::Unconfigured)), response);
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::ReceiveConfig));
assert_eq!(Some(Message::AckOperation(Address(3), Operation::ReceiveConfig)), response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::ConfigInProgress)), response);
let response = sign.process_message(&Message::SendData(
Offset(0x00),
Data::try_new(SignType::Max3000Side90x7.to_bytes()).unwrap(),
));
assert_eq!(None, response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::ConfigInProgress)), response);
let response = sign.process_message(&Message::DataChunksSent(ChunkCount(1)));
assert_eq!(None, response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::ConfigReceived)), response);
assert_eq!(Some(SignType::Max3000Side90x7), sign.sign_type());
assert_eq!(90, sign.width);
assert_eq!(7, sign.height);
let response = sign.process_message(&Message::Goodbye(Address(3)));
assert_eq!(None, response);
assert_eq!(None, sign.sign_type());
assert_eq!(0, sign.pages().len());
assert_eq!(0, sign.width);
assert_eq!(0, sign.height);
let response = sign.process_message(&Message::Hello(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::Unconfigured)), response);
}
#[test]
fn invalid_operations() {
let mut sign = VirtualSign::new(Address(3), PageFlipStyle::Manual);
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::ReceivePixels));
assert_eq!(None, response);
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::ShowLoadedPage));
assert_eq!(None, response);
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::LoadNextPage));
assert_eq!(None, response);
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::FinishReset));
assert_eq!(None, response);
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::ReceiveConfig));
assert_eq!(Some(Message::AckOperation(Address(3), Operation::ReceiveConfig)), response);
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::ReceiveConfig));
assert_eq!(None, response);
}
#[test]
fn unknown_config() {
let mut sign = VirtualSign::new(Address(3), PageFlipStyle::Manual);
let response = sign.process_message(&Message::Hello(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::Unconfigured)), response);
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::ReceiveConfig));
assert_eq!(Some(Message::AckOperation(Address(3), Operation::ReceiveConfig)), response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::ConfigInProgress)), response);
let data = vec![
0x04, 0x99, 0x00, 0x0F, 0x09, 0x1C, 0x1C, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let response = sign.process_message(&Message::SendData(Offset(0x00), Data::try_new(data).unwrap()));
assert_eq!(None, response);
let response = sign.process_message(&Message::DataChunksSent(ChunkCount(1)));
assert_eq!(None, response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::ConfigReceived)), response);
assert_eq!(None, sign.sign_type());
assert_eq!(56, sign.width);
assert_eq!(9, sign.height);
}
#[test]
fn invalid_config() {
let mut sign = VirtualSign::new(Address(3), PageFlipStyle::Manual);
let response = sign.process_message(&Message::Hello(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::Unconfigured)), response);
let response = sign.process_message(&Message::RequestOperation(Address(3), Operation::ReceiveConfig));
assert_eq!(Some(Message::AckOperation(Address(3), Operation::ReceiveConfig)), response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::ConfigInProgress)), response);
let data = vec![
0x0F, 0x99, 0x00, 0x0F, 0x09, 0x1C, 0x1C, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let response = sign.process_message(&Message::SendData(Offset(0x00), Data::try_new(data).unwrap()));
assert_eq!(None, response);
let response = sign.process_message(&Message::DataChunksSent(ChunkCount(1)));
assert_eq!(None, response);
let response = sign.process_message(&Message::QueryState(Address(3)));
assert_eq!(Some(Message::ReportState(Address(3), State::ConfigFailed)), response);
assert_eq!(None, sign.sign_type());
assert_eq!(0, sign.width);
assert_eq!(0, sign.height);
}
}