lipl_display_common/
lib.rs1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use serde::{Deserialize, Serialize};
4use std::convert::TryFrom;
5use std::str::FromStr;
6use uuid::{uuid, Uuid};
7
8mod error;
9
10pub use error::Error;
12pub type Result<T> = std::result::Result<T, Error>;
13
14pub trait HandleMessage {
15 fn handle_message(&mut self, message: Message);
16}
17
18pub const SERVICE_UUID: Uuid = uuid!("27a70fc8-dc38-40c7-80bc-359462e4b808");
20pub const LOCAL_NAME: &str = "lipl";
22pub const MANUFACTURER_ID: u16 = 0xffff;
24
25pub const CHARACTERISTIC_TEXT_UUID: Uuid = uuid!("04973569-c039-4ce9-ad96-861589a74f9e");
27pub const CHARACTERISTIC_STATUS_UUID: Uuid = uuid!("61a8cb7f-d4c1-49b7-a3cf-f2c69dbb7aeb");
29pub const CHARACTERISTIC_COMMAND_UUID: Uuid = uuid!("da35e0b2-7864-49e5-aa47-8050d1cc1484");
31
32pub const WAIT_MESSAGE: &str = "Even geduld a.u.b. ...";
33
34pub const MESSAGES: &[(&str, Command); 7] = &[
35 ("d", Command::Dark),
36 ("l", Command::Light),
37 ("+", Command::Increase),
38 ("-", Command::Decrease),
39 ("?", Command::Wait),
40 ("e", Command::Exit),
41 ("o", Command::Poweroff),
42];
43
44pub trait BackgroundThread {
45 fn stop(&mut self);
46}
47
48#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Serialize)]
50#[serde(rename_all = "lowercase")]
51pub enum Message {
52 Part(String),
53 Status(String),
54 Command(Command),
55}
56
57impl Message {
58 pub fn is_stop(&self) -> bool {
59 [
60 Message::Command(Command::Exit),
61 Message::Command(Command::Poweroff),
62 ]
63 .contains(self)
64 }
65}
66
67impl std::fmt::Display for Message {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 write!(
70 f,
71 "{}",
72 match self {
73 Message::Part(text) => format!("Text: {}", text),
74 Message::Status(status) => format!("Status: {}", status),
75 Message::Command(command) => format!("Command: {}", command),
76 }
77 )
78 }
79}
80
81#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
83pub enum Command {
84 Poweroff,
85 Exit,
86 Increase,
87 Decrease,
88 Light,
89 Dark,
90 Wait,
91}
92
93impl std::fmt::Display for Command {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 write!(
96 f,
97 "{}",
98 MESSAGES.iter().find(|s| &s.1 == self).map(|s| s.0).unwrap()
99 )
100 }
101}
102
103impl FromStr for Command {
104 type Err = error::Error;
105 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
106 MESSAGES
107 .iter()
108 .find(|t| t.0 == s)
109 .map(|t| t.1.clone())
110 .ok_or(error::Error::GattCharaceristicValueParsing(
111 "Invalid command".to_owned(),
112 ))
113 }
114}
115
116impl TryFrom<(&str, Uuid)> for Message {
117 type Error = Error;
118 fn try_from(received: (&str, Uuid)) -> Result<Self> {
119 let uuid = received.1;
120 let s = received.0.to_owned();
121
122 if uuid == CHARACTERISTIC_TEXT_UUID {
123 return Ok(Message::Part(s));
124 }
125
126 if uuid == CHARACTERISTIC_STATUS_UUID {
127 return Ok(Message::Status(s));
128 }
129
130 if uuid == CHARACTERISTIC_COMMAND_UUID {
131 return s.parse::<Command>().map(Message::Command);
132 }
133
134 Err(Error::GattCharaceristicValueParsing(s))
135 }
136}
137
138#[derive(Clone, Serialize, Default)]
141pub struct LiplScreen {
142 pub text: String,
143 pub status: String,
144 pub dark: bool,
145 #[serde(rename = "fontSize")]
146 pub font_size: f32,
147}
148
149impl LiplScreen {
150 pub fn new(dark: bool, initial_font_size: f32) -> Self {
161 Self {
162 dark,
163 font_size: initial_font_size,
164 ..Default::default()
165 }
166 }
167}
168
169impl HandleMessage for LiplScreen {
170 fn handle_message(&mut self, message: Message) {
183 match message {
184 Message::Command(command) => match command {
185 Command::Dark => {
186 self.dark = true;
187 }
188 Command::Light => {
189 self.dark = false;
190 }
191 Command::Decrease => {
192 self.font_size = (self.font_size - 1.0).max(2.0);
193 }
194 Command::Increase => {
195 self.font_size = (self.font_size + 1.0).min(100.0);
196 }
197 Command::Wait => {
198 self.text = String::new();
199 WAIT_MESSAGE.clone_into(&mut self.status);
200 }
201 Command::Exit => {}
202 Command::Poweroff => {}
203 },
204 Message::Part(part) => {
205 self.text = part;
206 }
207 Message::Status(status) => {
208 self.status = status;
209 }
210 }
211 }
212}
213
214#[cfg(test)]
215mod test {
216 use super::{Command, MESSAGES};
217
218 #[test]
219 fn parse() {
220 for message in MESSAGES {
221 assert_eq!(message.0.parse::<Command>().unwrap(), message.1);
222 }
223
224 for message in MESSAGES {
225 assert_eq!(message.1.to_string(), message.0.to_string());
226 }
227 }
228}