#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::str::FromStr;
use uuid::{uuid, Uuid};
mod error;
pub use error::Error;
pub type Result<T> = std::result::Result<T, Error>;
pub trait HandleMessage {
fn handle_message(&mut self, message: Message);
}
pub const SERVICE_UUID: Uuid = uuid!("27a70fc8-dc38-40c7-80bc-359462e4b808");
pub const LOCAL_NAME: &str = "lipl";
pub const MANUFACTURER_ID: u16 = 0xffff;
pub const CHARACTERISTIC_TEXT_UUID: Uuid = uuid!("04973569-c039-4ce9-ad96-861589a74f9e");
pub const CHARACTERISTIC_STATUS_UUID: Uuid = uuid!("61a8cb7f-d4c1-49b7-a3cf-f2c69dbb7aeb");
pub const CHARACTERISTIC_COMMAND_UUID: Uuid = uuid!("da35e0b2-7864-49e5-aa47-8050d1cc1484");
pub const WAIT_MESSAGE: &str = "Even geduld a.u.b. ...";
pub const MESSAGES: &[(&str, Command); 7] = &[
("d", Command::Dark),
("l", Command::Light),
("+", Command::Increase),
("-", Command::Decrease),
("?", Command::Wait),
("e", Command::Exit),
("o", Command::Poweroff),
];
pub trait BackgroundThread {
fn stop(&mut self);
}
#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Message {
Part(String),
Status(String),
Command(Command),
}
impl Message {
pub fn is_stop(&self) -> bool {
[
Message::Command(Command::Exit),
Message::Command(Command::Poweroff),
]
.contains(self)
}
}
impl std::fmt::Display for Message {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Message::Part(text) => format!("Text: {}", text),
Message::Status(status) => format!("Status: {}", status),
Message::Command(command) => format!("Command: {}", command),
}
)
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum Command {
Poweroff,
Exit,
Increase,
Decrease,
Light,
Dark,
Wait,
}
impl std::fmt::Display for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
MESSAGES.iter().find(|s| &s.1 == self).map(|s| s.0).unwrap()
)
}
}
impl FromStr for Command {
type Err = error::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
MESSAGES
.iter()
.find(|t| t.0 == s)
.map(|t| t.1.clone())
.ok_or(error::Error::GattCharaceristicValueParsing(
"Invalid command".to_owned(),
))
}
}
impl TryFrom<(&str, Uuid)> for Message {
type Error = Error;
fn try_from(received: (&str, Uuid)) -> Result<Self> {
let uuid = received.1;
let s = received.0.to_owned();
if uuid == CHARACTERISTIC_TEXT_UUID {
return Ok(Message::Part(s));
}
if uuid == CHARACTERISTIC_STATUS_UUID {
return Ok(Message::Status(s));
}
if uuid == CHARACTERISTIC_COMMAND_UUID {
return s.parse::<Command>().map(Message::Command);
}
Err(Error::GattCharaceristicValueParsing(s))
}
}
#[derive(Clone, Serialize, Default)]
pub struct LiplScreen {
pub text: String,
pub status: String,
pub dark: bool,
#[serde(rename = "fontSize")]
pub font_size: f32,
}
impl LiplScreen {
pub fn new(dark: bool, initial_font_size: f32) -> Self {
Self {
dark,
font_size: initial_font_size,
..Default::default()
}
}
}
impl HandleMessage for LiplScreen {
fn handle_message(&mut self, message: Message) {
match message {
Message::Command(command) => match command {
Command::Dark => {
self.dark = true;
}
Command::Light => {
self.dark = false;
}
Command::Decrease => {
self.font_size = (self.font_size - 1.0).max(2.0);
}
Command::Increase => {
self.font_size = (self.font_size + 1.0).min(100.0);
}
Command::Wait => {
self.text = String::new();
WAIT_MESSAGE.clone_into(&mut self.status);
}
Command::Exit => {}
Command::Poweroff => {}
},
Message::Part(part) => {
self.text = part;
}
Message::Status(status) => {
self.status = status;
}
}
}
}
#[cfg(test)]
mod test {
use super::{Command, MESSAGES};
#[test]
fn parse() {
for message in MESSAGES {
assert_eq!(message.0.parse::<Command>().unwrap(), message.1);
}
for message in MESSAGES {
assert_eq!(message.1.to_string(), message.0.to_string());
}
}
}