use core::fmt; use crate::lcd_gui::LCDScreen; //---Config struct---------------------------------------------------------------------------------- #[derive(Copy, Clone)] pub enum FanOutput { P1, P2, } impl FanOutput { pub fn from_bits(bits: u16) -> FanOutput { if bits == 0 { return Self::P1 } return Self::P2; } pub fn to_bits(&self) -> u16 { match self { Self::P1 => 0, Self::P2 => 1, } } } #[derive(Copy, Clone)] pub struct CalData { pub offset: f32, pub raw_value: Option, } impl CalData { pub fn new(offset: f32) -> CalData { CalData { offset, raw_value: None, } } } #[derive(Copy, Clone)] pub struct SystemConfig { pub max_temp_diff: f32, pub min_temp_diff: f32, pub ext_offset: CalData, pub p1_offset: CalData, pub p2_offset: CalData, pub fan_output: FanOutput, } impl SystemConfig { pub fn new() -> SystemConfig { SystemConfig { max_temp_diff: 8.0, min_temp_diff: 4.0, ext_offset: CalData::new(0.0), p1_offset: CalData::new(0.0), p2_offset: CalData::new(0.0), fan_output: FanOutput::P1, } } } //---Menu struct------------------------------------------------------------------------------------ const MENU_ENTRY_NB: usize = 6; pub enum MenuState { Scroll, Entry, } pub struct Menu { entries: [Entry; MENU_ENTRY_NB], state: MenuState, should_refresh: bool, entry_index: usize, } impl Menu { pub fn new(config: &SystemConfig) -> Menu { Menu { entries: [ Entry::new("fan out", EntryValue::FanOut (FanOutput::P1), config), Entry::new("max diff", EntryValue::MaxTempDiff (0.0), config), Entry::new("min diff", EntryValue::MinTempDiff (0.0), config), Entry::new("ex", EntryValue::ExtOffset (CalData::new(0.0)), config), Entry::new("s1", EntryValue::P1Offset (CalData::new(0.0)), config), Entry::new("s2", EntryValue::P2Offset (CalData::new(0.0)), config), ], state: MenuState::Scroll, should_refresh: true, entry_index: 0, } } pub fn button_update(&mut self, config: &mut SystemConfig) -> bool { self.should_refresh = true; // update state machine match self.state { MenuState::Scroll => { if self.entry_index == 0 { return true } // read desired value from config self.state = MenuState::Entry; self.entries[self.entry_index - 1].select(config); }, MenuState::Entry => { // write modified value to config self.state = MenuState::Scroll; self.entries[self.entry_index - 1].deselect(config); } } false } pub fn movement_update(&mut self, movement: i32) { if movement != 0 { match self.state { MenuState::Scroll => { // convert movement to entry index let mut index = self.entry_index as i32 + movement; if index > MENU_ENTRY_NB as i32 { index = MENU_ENTRY_NB as i32; } else if index < 0 { index = 0; } self.entry_index = index as usize; }, MenuState::Entry => { // update entry self.entries[self.entry_index - 1].update(-movement); //note : "-" sign is just to revert encoder direction, feels better when using //the interface }, } self.should_refresh = true; } } pub fn reset(&mut self) { match self.state { MenuState::Entry => { // if an entry is selected, disguard all modifications self.entries[self.entry_index - 1].disgard(); self.state = MenuState::Scroll; }, _ => (), } self.entry_index = 0; } pub fn display(&mut self, screen: &mut L) { if self.should_refresh { // first line screen.clear(); screen.set_cursor_pos(0); let _ = write!(screen, "\u{007e}"); if self.entry_index == 0 { let _ = write!(screen, "retour"); } else { self.entries[self.entry_index - 1].display(screen); } // second line (if any) if self.entry_index < MENU_ENTRY_NB { screen.set_cursor_pos(0x40); let _ = write!(screen, " "); self.entries[self.entry_index].display(screen); } self.should_refresh = false; } } } pub enum EntryValue { MaxTempDiff (f32), MinTempDiff (f32), ExtOffset (CalData), P1Offset (CalData), P2Offset (CalData), FanOut (FanOutput), } impl EntryValue { fn modify(&mut self, n: i32) { use libm::{powf, copysignf, fabsf}; use crate::utils::TEMP_RANGE; match self { Self::MaxTempDiff (val) | Self::MinTempDiff (val) => { *val += copysignf(fabsf(n as f32) * 0.85 * powf(10.0, 0.15 * fabsf(n as f32)) * 0.1, n as f32); // avoid display issues by limiting value range if *val > 99.9 { *val = 99.9; } else if *val < -99.9 { *val = -99.9; } }, Self::ExtOffset (data) | Self::P1Offset (data) | Self::P2Offset (data) => { data.offset += copysignf(fabsf(n as f32) * 0.85 * powf(10.0, 0.15 * fabsf(n as f32)) * 0.1, n as f32); // avoid limiting issues like before, taking probe bounds into account if data.offset + TEMP_RANGE.end > 99.9 { data.offset = 99.9 - TEMP_RANGE.end; } else if data.offset + TEMP_RANGE.start < -99.9 { data.offset = -99.9 - TEMP_RANGE.start; } }, Self::FanOut (output) => { if n%2 != 0 { match output { FanOutput::P1 => *output = FanOutput::P2, FanOutput::P2 => *output = FanOutput::P1, }}}} } fn load(&mut self, config: &SystemConfig) { match self { Self::MaxTempDiff (val) => *val = config.max_temp_diff, Self::MinTempDiff (val) => *val = config.min_temp_diff, Self::ExtOffset (data) => *data = config.ext_offset, Self::P1Offset (data) => *data = config.p1_offset, Self::P2Offset (data) => *data = config.p2_offset, Self::FanOut (val) => *val = config.fan_output, }; } fn store(&mut self, config: &mut SystemConfig) { match self { Self::MaxTempDiff (val) => config.max_temp_diff = *val, Self::MinTempDiff (val) => config.min_temp_diff = *val, Self::ExtOffset (data) => config.ext_offset = *data, Self::P1Offset (data) => config.p1_offset = *data, Self::P2Offset (data) => config.p2_offset = *data, Self::FanOut (val) => config.fan_output = *val, }; } } impl fmt::Display for EntryValue { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { use crate::lcd_gui::Temp; match self { Self::MaxTempDiff (val) | Self::MinTempDiff (val) => formatter.write_fmt(format_args!("{:.1}", val)), Self::ExtOffset (data) | Self::P1Offset (data) | Self::P2Offset (data) => { let temp: Temp = data.raw_value.map(|val| val + data.offset).into(); formatter.write_fmt(format_args!("{:.1}({:.1})", data.offset, temp)) }, Self::FanOut (output) => { match output { FanOutput::P1 => formatter.write_fmt(format_args!("1")), FanOutput::P2 => formatter.write_fmt(format_args!("2")), }}} } } pub struct Entry { text: &'static str, value: EntryValue, is_selected: bool, } impl Entry { pub fn new(text: &'static str, mut value: EntryValue, config: &SystemConfig) -> Entry { value.load(config); Entry { text, value, is_selected: false, } } pub fn display(&self, buffer: &mut B) { let _ = match self.is_selected { true => write!(buffer, "{}:{}", self.text, self.value), false => write!(buffer, "{} {}", self.text, self.value), }; } pub fn select(&mut self, config: &SystemConfig) { self.is_selected = true; self.value.load(config); } pub fn deselect(&mut self, config: &mut SystemConfig) { self.is_selected = false; self.value.store(config); } pub fn disgard(&mut self) { self.is_selected = false; } pub fn update(&mut self, n: i32) { self.value.modify(n); } }