1use serde::{Deserialize, Serialize};
2use serde_json::json;
3use tokio_tungstenite::tungstenite::protocol::Message;
4
5use crate::error::{ObnizError, ObnizResult};
6use crate::obniz::Obniz;
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub enum QrCorrectionType {
11 #[serde(rename = "L")]
12 Low,
13 #[serde(rename = "M")]
14 Medium,
15 #[serde(rename = "Q")]
16 Quality,
17 #[serde(rename = "H")]
18 High,
19}
20
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub enum DisplayRawColorDepth {
24 #[serde(rename = "1")]
25 OneBit,
26 #[serde(rename = "4")]
27 FourBit,
28 #[serde(rename = "16")]
29 SixteenBit,
30}
31
32#[derive(Debug, Clone)]
34pub struct RawDisplayConfig {
35 pub width: u16,
36 pub height: u16,
37 pub color_depth: DisplayRawColorDepth,
38 pub data: Vec<u16>,
39}
40
41#[derive(Debug, Clone)]
43pub struct PinAssignment {
44 pub pin: u8,
45 pub module_name: String,
46 pub pin_name: String,
47}
48
49#[derive(Debug, Clone)]
51pub struct DisplayManager {
52 obniz: Obniz,
53}
54
55impl DisplayManager {
56 pub fn new(obniz: Obniz) -> Self {
57 Self { obniz }
58 }
59
60 pub async fn text(&self, text: &str) -> ObnizResult<()> {
62 if text.is_empty() {
63 return Err(ObnizError::Generic("Text cannot be empty".to_string()));
64 }
65
66 let request = json!([{"display": {"text": text}}]);
67 let message = Message::from(request.to_string());
68
69 self.obniz
70 .send_message(message)
71 .map_err(|e| ObnizError::Connection(e.to_string()))
72 }
73
74 pub async fn clear(&self) -> ObnizResult<()> {
76 let request = json!([{"display": {"clear": true}}]);
77 let message = Message::from(request.to_string());
78
79 self.obniz
80 .send_message(message)
81 .map_err(|e| ObnizError::Connection(e.to_string()))
82 }
83
84 pub async fn qr(&self, text: &str, correction_type: QrCorrectionType) -> ObnizResult<()> {
86 if text.is_empty() {
87 return Err(ObnizError::Generic("QR text cannot be empty".to_string()));
88 }
89
90 let request = json!([{
91 "display": {
92 "qr": {
93 "text": text,
94 "correction": correction_type
95 }
96 }
97 }]);
98 let message = Message::from(request.to_string());
99
100 self.obniz
101 .send_message(message)
102 .map_err(|e| ObnizError::Connection(e.to_string()))
103 }
104
105 pub async fn raw(&self, config: RawDisplayConfig) -> ObnizResult<()> {
107 if config.data.is_empty() {
108 return Err(ObnizError::Generic("Raw data cannot be empty".to_string()));
109 }
110
111 if config.width == 0 || config.height == 0 {
112 return Err(ObnizError::Generic(
113 "Width and height must be greater than 0".to_string(),
114 ));
115 }
116
117 let expected_length = match config.color_depth {
118 DisplayRawColorDepth::OneBit => (config.width * config.height).div_ceil(8),
119 DisplayRawColorDepth::FourBit => (config.width * config.height).div_ceil(2),
120 DisplayRawColorDepth::SixteenBit => config.width * config.height,
121 };
122
123 if config.data.len() != expected_length as usize {
124 return Err(ObnizError::Generic(format!(
125 "Data length mismatch. Expected {expected_length} but got {}",
126 config.data.len()
127 )));
128 }
129
130 let request = json!([{
131 "display": {
132 "raw": {
133 "width": config.width,
134 "height": config.height,
135 "color_depth": config.color_depth,
136 "data": config.data
137 }
138 }
139 }]);
140 let message = Message::from(request.to_string());
141
142 self.obniz
143 .send_message(message)
144 .map_err(|e| ObnizError::Connection(e.to_string()))
145 }
146
147 pub async fn brightness(&self, level: u8) -> ObnizResult<()> {
149 if level > 100 {
150 return Err(ObnizError::Generic(
151 "Brightness level must be between 0-100".to_string(),
152 ));
153 }
154
155 let request = json!([{
156 "display": {
157 "brightness": level
158 }
159 }]);
160 let message = Message::from(request.to_string());
161
162 self.obniz
163 .send_message(message)
164 .map_err(|e| ObnizError::Connection(e.to_string()))
165 }
166
167 pub async fn contrast(&self, level: u8) -> ObnizResult<()> {
169 if level > 100 {
170 return Err(ObnizError::Generic(
171 "Contrast level must be between 0-100".to_string(),
172 ));
173 }
174
175 let request = json!([{
176 "display": {
177 "contrast": level
178 }
179 }]);
180 let message = Message::from(request.to_string());
181
182 self.obniz
183 .send_message(message)
184 .map_err(|e| ObnizError::Connection(e.to_string()))
185 }
186
187 pub async fn pin_assign(&self, assignments: Vec<PinAssignment>) -> ObnizResult<()> {
189 if assignments.is_empty() {
190 return Err(ObnizError::Generic(
191 "Pin assignments cannot be empty".to_string(),
192 ));
193 }
194
195 for assignment in &assignments {
196 if assignment.pin > 11 {
197 return Err(ObnizError::InvalidPin(assignment.pin));
198 }
199 if assignment.module_name.is_empty() || assignment.pin_name.is_empty() {
200 return Err(ObnizError::Generic(
201 "Module name and pin name cannot be empty".to_string(),
202 ));
203 }
204 }
205
206 let mut pin_config = json!({});
207 for assignment in assignments {
208 pin_config[format!("io{}", assignment.pin)] = json!({
209 assignment.module_name: assignment.pin_name
210 });
211 }
212
213 let request = json!([pin_config]);
214 let message = Message::from(request.to_string());
215
216 self.obniz
217 .send_message(message)
218 .map_err(|e| ObnizError::Connection(e.to_string()))
219 }
220
221 pub async fn pixel(&self, x: u16, y: u16, color: bool) -> ObnizResult<()> {
223 let request = json!([{
224 "display": {
225 "pixel": {
226 "x": x,
227 "y": y,
228 "color": color
229 }
230 }
231 }]);
232 let message = Message::from(request.to_string());
233
234 self.obniz
235 .send_message(message)
236 .map_err(|e| ObnizError::Connection(e.to_string()))
237 }
238
239 pub async fn line(&self, x1: u16, y1: u16, x2: u16, y2: u16, color: bool) -> ObnizResult<()> {
241 let request = json!([{
242 "display": {
243 "line": {
244 "x1": x1,
245 "y1": y1,
246 "x2": x2,
247 "y2": y2,
248 "color": color
249 }
250 }
251 }]);
252 let message = Message::from(request.to_string());
253
254 self.obniz
255 .send_message(message)
256 .map_err(|e| ObnizError::Connection(e.to_string()))
257 }
258
259 pub async fn rect(
261 &self,
262 x: u16,
263 y: u16,
264 width: u16,
265 height: u16,
266 filled: bool,
267 color: bool,
268 ) -> ObnizResult<()> {
269 if width == 0 || height == 0 {
270 return Err(ObnizError::Generic(
271 "Width and height must be greater than 0".to_string(),
272 ));
273 }
274
275 let request = json!([{
276 "display": {
277 "rect": {
278 "x": x,
279 "y": y,
280 "width": width,
281 "height": height,
282 "filled": filled,
283 "color": color
284 }
285 }
286 }]);
287 let message = Message::from(request.to_string());
288
289 self.obniz
290 .send_message(message)
291 .map_err(|e| ObnizError::Connection(e.to_string()))
292 }
293
294 pub async fn circle(
296 &self,
297 x: u16,
298 y: u16,
299 radius: u16,
300 filled: bool,
301 color: bool,
302 ) -> ObnizResult<()> {
303 if radius == 0 {
304 return Err(ObnizError::Generic(
305 "Radius must be greater than 0".to_string(),
306 ));
307 }
308
309 let request = json!([{
310 "display": {
311 "circle": {
312 "x": x,
313 "y": y,
314 "radius": radius,
315 "filled": filled,
316 "color": color
317 }
318 }
319 }]);
320 let message = Message::from(request.to_string());
321
322 self.obniz
323 .send_message(message)
324 .map_err(|e| ObnizError::Connection(e.to_string()))
325 }
326
327 pub async fn text_size(&self, size: u8) -> ObnizResult<()> {
329 if size == 0 {
330 return Err(ObnizError::Generic(
331 "Text size must be greater than 0".to_string(),
332 ));
333 }
334
335 let request = json!([{
336 "display": {
337 "text_size": size
338 }
339 }]);
340 let message = Message::from(request.to_string());
341
342 self.obniz
343 .send_message(message)
344 .map_err(|e| ObnizError::Connection(e.to_string()))
345 }
346
347 pub async fn text_pos(&self, x: u16, y: u16) -> ObnizResult<()> {
349 let request = json!([{
350 "display": {
351 "text_pos": {
352 "x": x,
353 "y": y
354 }
355 }
356 }]);
357 let message = Message::from(request.to_string());
358
359 self.obniz
360 .send_message(message)
361 .map_err(|e| ObnizError::Connection(e.to_string()))
362 }
363}
364
365pub trait ObnizDisplay {
367 fn display_text(&self, text: &str) -> ObnizResult<()>;
368 fn display_clear(&self) -> ObnizResult<()>;
369 fn display_qr(&self, text: &str, correction_type: QrCorrectionType) -> ObnizResult<()>;
370 fn display_raw(&self, config: RawDisplayConfig) -> ObnizResult<()>;
371 fn display_pin_assign(&self, assignments: Vec<PinAssignment>) -> ObnizResult<()>;
372}
373
374impl ObnizDisplay for Obniz {
375 fn display_text(&self, text: &str) -> ObnizResult<()> {
376 let rt = tokio::runtime::Handle::try_current()
377 .map_err(|_| ObnizError::Generic("No tokio runtime available".to_string()))?;
378
379 rt.block_on(async { self.display().text(text).await })
380 }
381
382 fn display_clear(&self) -> ObnizResult<()> {
383 let rt = tokio::runtime::Handle::try_current()
384 .map_err(|_| ObnizError::Generic("No tokio runtime available".to_string()))?;
385
386 rt.block_on(async { self.display().clear().await })
387 }
388
389 fn display_qr(&self, text: &str, correction_type: QrCorrectionType) -> ObnizResult<()> {
390 let rt = tokio::runtime::Handle::try_current()
391 .map_err(|_| ObnizError::Generic("No tokio runtime available".to_string()))?;
392
393 rt.block_on(async { self.display().qr(text, correction_type).await })
394 }
395
396 fn display_raw(&self, config: RawDisplayConfig) -> ObnizResult<()> {
397 let rt = tokio::runtime::Handle::try_current()
398 .map_err(|_| ObnizError::Generic("No tokio runtime available".to_string()))?;
399
400 rt.block_on(async { self.display().raw(config).await })
401 }
402
403 fn display_pin_assign(&self, assignments: Vec<PinAssignment>) -> ObnizResult<()> {
404 let rt = tokio::runtime::Handle::try_current()
405 .map_err(|_| ObnizError::Generic("No tokio runtime available".to_string()))?;
406
407 rt.block_on(async { self.display().pin_assign(assignments).await })
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn test_qr_correction_type_serialization() {
417 use serde_json;
418
419 let correction = QrCorrectionType::Low;
420 let serialized = serde_json::to_string(&correction).unwrap();
421 assert_eq!(serialized, "\"L\"");
422
423 let correction = QrCorrectionType::High;
424 let serialized = serde_json::to_string(&correction).unwrap();
425 assert_eq!(serialized, "\"H\"");
426 }
427
428 #[test]
429 fn test_color_depth_serialization() {
430 use serde_json;
431
432 let depth = DisplayRawColorDepth::OneBit;
433 let serialized = serde_json::to_string(&depth).unwrap();
434 assert_eq!(serialized, "\"1\"");
435
436 let depth = DisplayRawColorDepth::SixteenBit;
437 let serialized = serde_json::to_string(&depth).unwrap();
438 assert_eq!(serialized, "\"16\"");
439 }
440
441 #[test]
442 fn test_raw_display_config_validation() {
443 let config = RawDisplayConfig {
445 width: 128,
446 height: 64,
447 color_depth: DisplayRawColorDepth::OneBit,
448 data: vec![0; 1024], };
450 assert_eq!(config.width, 128);
451 assert_eq!(config.height, 64);
452 }
453
454 #[test]
455 fn test_pin_assignment_creation() {
456 let assignment = PinAssignment {
457 pin: 5,
458 module_name: "spi".to_string(),
459 pin_name: "mosi".to_string(),
460 };
461
462 assert_eq!(assignment.pin, 5);
463 assert_eq!(assignment.module_name, "spi");
464 assert_eq!(assignment.pin_name, "mosi");
465 }
466}