diff --git a/src/main.rs b/src/main.rs index 116a66c..b5d334a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use std::{fs, path::PathBuf}; use anyhow::{Context, Result}; use badgemagic::{ ble::Device as BleDevice, - protocol::{Mode, PayloadBuffer, Speed, Style}, + protocol::{Brightness, Mode, PayloadBuffer, Speed, Style}, usb_hid::Device as UsbDevice, }; use base64::Engine; @@ -63,6 +63,9 @@ enum TransportProtocol { #[derive(Deserialize)] #[serde(deny_unknown_fields)] struct Config { + #[serde(default)] + brightness: Brightness, + #[serde(rename = "message")] messages: Vec, } @@ -148,6 +151,7 @@ fn gnerate_payload(args: &mut Args) -> Result { }; let mut payload = PayloadBuffer::new(); + payload.set_brightness(config.brightness); for message in config.messages { let mut style = Style::default(); diff --git a/src/protocol.rs b/src/protocol.rs index e425b6c..32c41e8 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1,7 +1,5 @@ //! Protocol used to update the badge -use std::num::TryFromIntError; - #[cfg(feature = "embedded-graphics")] use embedded_graphics::{ draw_target::DrawTarget, @@ -11,6 +9,7 @@ use embedded_graphics::{ primitives::Rectangle, Drawable, }; +use std::{convert::Infallible, num::TryFromIntError}; use time::OffsetDateTime; use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U16}; @@ -54,7 +53,7 @@ impl Style { self } - /// Show a dotted border arround the display. + /// Show a dotted border around the display. /// ``` /// use badgemagic::protocol::Style; /// # ( @@ -161,7 +160,7 @@ impl TryFrom for Speed { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] pub enum Mode { - /// Scroll thorugh the message from left to right + /// Scroll through the message from left to right #[default] Left, @@ -193,14 +192,72 @@ pub enum Mode { Laser, } +/// Display Brightness +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "f32", into = "u8"))] +pub enum Brightness { + #[default] + Full = 0x00, + ThreeQuarters = 0x10, + Half = 0x20, + OneQuarter = 0x30, +} + +impl From for u8 { + fn from(value: Brightness) -> Self { + value as u8 + } +} + +impl TryFrom for Brightness { + type Error = TryFromIntError; + fn try_from(value: u8) -> Result { + Ok(match value { + 0x00 => Self::Full, + 0x10 => Self::ThreeQuarters, + 0x20 => Self::Half, + 0x30 => Self::OneQuarter, + _ => return Err(u8::try_from(-1).unwrap_err()), + }) + } +} + +impl From for f32 { + fn from(value: Brightness) -> Self { + match value { + Brightness::Full => 1.0, + Brightness::ThreeQuarters => 0.75, + Brightness::Half => 0.5, + Brightness::OneQuarter => 0.5, + } + } +} + +impl TryFrom for Brightness { + type Error = Infallible; + fn try_from(value: f32) -> Result { + if value < 0.375 { + Ok(Self::OneQuarter) + } else if value < 0.625 { + Ok(Self::Half) + } else if value < 0.875 { + Ok(Self::ThreeQuarters) + } else { + Ok(Self::Full) + } + } +} + const MSG_PADDING_ALIGN: usize = 64; -const MAGIC: [u8; 6] = *b"wang\0\0"; +const MAGIC: [u8; 5] = *b"wang\0"; #[derive(FromBytes, IntoBytes, Immutable, KnownLayout)] #[repr(C)] struct Header { - magic: [u8; 6], + magic: [u8; 5], + brightness: u8, blink: u8, border: u8, speed_and_mode: [u8; 8], @@ -241,7 +298,7 @@ impl Timestamp { /// Buffer to create a payload /// -/// A payload consits of up to 8 messages +/// A payload consists of up to 8 messages /// ``` /// # #[cfg(feature = "embedded-graphics")] /// # fn main() { @@ -283,6 +340,7 @@ impl PayloadBuffer { num_messages: 0, data: Header { magic: MAGIC, + brightness: Brightness::Full.into(), blink: 0, border: 0, speed_and_mode: [0; 8], @@ -300,6 +358,10 @@ impl PayloadBuffer { Header::mut_from_prefix(&mut self.data).unwrap().0 } + pub fn set_brightness(&mut self, brightness: Brightness) { + self.header_mut().brightness = brightness.into(); + } + /// Return the current number of messages pub fn num_messages(&mut self) -> usize { self.num_messages as usize @@ -368,7 +430,7 @@ impl PayloadBuffer { &self.data } - /// Convert the payload buffe into bytes (with padding) + /// Convert the payload buffer into bytes (with padding) #[allow(clippy::missing_panics_doc)] // should never panic #[must_use] pub fn into_padded_bytes(self) -> impl AsRef<[u8]> { @@ -486,7 +548,7 @@ impl DrawTarget for MessageBuffer<'_> { mod test { use std::ops::Range; - use super::Speed; + use super::{Brightness, Speed}; #[test] fn speed_to_u8_and_back() { @@ -499,4 +561,26 @@ mod test { } } } + + #[test] + fn u8_to_brightness_and_back() { + const VALID_BRIGHTNESS_VALUES: [u8; 4] = [0x00, 0x10, 0x20, 0x30]; + for i in u8::MIN..u8::MAX { + if let Ok(brightness) = Brightness::try_from(i) { + assert_eq!(u8::from(brightness), i); + } else { + assert!(!VALID_BRIGHTNESS_VALUES.contains(&i)); + } + } + } + + #[test] + fn f32_to_brightness_and_back() { + const VALID_BRIGHTNESS_VALUES: [f32; 4] = [0.25, 0.5, 0.75, 1.0]; + for i in i8::MIN..i8::MAX { + let i = i as f32 / 4f32; + let Ok(brightness) = Brightness::try_from(i); + assert!(VALID_BRIGHTNESS_VALUES.contains(&(f32::from(brightness)))); + } + } }