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}