Skip to main content

led_bargraph/
lib.rs

1//! # Bargraph
2//!
3//! A library for the [Adafruit Bi-Color (Red/Green) 24-Bar Bargraph w/I2C Backpack Kit](https://www.adafruit.com/product/1721).
4#![deny(missing_docs)]
5extern crate ansi_term;
6extern crate embedded_hal as hal;
7extern crate ht16k33;
8extern crate num_integer;
9
10#[macro_use]
11extern crate slog;
12extern crate slog_stdlog;
13
14use ansi_term::Colour::{Fixed, Green, Red, White, Yellow};
15use ansi_term::Style;
16
17use hal::blocking::i2c::{Write, WriteRead};
18
19use ht16k33::{Display, HT16K33};
20
21use num_integer::Integer;
22
23use slog::Drain;
24
25#[derive(Clone, Copy, Debug, PartialEq)]
26/// LED colors.
27pub enum LedColor {
28    /// Turn off both the Red & Green LEDs.
29    Off,
30    /// Turn on only the Green LED.
31    Green,
32    /// Turn on only the Red LED.
33    Red,
34    /// Turn on both the Red  & Green LEDs.
35    Yellow,
36}
37
38const BARGRAPH_DISPLAY_CHAR: &str = "\u{258A}";
39const BARGRAPH_RESOLUTION: u8 = 24;
40
41/// The bargraph state.
42pub struct Bargraph<I2C> {
43    device: HT16K33<I2C>,
44    logger: slog::Logger,
45}
46
47impl<I2C, E> Bargraph<I2C>
48where
49    I2C: Write<Error = E> + WriteRead<Error = E>,
50{
51    /// Create a Bargraph for display.
52    ///
53    /// # Arguments
54    ///
55    /// * `device` - A connected `HT16K33` device that drives the display.
56    /// * `logger` - A logging instance.
57    ///
58    /// # Notes
59    ///
60    /// `logger = None` will log to the `slog-stdlog` drain. This makes the
61    /// library effectively work the same as if it was just using `log` instead
62    /// of `slog`.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// // NOTE: `None is used for the Logger in these examples for convenience,
68    /// // in practice using an actual logger in preferred.
69    ///
70    /// extern crate ht16k33;
71    /// extern crate led_bargraph;
72    ///
73    /// use ht16k33::i2c_mock::I2cMock;
74    /// use led_bargraph::Bargraph;
75    /// # fn main() {
76    ///
77    /// // Create an I2C device.
78    /// let mut i2c = I2cMock::new(None);
79    ///
80    /// // The I2C device address.
81    /// let address: u8 = 0;
82    ///
83    /// let mut bargraph = Bargraph::new(i2c, address, None);
84    ///
85    /// # }
86    /// ```
87    pub fn new<L>(i2c: I2C, i2c_address: u8, logger: L) -> Self
88    where
89        L: Into<Option<slog::Logger>>,
90    {
91        let logger = logger
92            .into()
93            .unwrap_or_else(|| slog::Logger::root(slog_stdlog::StdLog.fuse(), o!()));
94
95        trace!(logger, "Constructing Bargraph");
96
97        let ht16k33_logger = logger.new(o!("mod" => "HT16K33"));
98        let ht16k33 = HT16K33::new(i2c, i2c_address, ht16k33_logger);
99
100        Bargraph {
101            device: ht16k33,
102            logger,
103        }
104    }
105
106    /// Initialize the Bargraph display & the connected `HT16K33` device.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// # extern crate ht16k33;
112    /// # extern crate led_bargraph;
113    /// # use ht16k33::i2c_mock::I2cMock;
114    /// # use led_bargraph::Bargraph;
115    /// # fn main() {
116    ///
117    /// # let mut i2c = I2cMock::new(None);
118    /// # let address: u8 = 0;
119    ///
120    /// let mut bargraph = Bargraph::new(i2c, address, None);
121    /// bargraph.initialize().unwrap();
122    ///
123    /// # }
124    /// ```
125    pub fn initialize(&mut self) -> Result<(), E> {
126        trace!(self.logger, "initialize");
127
128        // Reset the display.
129        self.device.initialize()?;
130
131        Ok(())
132    }
133
134    /// Clear the Bargraph display.
135    ///
136    /// # Examples
137    ///
138    /// ```
139    /// # extern crate ht16k33;
140    /// # extern crate led_bargraph;
141    /// # use ht16k33::i2c_mock::I2cMock;
142    /// # use led_bargraph::Bargraph;
143    /// # fn main() {
144    /// # let mut i2c = I2cMock::new(None);
145    /// # let address: u8 = 0;
146    ///
147    /// let mut bargraph = Bargraph::new(i2c, address, None);
148    /// bargraph.clear().unwrap();
149    ///
150    /// # }
151    /// ```
152    pub fn clear(&mut self) -> Result<(), E> {
153        trace!(self.logger, "clear");
154
155        self.device.clear_display_buffer();
156        self.device.write_display_buffer()
157    }
158
159    /// Update the Bargraph display, showing `range` total values with all values
160    /// from `0` to `value` filled.
161    ///
162    /// If `value` is greater than `range`, then all bars are filled and will blink;
163    /// automatic re-scaling of the range does *not* happen because:
164    ///
165    /// * The bargraph can only scale to a maximum resolution.
166    /// * Users are already familiar with viewing the current range, and dynamically
167    ///   changing the range makes it hard for users to see what's happening at a glance.
168    ///
169    /// # Arguments
170    ///
171    /// * `value` - How many values to fill, starting from `0`.
172    /// * `range` - Total number of values to display.
173    ///
174    /// # Examples
175    ///
176    /// ```
177    /// # extern crate ht16k33;
178    /// # extern crate led_bargraph;
179    /// # use ht16k33::i2c_mock::I2cMock;
180    /// # use led_bargraph::Bargraph;
181    /// # fn main() {
182    /// # let mut i2c = I2cMock::new(None);
183    /// # let address: u8 = 0;
184    ///
185    /// let mut bargraph = Bargraph::new(i2c, address, None);
186    /// bargraph.update(5, 6, false).unwrap();
187    ///
188    /// # }
189    /// ```
190    pub fn update(&mut self, value: u8, range: u8, show: bool) -> Result<(), E> {
191        trace!(self.logger, "update");
192
193        // Reset the display in preparation for the update.
194        self.device.clear_display_buffer();
195
196        let mut blink = false;
197        let mut clamped_value = value;
198
199        if value > range {
200            warn!(self.logger, "Value is greater than range, setting display to blink";
201                  "value" => value, "range" => range);
202            clamped_value = range;
203            blink = true;
204        }
205
206        for current_value in 1..=range {
207            let fill = current_value <= clamped_value;
208            self.update_value(current_value - 1, range, fill);
209        }
210
211        self.device.write_display_buffer()?;
212
213        self.set_blink(blink)?;
214
215        if show {
216            self.show()?;
217        }
218
219        Ok(())
220    }
221
222    /// Enable/Disable continuous blinking of the Bargraph display.
223    ///
224    /// # Arguments
225    ///
226    /// * `enabled` - Whether to enabled blinking or not.
227    ///
228    /// # Examples
229    ///
230    /// ```
231    /// # extern crate ht16k33;
232    /// # extern crate led_bargraph;
233    /// # use ht16k33::i2c_mock::I2cMock;
234    /// # use led_bargraph::Bargraph;
235    /// # fn main() {
236    /// # let mut i2c = I2cMock::new(None);
237    /// # let address: u8 = 0;
238    ///
239    /// let mut bargraph = Bargraph::new(i2c, address, None);
240    /// bargraph.set_blink(true).unwrap();
241    ///
242    /// # }
243    /// ```
244    pub fn set_blink(&mut self, enabled: bool) -> Result<(), E> {
245        // TODO Add support for different blink speeds.
246        trace!(self.logger, "set_blink"; "enabled" => enabled);
247
248        if enabled {
249            self.device.set_display(Display::ONE_HZ)
250        } else {
251            self.device.set_display(Display::ON)
252        }
253    }
254
255    /// Show the current bargraph display on-screen.
256    ///
257    /// # Examples
258    ///
259    /// ```
260    /// # extern crate ht16k33;
261    /// # extern crate led_bargraph;
262    /// # use ht16k33::i2c_mock::I2cMock;
263    /// # use led_bargraph::Bargraph;
264    /// # fn main() {
265    /// # let mut i2c = I2cMock::new(None);
266    /// # let address: u8 = 0;
267    ///
268    /// let mut bargraph = Bargraph::new(i2c, address, None);
269    /// bargraph.show().unwrap();
270    ///
271    /// # }
272    /// ```
273    pub fn show(&mut self) -> Result<(), E> {
274        trace!(self.logger, "show");
275
276        // Read & retrieve the buffer values from the device.
277        self.device.read_display_buffer()?;
278        let &buffer = self.device.display_buffer();
279
280        let display = self.device.display();
281
282        // Convert the buffer values for display as LEDs.
283        let mut leds = [LedColor::Off; BARGRAPH_RESOLUTION as usize];
284
285        // The Adafruit bargraph only utilizes the first 6 rows:
286        //
287        // 6 rows x 8 commons == 48 LEDs == 24 bars * 2 colors
288        //
289        // As each row represents 8 of the 48 LEDs, many of the indexes will empty. Need to merge
290        // each row together to get the complete display. When merging, if both red & green LEDs
291        // are enabled, then update them to be yellow.
292        for (row, common) in buffer.iter().enumerate().take(6) {
293            if *display == Display::OFF {
294                trace!(
295                    self.logger,
296                    "Display is off, don't attempt retrieve/merge the LED bars"
297                );
298                break;
299            }
300
301            let bars = self.row_common_to_bars(row as u8, common.bits());
302
303            for index in 0..bars.len() {
304                if let Some(color) = bars[index] {
305                    match leds[index] {
306                        LedColor::Green => {
307                            if color == LedColor::Red {
308                                leds[index] = LedColor::Yellow;
309                            }
310                        }
311                        LedColor::Red => {
312                            if color == LedColor::Green {
313                                leds[index] = LedColor::Yellow;
314                            }
315                        }
316                        LedColor::Off => {
317                            leds[index] = color;
318                        }
319                        LedColor::Yellow => {
320                            // Do nothing.
321                        }
322                    }
323                }
324            }
325        }
326        debug!(self.logger, "bars"; "colors" => format!("{:#?}", leds));
327
328        // Display the LEDs.
329        self.display_ascii_bargraph(&leds, *display);
330
331        Ok(())
332    }
333
334    // Enable/disable the fill for a `value` on the Bargraph display.
335    //
336    // # Arguments
337    //
338    // * `value` - Which value to fill.
339    // * `range` - The total range of the display (for calculating the value size).
340    // * `fill` - Whether to fill (true) the value or only display its header.
341    //
342    // # Notes
343    //
344    // Value `0` is at the bottom of the display (lowest value).
345    fn update_value(&mut self, value: u8, range: u8, fill: bool) {
346        trace!(self.logger, "update_value"; "value" => value, "range" => range, "fill" => fill);
347
348        // Calculate the size of the value.
349        let value_size = BARGRAPH_RESOLUTION / range;
350
351        let start_bar = value * value_size;
352        let end_bar = start_bar + value_size - 1;
353
354        // Fill in the value.
355        for current_bar in start_bar..end_bar {
356            let fill_color = if fill {
357                LedColor::Yellow
358            } else {
359                LedColor::Off
360            };
361            self.update_bar(current_bar, fill_color);
362        }
363
364        // Color the "top" bar of the value.
365        let fill_color = if fill { LedColor::Red } else { LedColor::Green };
366        self.update_bar(end_bar, fill_color);
367    }
368
369    // Set the bar to the desired color.
370    //
371    // The buffer must be later written using [write_display_buffer()](struct.HT16K33.html#method.write_display_buffer)
372    // for the change to be displayed.
373    //
374    // # Arguments
375    //
376    // * `bar- A value from `0` to `23`.
377    // * `color` - A valid color.
378    #[allow(clippy::blacklisted_name)]
379    fn update_bar(&mut self, bar: u8, color: LedColor) {
380        trace!(self.logger, "update_bar"; "bar" => bar, "color" => format!("{:?}", color));
381
382        let (row, common) = self.bar_to_row_common(bar);
383
384        let red_led = ht16k33::LedLocation::new(row, common).unwrap();
385        let green_led = ht16k33::LedLocation::new(row + 1, common).unwrap();
386
387        let red_enabled = color == LedColor::Red || color == LedColor::Yellow;
388        let green_enabled = color == LedColor::Green || color == LedColor::Yellow;
389
390        self.device.update_display_buffer(red_led, red_enabled);
391        self.device.update_display_buffer(green_led, green_enabled);
392    }
393
394    // This transform follows the layout of the Adafruit bargraph backpack.
395    #[allow(clippy::blacklisted_name)]
396    fn bar_to_row_common(&self, bar: u8) -> (u8, u8) {
397        let (count, remainder) = bar.div_mod_floor(&12);
398        let (mut row, mut common) = remainder.div_mod_floor(&4);
399        row *= 2;
400        common += count * 4;
401
402        trace!(self.logger, "bar_to_row_common"; "bar" => bar, "row" => row, "common" => common);
403
404        (row, common)
405    }
406
407    // For the given row & common determine the bar #'s and whether they're off, or enabled as red
408    // or green. Each common "value" represents the state of 8 LEDs.
409    //
410    // The row determines if it's red (even) or green (odd).
411    //
412    // The bits of the common determine which commons are enabled.
413    //
414    // There are 2 LEDs per bar (1x red, 1x green), these bar #'s need to merged with the bar
415    // #'s from other rows to determine if actual bar # is lit or not.
416    //
417    // This transform follows the layout of the Adafruit bargraph backpack.
418    fn row_common_to_bars(
419        &self,
420        row_in: u8,
421        common_in: u8,
422    ) -> [Option<LedColor>; BARGRAPH_RESOLUTION as usize] {
423        let mut bars = [None; BARGRAPH_RESOLUTION as usize];
424
425        let (row, green) = row_in.div_mod_floor(&2);
426
427        for position in 0..ht16k33::COMMONS_SIZE {
428            let check = 1 << position;
429
430            let (count, common) = (position as u8).div_mod_floor(&4);
431            let remainder = row * 4 + common;
432            #[allow(clippy::blacklisted_name)]
433            let bar = count * 12 + remainder;
434            let enabled = check == common_in & check;
435
436            if enabled {
437                bars[bar as usize] = if green == 1 {
438                    Some(LedColor::Green)
439                } else {
440                    Some(LedColor::Red)
441                };
442            } else {
443                bars[bar as usize] = Some(LedColor::Off);
444            }
445        }
446
447        trace!(self.logger, "row_common_to_bars"; "row" => row_in, "common" => format!("{:#010b}", common_in), "bars" => format!("{:?}", bars));
448
449        bars
450    }
451
452    // Unicode box-drawing characters: https://en.wikipedia.org/wiki/Box-drawing_character
453    fn display_ascii_bargraph(&self, leds: &[LedColor], display: Display) {
454        println!(
455            "{corner_top_left}{line}{corner_top_right}",
456            corner_top_left = White.paint("\u{2554}"),
457            line = White.paint(
458                std::iter::repeat("\u{2550}")
459                    .take(leds.len() as usize)
460                    .collect::<String>()
461            ),
462            corner_top_right = White.paint("\u{2557}")
463        );
464
465        print!("{side}", side = White.paint("\u{2551}"),);
466
467        for led in leds.iter() {
468            let mut style = Style::new();
469
470            if display == Display::HALF_HZ
471                || display == Display::ONE_HZ
472                || display == Display::TWO_HZ
473            {
474                style = style.blink();
475            }
476
477            let mut color = match led {
478                LedColor::Green => style.fg(Green),
479                LedColor::Red => style.fg(Red),
480                LedColor::Yellow => style.fg(Yellow),
481                LedColor::Off => style.fg(Fixed(238)), // Dark grey.
482            };
483
484            print!("{}", color.paint(BARGRAPH_DISPLAY_CHAR));
485        }
486
487        println!("{side}", side = White.paint("\u{2551}"),);
488
489        println!(
490            "{corner_bottom_left}{line}{corner_bottom_right}",
491            corner_bottom_left = White.paint("\u{255A}"),
492            line = White.paint(
493                std::iter::repeat("\u{2550}")
494                    .take(leds.len() as usize)
495                    .collect::<String>()
496            ),
497            corner_bottom_right = White.paint("\u{255D}")
498        );
499    }
500}
501
502#[cfg(test)]
503mod tests {
504    use super::*;
505    use ht16k33::i2c_mock::I2cMock;
506
507    const ADDRESS: u8 = 0;
508
509    #[test]
510    fn new() {
511        let i2c = I2cMock::new(None);
512        let _bargraph = Bargraph::new(i2c, ADDRESS, None);
513    }
514
515    #[test]
516    fn initialize() {
517        let i2c = I2cMock::new(None);
518        let mut bargraph = Bargraph::new(i2c, ADDRESS, None);
519        bargraph.initialize().unwrap();
520    }
521
522    #[test]
523    fn clear() {
524        let i2c = I2cMock::new(None);
525        let mut bargraph = Bargraph::new(i2c, ADDRESS, None);
526        bargraph.initialize().unwrap();
527
528        bargraph.clear().unwrap();
529    }
530
531    #[test]
532    fn update() {
533        let i2c = I2cMock::new(None);
534        let mut bargraph = Bargraph::new(i2c, ADDRESS, None);
535        bargraph.initialize().unwrap();
536
537        bargraph.update(5, 6, false).unwrap();
538    }
539
540    #[test]
541    fn set_blink() {
542        let i2c = I2cMock::new(None);
543        let mut bargraph = Bargraph::new(i2c, ADDRESS, None);
544        bargraph.initialize().unwrap();
545
546        bargraph.set_blink(true).unwrap();
547        bargraph.set_blink(false).unwrap();
548    }
549
550    #[test]
551    fn show() {
552        let i2c = I2cMock::new(None);
553        let mut bargraph = Bargraph::new(i2c, ADDRESS, None);
554        bargraph.initialize().unwrap();
555
556        bargraph.show().unwrap();
557    }
558}