Skip to main content

automation_hat/
lights.rs

1//! LED control functionality for the Automation HAT.
2//!
3//! This module provides the `LED` struct, which represents a single LED on the Automation HAT.
4//! Each LED has a brightness level that can be controlled from 0.0 to 1.0.
5
6use linux_embedded_hal::I2cdev;
7use sn3218_hal::SN3218;
8use std::collections::HashMap;
9use std::sync::{Arc, Mutex, OnceLock};
10
11// Shared global state to track LED brightness values across the system
12static LED_STATE: OnceLock<Mutex<HashMap<u8, u8>>> = OnceLock::new();
13
14/// Represents a single LED on the Automation HAT.
15///
16/// The `LED` struct provides control over a single LED, allowing it to be turned on/off
17/// or set to a specific brightness level. LEDs are controlled through the SN3218 LED driver
18/// chip which supports 18 channels with 255 brightness levels each.
19pub struct LED {
20    /// Reference to the SN3218 LED driver
21    driver: Arc<Mutex<SN3218<I2cdev>>>,
22    /// Channel number on the SN3218 (0-17)
23    channel: u8,
24    /// Current brightness value (0.0-1.0)
25    pub brightness: f64,
26    /// Maximum hardware brightness value (typically 255)
27    max_brightness: u8,
28}
29
30impl LED {
31    /// Creates a new LED instance for the specified channel.
32    ///
33    /// # Arguments
34    ///
35    /// * `driver` - Shared reference to the SN3218 LED driver
36    /// * `channel` - The channel number (0-17) on the SN3218 chip
37    ///
38    /// # Returns
39    ///
40    /// A new `LED` instance initialized to off (brightness 0.0)
41    pub fn new(driver: Arc<Mutex<SN3218<I2cdev>>>, channel: u8) -> Self {
42        // Initialize global LED state if not already done
43        LED_STATE.get_or_init(|| Mutex::new(HashMap::new()));
44
45        LED {
46            driver,
47            channel,
48            brightness: 0.0,
49            max_brightness: 255,
50        }
51    }
52
53    /// Turns the LED on at full brightness.
54    ///
55    /// # Returns
56    ///
57    /// A `Result` indicating success or containing an error
58    pub fn on(&mut self) -> Result<(), Box<dyn std::error::Error>> {
59        self.set_brightness(1.0)
60    }
61
62    /// Turns the LED off (brightness 0.0).
63    ///
64    /// # Returns
65    ///
66    /// A `Result` indicating success or containing an error
67    pub fn off(&mut self) -> Result<(), Box<dyn std::error::Error>> {
68        self.set_brightness(0.0)
69    }
70
71    /// Toggles the LED between on and off states.
72    ///
73    /// If the LED is currently off (brightness 0.0), it will be turned on.
74    /// Otherwise, it will be turned off.
75    ///
76    /// # Returns
77    ///
78    /// A `Result` indicating success or containing an error
79    pub fn toggle(&mut self) -> Result<(), Box<dyn std::error::Error>> {
80        if self.brightness == 0.0 {
81            self.on()
82        } else {
83            self.off()
84        }
85    }
86
87    /// Sets the LED brightness to a specific value.
88    ///
89    /// # Arguments
90    ///
91    /// * `brightness` - A value between 0.0 (off) and 1.0 (full brightness)
92    ///
93    /// # Returns
94    ///
95    /// A `Result` indicating success or containing an error
96    ///
97    /// # Errors
98    ///
99    /// Returns an error if the brightness value is outside the valid range of 0.0 to 1.0,
100    /// or if communication with the LED driver fails.
101    pub fn set_brightness(&mut self, brightness: f64) -> Result<(), Box<dyn std::error::Error>> {
102        if brightness < 0.0 || brightness > 1.0 {
103            return Err("Brightness must be between 0.0 and 1.0".into());
104        }
105
106        self.brightness = brightness;
107        let value = (brightness * self.max_brightness as f64) as u8;
108
109        let led_state_mutex = LED_STATE.get_or_init(|| Mutex::new(HashMap::new()));
110        let mut led_state = led_state_mutex.lock().unwrap();
111
112        // Update the state for this channel
113        led_state.insert(self.channel, value);
114
115        // Prepare values array with current state of all channels
116        let mut values = [0u8; 18];
117        let mut led_mask = 0u32;
118
119        for (channel, brightness) in led_state.iter() {
120            if *channel < 18 {
121                values[*channel as usize] = *brightness;
122                if *brightness > 0 {
123                    led_mask |= 1u32 << channel;
124                }
125            }
126        }
127
128        let mut driver = self.driver.lock().unwrap();
129        driver.enable_leds(led_mask).unwrap();
130        driver.output(&values).unwrap();
131
132        Ok(())
133    }
134
135    /// Alias for `set_brightness` - sets the LED to a specific brightness.
136    ///
137    /// # Arguments
138    ///
139    /// * `brightness` - A value between 0.0 (off) and 1.0 (full brightness)
140    ///
141    /// # Returns
142    ///
143    /// A `Result` indicating success or containing an error
144    pub fn set(&mut self, brightness: f64) -> Result<(), Box<dyn std::error::Error>> {
145        self.set_brightness(brightness)
146    }
147}
148
149/// Implement Clone for LED to allow LED objects to be duplicated.
150/// This is useful when the same LED needs to be shared between multiple components.
151impl Clone for LED {
152    fn clone(&self) -> Self {
153        LED {
154            driver: Arc::clone(&self.driver),
155            channel: self.channel,
156            brightness: self.brightness,
157            max_brightness: self.max_brightness,
158        }
159    }
160}
161
162/// Implement Debug for LED to allow for easier debugging and printing of LED objects.
163impl std::fmt::Debug for LED {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        f.debug_struct("LED")
166            .field("channel", &self.channel)
167            .field("brightness", &self.brightness)
168            .field("max_brightness", &self.max_brightness)
169            .finish()
170    }
171}