seymourLifeRust/src/tty.rs
Blizzard Finnegan 94cbba27cf
All checks were successful
Basic Cargo Checks / docker-build (push) Successful in 1m57s
Basic Cargo Checks / docker-check (push) Successful in 2m1s
Change priority of serial
2023-06-19 14:35:09 -04:00

209 lines
8.2 KiB
Rust
Executable file

use std::{collections::HashMap,
io::{BufReader, Write, Read},
boxed::Box,
time::Duration};
use once_cell::sync::Lazy;
use serialport::SerialPort;
use derivative::Derivative;
const BAUD_RATE:u32 = 115200;
const SERIAL_READ_TIMEOUT: std::time::Duration = Duration::from_millis(500);
#[derive(Eq,Derivative,Debug)]
#[derivative(PartialEq, Hash)]
pub enum Command{
Quit,
StartBP,
CheckBPState,
LifecycleMenu,
BrightnessMenu,
BrightnessLow,
BrightnessHigh,
ReadTemp,
UpMenuLevel,
RedrawMenu,
Login,
DebugMenu,
Newline,
Shutdown,
GetSerial,
}
#[derive(Clone,Eq,Derivative,Debug)]
#[derivative(PartialEq, Hash)]
pub enum Response{
PasswordPrompt,
ShellPrompt,
BPOn,
BPOff,
TempCount(Option<u64>),
LoginPrompt,
DebugMenu,
Rebooting,
Other,
Empty,
ShuttingDown,
FailedDebugMenu,
PreShellPrompt,
EmptyNewline,
DebugInit,
Serial(Option<String>),
}
const COMMAND_MAP:Lazy<HashMap<Command,&str>> = Lazy::new(||HashMap::from([
(Command::Quit, "q\n"),
(Command::StartBP, "N"),
(Command::CheckBPState, "n"),
(Command::LifecycleMenu, "L"),
(Command::BrightnessMenu, "B"),
(Command::BrightnessHigh, "0"),
(Command::BrightnessLow, "1"),
(Command::ReadTemp, "H"),
(Command::UpMenuLevel, "\\"),
(Command::Login,"root\n"),
(Command::RedrawMenu,"?"),
(Command::DebugMenu," python3 -m debugmenu; shutdown -r now\n"),
(Command::Newline,"\n"),
(Command::Shutdown,"shutdown -r now\n"),
(Command::GetSerial,"echo 'y1q' | python3 -m debugmenu\n"),
]));
const RESPONSES:[(&str,Response);13] = [
("Last login:",Response::PreShellPrompt),
("reboot: Restarting",Response::Rebooting),
("command not found",Response::FailedDebugMenu),
("login:",Response::LoginPrompt),
("Password:",Response::PasswordPrompt),
("DtCtrlCfgDeviceSerialNum",Response::Serial(None)),
("root@",Response::ShellPrompt),
("EXIT Debug menu",Response::ShuttingDown),
("Check NIBP In Progress: True",Response::BPOn),
("Check NIBP In Progress: False",Response::BPOff),
("SureTemp Probe Pulls:",Response::TempCount(None)),
(">",Response::DebugMenu),
("Loading App-Framework",Response::DebugInit),
];
pub struct TTY{
tty: Box<dyn SerialPort>,
failed_read_count: u8
}
impl std::fmt::Debug for TTY{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result{
let absolute_location = self.tty.name();
let relative_location:String;
match absolute_location{
Some(abs_location_string) => {
let sectioned_abs_location = abs_location_string.rsplit_once('/');
match sectioned_abs_location{
Some((_,serial_device_name)) => relative_location = serial_device_name.to_string(),
None => relative_location = "unknown".to_string()
}
},
None => relative_location = "unknown".to_string()
};
f.debug_struct("TTY")
.field("Serial port name",&relative_location)
.finish()
}
}
impl TTY{
pub fn new(serial_location:&str) -> Option<Self>{
let possible_tty = serialport::new(serial_location,BAUD_RATE).timeout(SERIAL_READ_TIMEOUT).open();
if let Ok(tty) = possible_tty{
Some(TTY {
tty,
failed_read_count: 0
})
} else{
None
}
}
pub fn write_to_device(&mut self,command:Command) -> bool {
log::trace!("writing {:?} to tty {}...", command, self.tty.name().unwrap_or("unknown".to_string()));
let output = self.tty.write_all(COMMAND_MAP.get(&command).unwrap().as_bytes()).is_ok();
_ = self.tty.flush();
if command == Command::Login { std::thread::sleep(std::time::Duration::from_secs(2)); }
std::thread::sleep(std::time::Duration::from_millis(500));
return output;
}
pub fn read_from_device(&mut self,_break_char:Option<&str>) -> Response {
let mut reader = BufReader::new(&mut self.tty);
let mut read_buffer: Vec<u8> = Vec::new();
_ = reader.read_to_end(&mut read_buffer);
if read_buffer.len() > 0 {
let read_line:String = String::from_utf8_lossy(read_buffer.as_slice()).to_string();
if read_line.eq("\r\n") {
return Response::EmptyNewline;
}
for (string,enum_value) in RESPONSES{
if read_line.contains(string){
if(enum_value == Response::BPOn) || (enum_value == Response::BPOff) {
//Don't log BPOn or BPOff, we're gonna see a LOT of those and we don't want
//to overfill the SD card
}
else{
log::trace!("Successful read of {:?} from tty {}, which matches pattern {:?}",read_line,self.tty.name().unwrap_or("unknown shell".to_string()),enum_value);
};
self.failed_read_count = 0;
if enum_value == Response::TempCount(None){
let mut lines = read_line.lines();
while let Some(single_line) = lines.next(){
if single_line.contains(string){
let trimmed_line = single_line.trim();
match trimmed_line.rsplit_once(' '){
None => return enum_value,
Some((_header,temp_count)) => {
match temp_count.trim().parse::<u64>(){
Err(_) => {
log::error!("String {} from device {} unable to be parsed!",temp_count,self.tty.name().unwrap_or("unknown shell".to_string()));
return Response::TempCount(None)
},
Ok(parsed_temp_count) => {
//log::trace!("Header: {}",header);
log::trace!("parsed temp count for device {}: {}",self.tty.name().unwrap_or("unknown shell".to_string()),temp_count);
return Response::TempCount(Some(parsed_temp_count))
}
}
}
}
}
}
}
else if enum_value == Response::Serial(None) {
return Response::Serial(Some(read_line));
}
else if enum_value == Response::PasswordPrompt {
log::error!("Recieved password prompt on device {}! Something fell apart here. Check preceeding log lines.",self.tty.name().unwrap_or("unknown shell".to_string()));
self.write_to_device(Command::Newline);
_ = self.read_from_device(None);
}
else{
return enum_value;
}
}
}
log::trace!("Unable to determine response. Response string is: [{:?}]",read_line);
return Response::Other;
}
else {
log::debug!("Read an empty string from device {:?}. Possible read error.", self);
//Due to a linux kernel power-saving setting that is overly complicated to fix,
//Serial connections will drop for a moment before re-opening, at seemingly-random
//intervals. The below is an attempt to catch and recover from this behaviour.
self.failed_read_count += 1;
if self.failed_read_count >= 15{
self.failed_read_count = 0;
let tty_location = self.tty.name().expect("Unable to read tty name!");
self.tty = serialport::new(tty_location,BAUD_RATE).timeout(SERIAL_READ_TIMEOUT).open().expect("Unable to open serial connection!");
return self.read_from_device(_break_char);
}
return Response::Empty;
};
}
}