#![deny(missing_docs)]
extern crate ansi_term;
extern crate embedded_hal as hal;
extern crate ht16k33;
extern crate num_integer;
#[macro_use]
extern crate slog;
extern crate slog_stdlog;
use ansi_term::Colour::{Fixed, Green, Red, White, Yellow};
use ansi_term::Style;
use hal::blocking::i2c::{Write, WriteRead};
use ht16k33::{Display, HT16K33};
use num_integer::Integer;
use slog::Drain;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LedColor {
Off,
Green,
Red,
Yellow,
}
const BARGRAPH_DISPLAY_CHAR: &str = "\u{258A}";
const BARGRAPH_RESOLUTION: u8 = 24;
pub struct Bargraph<I2C> {
device: HT16K33<I2C>,
logger: slog::Logger,
}
impl<I2C, E> Bargraph<I2C>
where
I2C: Write<Error = E> + WriteRead<Error = E>,
{
pub fn new<L>(i2c: I2C, i2c_address: u8, logger: L) -> Self
where
L: Into<Option<slog::Logger>>,
{
let logger = logger
.into()
.unwrap_or_else(|| slog::Logger::root(slog_stdlog::StdLog.fuse(), o!()));
trace!(logger, "Constructing Bargraph");
let ht16k33_logger = logger.new(o!("mod" => "HT16K33"));
let ht16k33 = HT16K33::new(i2c, i2c_address, ht16k33_logger);
Bargraph {
device: ht16k33,
logger,
}
}
pub fn initialize(&mut self) -> Result<(), E> {
trace!(self.logger, "initialize");
self.device.initialize()?;
Ok(())
}
pub fn clear(&mut self) -> Result<(), E> {
trace!(self.logger, "clear");
self.device.clear_display_buffer();
self.device.write_display_buffer()
}
pub fn update(&mut self, value: u8, range: u8, show: bool) -> Result<(), E> {
trace!(self.logger, "update");
self.device.clear_display_buffer();
let mut blink = false;
let mut clamped_value = value;
if value > range {
warn!(self.logger, "Value is greater than range, setting display to blink";
"value" => value, "range" => range);
clamped_value = range;
blink = true;
}
for current_value in 1..=range {
let fill = current_value <= clamped_value;
self.update_value(current_value - 1, range, fill);
}
self.device.write_display_buffer()?;
self.set_blink(blink)?;
if show {
self.show()?;
}
Ok(())
}
pub fn set_blink(&mut self, enabled: bool) -> Result<(), E> {
trace!(self.logger, "set_blink"; "enabled" => enabled);
if enabled {
self.device.set_display(Display::ONE_HZ)
} else {
self.device.set_display(Display::ON)
}
}
pub fn show(&mut self) -> Result<(), E> {
trace!(self.logger, "show");
self.device.read_display_buffer()?;
let &buffer = self.device.display_buffer();
let display = self.device.display();
let mut leds = [LedColor::Off; BARGRAPH_RESOLUTION as usize];
for (row, common) in buffer.iter().enumerate().take(6) {
if *display == Display::OFF {
trace!(
self.logger,
"Display is off, don't attempt retrieve/merge the LED bars"
);
break;
}
let bars = self.row_common_to_bars(row as u8, common.bits());
for index in 0..bars.len() {
if let Some(color) = bars[index] {
match leds[index] {
LedColor::Green => {
if color == LedColor::Red {
leds[index] = LedColor::Yellow;
}
}
LedColor::Red => {
if color == LedColor::Green {
leds[index] = LedColor::Yellow;
}
}
LedColor::Off => {
leds[index] = color;
}
LedColor::Yellow => {
}
}
}
}
}
debug!(self.logger, "bars"; "colors" => format!("{:#?}", leds));
self.display_ascii_bargraph(&leds, *display);
Ok(())
}
fn update_value(&mut self, value: u8, range: u8, fill: bool) {
trace!(self.logger, "update_value"; "value" => value, "range" => range, "fill" => fill);
let value_size = BARGRAPH_RESOLUTION / range;
let start_bar = value * value_size;
let end_bar = start_bar + value_size - 1;
for current_bar in start_bar..end_bar {
let fill_color = if fill {
LedColor::Yellow
} else {
LedColor::Off
};
self.update_bar(current_bar, fill_color);
}
let fill_color = if fill { LedColor::Red } else { LedColor::Green };
self.update_bar(end_bar, fill_color);
}
#[allow(clippy::blacklisted_name)]
fn update_bar(&mut self, bar: u8, color: LedColor) {
trace!(self.logger, "update_bar"; "bar" => bar, "color" => format!("{:?}", color));
let (row, common) = self.bar_to_row_common(bar);
let red_led = ht16k33::LedLocation::new(row, common).unwrap();
let green_led = ht16k33::LedLocation::new(row + 1, common).unwrap();
let red_enabled = color == LedColor::Red || color == LedColor::Yellow;
let green_enabled = color == LedColor::Green || color == LedColor::Yellow;
self.device.update_display_buffer(red_led, red_enabled);
self.device.update_display_buffer(green_led, green_enabled);
}
#[allow(clippy::blacklisted_name)]
fn bar_to_row_common(&self, bar: u8) -> (u8, u8) {
let (count, remainder) = bar.div_mod_floor(&12);
let (mut row, mut common) = remainder.div_mod_floor(&4);
row *= 2;
common += count * 4;
trace!(self.logger, "bar_to_row_common"; "bar" => bar, "row" => row, "common" => common);
(row, common)
}
fn row_common_to_bars(
&self,
row_in: u8,
common_in: u8,
) -> [Option<LedColor>; BARGRAPH_RESOLUTION as usize] {
let mut bars = [None; BARGRAPH_RESOLUTION as usize];
let (row, green) = row_in.div_mod_floor(&2);
for position in 0..ht16k33::COMMONS_SIZE {
let check = 1 << position;
let (count, common) = (position as u8).div_mod_floor(&4);
let remainder = row * 4 + common;
#[allow(clippy::blacklisted_name)]
let bar = count * 12 + remainder;
let enabled = check == common_in & check;
if enabled {
bars[bar as usize] = if green == 1 {
Some(LedColor::Green)
} else {
Some(LedColor::Red)
};
} else {
bars[bar as usize] = Some(LedColor::Off);
}
}
trace!(self.logger, "row_common_to_bars"; "row" => row_in, "common" => format!("{:#010b}", common_in), "bars" => format!("{:?}", bars));
bars
}
fn display_ascii_bargraph(&self, leds: &[LedColor], display: Display) {
println!(
"{corner_top_left}{line}{corner_top_right}",
corner_top_left = White.paint("\u{2554}"),
line = White.paint(
std::iter::repeat("\u{2550}")
.take(leds.len() as usize)
.collect::<String>()
),
corner_top_right = White.paint("\u{2557}")
);
print!("{side}", side = White.paint("\u{2551}"),);
for led in leds.iter() {
let mut style = Style::new();
if display == Display::HALF_HZ
|| display == Display::ONE_HZ
|| display == Display::TWO_HZ
{
style = style.blink();
}
let mut color = match led {
LedColor::Green => style.fg(Green),
LedColor::Red => style.fg(Red),
LedColor::Yellow => style.fg(Yellow),
LedColor::Off => style.fg(Fixed(238)), };
print!("{}", color.paint(BARGRAPH_DISPLAY_CHAR));
}
println!("{side}", side = White.paint("\u{2551}"),);
println!(
"{corner_bottom_left}{line}{corner_bottom_right}",
corner_bottom_left = White.paint("\u{255A}"),
line = White.paint(
std::iter::repeat("\u{2550}")
.take(leds.len() as usize)
.collect::<String>()
),
corner_bottom_right = White.paint("\u{255D}")
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use ht16k33::i2c_mock::I2cMock;
const ADDRESS: u8 = 0;
#[test]
fn new() {
let i2c = I2cMock::new(None);
let _bargraph = Bargraph::new(i2c, ADDRESS, None);
}
#[test]
fn initialize() {
let i2c = I2cMock::new(None);
let mut bargraph = Bargraph::new(i2c, ADDRESS, None);
bargraph.initialize().unwrap();
}
#[test]
fn clear() {
let i2c = I2cMock::new(None);
let mut bargraph = Bargraph::new(i2c, ADDRESS, None);
bargraph.initialize().unwrap();
bargraph.clear().unwrap();
}
#[test]
fn update() {
let i2c = I2cMock::new(None);
let mut bargraph = Bargraph::new(i2c, ADDRESS, None);
bargraph.initialize().unwrap();
bargraph.update(5, 6, false).unwrap();
}
#[test]
fn set_blink() {
let i2c = I2cMock::new(None);
let mut bargraph = Bargraph::new(i2c, ADDRESS, None);
bargraph.initialize().unwrap();
bargraph.set_blink(true).unwrap();
bargraph.set_blink(false).unwrap();
}
#[test]
fn show() {
let i2c = I2cMock::new(None);
let mut bargraph = Bargraph::new(i2c, ADDRESS, None);
bargraph.initialize().unwrap();
bargraph.show().unwrap();
}
}