seymourLifeRust/src/device.rs
Blizzard Finnegan 32789a4546
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Add catch for uboot
Apparently, you can just run "boot" in a uboot prompt to get to the
login prompt.
2023-06-27 09:00:06 -04:00

626 lines
28 KiB
Rust
Executable file

use std::{fs::{self, File}, path::Path, io::Write, thread, time::Duration};
use crate::tty::{TTY, Response,Command};
use rppal::gpio::{Gpio,OutputPin};
const TEMP_WAIT:Duration = Duration::from_secs(3);
const REBOOTS_SECTION: &str = "Reboots";
const BP_SECTION: &str = "Successful BP tests";
const TEMP_SECTION: &str = "Successful temp tests";
const OUTPUT_FOLDER: &str = "output/";
const SECTION_SEPARATOR: &str = ": ";
const UNINITIALISED_SERIAL: &str = "uninitialised";
const SERIAL_HEADER: &str = "DtCtrlCfgDeviceSerialNum";
#[derive(PartialEq,Debug)]
pub enum State{
Shutdown,
LoginPrompt,
DebugMenu,
LifecycleMenu,
BrightnessMenu,
ShellPrompt
}
#[derive(Debug)]
pub struct Device{
usb_tty:TTY,
output_file: Option<File>,
gpio: rppal::gpio::Gpio,
address: Option<u8>,
pin: Option<OutputPin>,
serial: String,
current_state: State,
reboots: u64,
temps: u64,
init_temps: u64,
temp_offset: u64,
bps: u64
}
impl Device{
fn load_values(&mut self) -> bool {
if ! Path::new(&OUTPUT_FOLDER).is_dir(){
_ = fs::create_dir(&OUTPUT_FOLDER);
};
//log::debug!("{:?}",&self.serial);
let output_path:String = OUTPUT_FOLDER.to_owned() + &self.serial + ".txt";
if ! Path::new(&output_path).exists(){
log::debug!("Creating file {}",&output_path);
let temp:Result<File, std::io::Error> = fs::File::create(&output_path);
match temp{
Ok(file) => {
self.output_file = Some(file);
self.save_values();
}
Err(_) => {
return false
}
}
}
else {
let temp:Result<String, std::io::Error> = std::fs::read_to_string(output_path);
match temp{
Ok(file_contents) =>{
let file_lines:Vec<&str> = file_contents.split("\n").collect();
log::trace!("{:?}",file_contents);
for line in file_lines {
if line.len() > 0{
//log::trace!("{:?}",line);
let section_and_data:Vec<&str> = line.split(SECTION_SEPARATOR).collect();
let section:&str = section_and_data[0];
let possible_value:Result<u64, std::num::ParseIntError> = section_and_data[1].trim().parse::<u64>();
match possible_value{
Ok(value) => {
//log::trace!("{:?} value: [{:?}]",section,value);
match section {
REBOOTS_SECTION => {
self.reboots = value;
//log::trace!("Reboots set to {:?}",self.reboots);
},
BP_SECTION => {
self.bps = value.clone();
//log::trace!("BPS set to {:?}",self.bps);
},
TEMP_SECTION => {
self.temp_offset = value;
//log::trace!("Temp offset set to {:?}",self.temp_offset);
},
_ => {
log::warn!("Invalid import value: [{:?}]. Please ensure that the output directory is clean.",section_and_data);
}
};
}
Err(_) => {
log::warn!("Unable to parse value [{:?}] into integer",section_and_data);
}
}
};
};
},
Err(error) => {
log::warn!("Could not load from file!");
log::debug!("{}",error);
}
}
};
return true
}
pub fn new(mut usb_port:TTY,response:Option<Response>) -> Result<Self,String>{
let initial_state:State;
match response{
Some(response_value)=> {
match response_value{
Response::PasswordPrompt=>{
usb_port.write_to_device(Command::Newline);
_ = usb_port.read_from_device(None);
initial_state = State::LoginPrompt;
},
Response::UBoot=>{
usb_port.write_to_device(Command::Boot);
while usb_port.read_from_device(None) != Response::LoginPrompt {}
initial_state = State::LoginPrompt;
},
//Response::Empty parsing here is potentially in bad faith
Response::Other | Response::Empty | Response::ShellPrompt | Response::FailedDebugMenu | Response::DebugInit |
Response::LoginPrompt | Response::ShuttingDown | Response::Rebooting | Response::PreShellPrompt =>
initial_state = State::LoginPrompt,
Response::BPOn | Response::BPOff | Response::TempCount(_) |
Response::DebugMenu=>{
usb_port.write_to_device(Command::Quit);
match usb_port.read_from_device(None){
Response::ShuttingDown | Response::Rebooting => {
while usb_port.read_from_device(None) != Response::LoginPrompt {}
initial_state = State::LoginPrompt;
},
Response::ShellPrompt => {
initial_state = State::ShellPrompt;
},
Response::DebugMenu => {
usb_port.write_to_device(Command::Newline);
match usb_port.read_from_device(None) {
Response::DebugMenu | Response::ShellPrompt => {
initial_state = State::ShellPrompt;
},
_ => {
log::error!("Unknown state for TTY {:?}!!! Consult logs immediately.",usb_port);
log::debug!("Last known state: DebugMenu.");
log::debug!("Assumed but incorrect current state: successfully exited debug menu");
return Err("Failed TTY init. Unknown state, cannot trust.".to_string());
}
};
},
_ => {
log::error!("Unknown state for TTY {:?}!!! Consult logs immediately.",usb_port);
log::debug!("Last known state: DebugMenu.");
log::debug!("Assumed but incorrect current state: attempted to exit debug menu");
return Err("Failed TTY init. Unknown state, cannot trust.".to_string());
}
};
},
//Serial response shouldn't exist, emptynewline is already filtered in main
Response::Serial(_) | Response::EmptyNewline => {
log::error!("Unknown state for TTY {:?}!!! Consult logs immediately.",usb_port);
log::debug!("How did I get here???");
return Err("Failed TTY init. Unknown state, cannot trust.".to_string());
},
};
},
None => initial_state = State::LoginPrompt
};
let temp = Gpio::new();
match temp{
Ok(gpio) =>{
let mut output = Self{
usb_tty: usb_port,
gpio,
address: None,
pin: None,
output_file: None,
serial: UNINITIALISED_SERIAL.to_string(),
current_state: initial_state,
reboots: 0,
temps: 0,
temp_offset: 0,
init_temps: 0,
bps: 0
};
if !output.load_values(){
log::warn!("Could not load values from file! File may be overwritten.");
}
return Ok(output);
}
Err(error) => {
log::warn!("Failed to init GPIO!");
log::debug!("{}",error);
return Err("Failed GPIO init".to_string());
}
}
}
fn go_to_brightness_menu(&mut self) -> &mut Self{
while !(self.current_state == State::BrightnessMenu){
match self.current_state {
State::BrightnessMenu => return self,
State::DebugMenu => {
self.usb_tty.write_to_device(Command::LifecycleMenu);
_ = self.usb_tty.read_from_device(None);
self.current_state = State::LifecycleMenu;
},
State::LifecycleMenu =>{
self.usb_tty.write_to_device(Command::BrightnessMenu);
_ = self.usb_tty.read_from_device(None);
self.current_state = State::BrightnessMenu;
return self;
},
State::LoginPrompt => {
self.usb_tty.write_to_device(Command::Login);
loop {
match self.usb_tty.read_from_device(None){
Response::PreShellPrompt | Response::Empty | Response::ShuttingDown |
Response::DebugInit | Response::EmptyNewline | Response::Rebooting => {},
Response::PasswordPrompt => {self.usb_tty.write_to_device(Command::Newline);},
Response::FailedDebugMenu | Response::ShellPrompt => break,
_ => {
log::error!("Unexpected response from device {}!",self.serial);
log::debug!("brightness menu, catch-all, login loop, {}, {:?}",self.serial,self.usb_tty);
log::error!("Unsure how to continue. Expect data from device {} to be erratic until next cycle.",self.serial);
//break;
},
};
};
self.current_state = State::ShellPrompt;
},
State::ShellPrompt => {
self.usb_tty.write_to_device(Command::DebugMenu);
loop {
match self.usb_tty.read_from_device(None) {
Response::PreShellPrompt | Response::Empty | Response::ShuttingDown |
Response::DebugInit | Response::EmptyNewline | Response::Rebooting => {},
Response::LoginPrompt => {
self.usb_tty.write_to_device(Command::Login);
while self.usb_tty.read_from_device(None) != Response::ShellPrompt {};
self.usb_tty.write_to_device(Command::DebugMenu);
},
Response::DebugMenu =>
break,
Response::FailedDebugMenu => {
self.usb_tty.write_to_device(Command::DebugMenu);
},
_ => {
log::error!("Unexpected response from device {}!", self.serial);
log::debug!("brightness menu, catch-all, shell prompt loop, {}, {:?}",self.serial,self.usb_tty);
log::error!("Unsure how to continue. Expect data from device {} to be erratic until next cycle.",self.serial);
//break;
},
};
};
//_ = self.usb_tty.read_from_device(None);
self.current_state = State::DebugMenu;
},
State::Shutdown => {
while self.usb_tty.read_from_device(None) != Response::LoginPrompt{}
self.current_state = State::LoginPrompt;
},
};
};
return self;
}
fn go_to_lifecycle_menu(&mut self) -> &mut Self{
while !(self.current_state == State::LifecycleMenu){
match self.current_state {
State::LifecycleMenu => return self,
State::DebugMenu => {
self.usb_tty.write_to_device(Command::LifecycleMenu);
_ = self.usb_tty.read_from_device(None);
self.current_state = State::LifecycleMenu;
return self;
},
State::BrightnessMenu =>{
self.usb_tty.write_to_device(Command::UpMenuLevel);
_ = self.usb_tty.read_from_device(None);
self.current_state = State::LifecycleMenu;
return self;
},
State::LoginPrompt => {
self.usb_tty.write_to_device(Command::Login);
loop {
match self.usb_tty.read_from_device(None){
Response::PreShellPrompt | Response::Empty | Response::ShuttingDown |
Response::DebugInit | Response::EmptyNewline | Response::Rebooting => {},
Response::PasswordPrompt => {self.usb_tty.write_to_device(Command::Newline);},
Response::FailedDebugMenu | Response::ShellPrompt => break,
_ => {
log::error!("Unexpected response from device {}!",self.serial);
log::debug!("lifecycle menu, catch-all, first loop, {}, {:?}",self.serial,self.usb_tty);
log::error!("Unsure how to continue. Expect data from device {} to be erratic until next cycle.",self.serial);
//break;
},
};
};
self.current_state = State::ShellPrompt;
},
State::ShellPrompt => {
self.usb_tty.write_to_device(Command::DebugMenu);
loop {
let read_in = self.usb_tty.read_from_device(None);
match read_in {
Response::PreShellPrompt | Response::Empty | Response::ShuttingDown |
Response::DebugInit | Response::EmptyNewline | Response::Rebooting => {},
Response::LoginPrompt => {
self.usb_tty.write_to_device(Command::DebugMenu);
},
Response::DebugMenu =>
break,
Response::FailedDebugMenu => {
self.usb_tty.write_to_device(Command::DebugMenu);
},
_ => {
log::error!("Unexpected response from device {}! {:?}", self.serial, read_in);
log::debug!("lifecycle menu, catch-all, second loop, {}, {:?}",self.serial,self.usb_tty);
log::error!("Unsure how to continue. Expect data from device {} to be erratic until next cycle.",self.serial);
//break;
},
};
};
self.current_state = State::DebugMenu;
},
State::Shutdown => {
while self.usb_tty.read_from_device(None) != Response::LoginPrompt {}
self.current_state = State::LoginPrompt;
},
};
};
return self;
}
fn save_values(&mut self) -> bool{
let output_path = OUTPUT_FOLDER.to_owned() + &self.serial + ".txt";
let temp = fs::OpenOptions::new().write(true).truncate(true).open(&output_path);
match temp{
Ok(opened_file) => self.output_file = Some(opened_file),
Err(_) => {
log::warn!("Could not open file [{}] to write! Potential permissions error.",&output_path);
return false
}
}
if let Some(ref mut file_name) = self.output_file{
let mut output_data = REBOOTS_SECTION.to_string();
output_data.push_str(SECTION_SEPARATOR);
output_data.push_str(&self.reboots.to_string());
output_data.push_str("\n");
output_data.push_str(BP_SECTION);
output_data.push_str(SECTION_SEPARATOR);
output_data.push_str(&self.bps.to_string());
output_data.push_str("\n");
output_data.push_str(TEMP_SECTION);
output_data.push_str(SECTION_SEPARATOR);
let saved_temps = (self.temps - self.init_temps) + self.temp_offset;
output_data.push_str(&saved_temps.to_string());
output_data.push_str("\n");
log::debug!("final data to write to '{:?}': [{:?}]",file_name,output_data);
let temp = file_name.write_all(output_data.as_bytes());
match temp{
Err(error) => {
log::warn!("{}",error);
},
_ => {}
}
}
else {
log::warn!("Cannot write to output file!");
}
return true
}
pub fn auto_set_serial(&mut self) -> bool{
loop {
match self.current_state {
State::LoginPrompt => {
self.usb_tty.write_to_device(Command::Login);
while self.usb_tty.read_from_device(None) != Response::ShellPrompt {};
self.current_state = State::ShellPrompt;
},
State::Shutdown => {
while self.usb_tty.read_from_device(None) != Response::LoginPrompt{};
self.current_state = State::LoginPrompt;
},
State::DebugMenu | State::LifecycleMenu | State::BrightnessMenu => {
self.usb_tty.write_to_device(Command::Quit);
_ = self.usb_tty.read_from_device(None);
self.current_state = State::ShellPrompt;
},
State::ShellPrompt => {
self.usb_tty.write_to_device(Command::GetSerial);
loop{
let return_value = self.usb_tty.read_from_device(None);
match return_value{
Response::Serial(Some(contains_serial)) =>{
for line in contains_serial.split("\n").collect::<Vec<&str>>(){
if !line.contains(':') { continue; }
let (section,value) = line.split_once(':').unwrap();
if section.contains(SERIAL_HEADER){
self.serial = value.trim().replace("\"","");
}
}
log::info!("Serial found for device {}",self.serial);
break;
},
Response::DebugInit | Response::Empty | Response::EmptyNewline => { continue; }
_ => {
log::error!("Bad value: {:?}",return_value);
return false
},
}
}
self.usb_tty.write_to_device(Command::DebugMenu);
while self.usb_tty.read_from_device(None) != Response::DebugMenu {}
self.current_state = State::DebugMenu;
self.load_values();
self.save_values();
return true
},
}
}
}
pub fn manual_set_serial(&mut self, serial:&str) -> &mut Self{
self.serial = serial.to_string();
self.load_values();
self.save_values();
return self;
}
pub fn get_serial(&self) -> &str{
&self.serial
}
pub fn get_location(&mut self) -> String{
std::format!("{:?}",self.usb_tty)
}
pub fn set_pin_address(&mut self, address:u8) -> &mut Self{
self.address = Some(address.clone());
let temp = self.gpio.get(address);
match temp{
Ok(pin) => self.pin = Some(pin.into_output()),
Err(error) => {
log::warn!("Could not set pin to this address {}; already assigned?",address);
log::debug!("{}",error);
}
}
return self;
}
pub fn start_temp(&mut self) -> &mut Self {
if let Some(ref mut pin) = self.pin {
pin.set_high();
}
return self;
}
pub fn stop_temp(&mut self) -> &mut Self {
if let Some(ref mut pin) = self.pin {
pin.set_low();
}
return self;
}
fn start_bp(&mut self) -> &mut Self {
self.go_to_lifecycle_menu();
self.usb_tty.write_to_device(Command::StartBP);
_ = self.usb_tty.read_from_device(None);
return self;
}
pub fn darken_screen(&mut self) -> &mut Self {
self.go_to_brightness_menu();
self.usb_tty.write_to_device(Command::BrightnessLow);
_ = self.usb_tty.read_from_device(None);
return self;
}
pub fn brighten_screen(&mut self) -> &mut Self {
self.go_to_brightness_menu();
self.usb_tty.write_to_device(Command::BrightnessHigh);
_ = self.usb_tty.read_from_device(None);
return self;
}
pub fn is_temp_running(&mut self) -> bool{
self.go_to_lifecycle_menu();
self.usb_tty.write_to_device(Command::ReadTemp);
for _ in 0..10 {
match self.usb_tty.read_from_device(None){
Response::TempCount(Some(count)) => return count != self.init_temps ,
_ => {},
}
}
self.usb_tty.write_to_device(Command::ReadTemp);
for _ in 0..10{
match self.usb_tty.read_from_device(None){
Response::TempCount(Some(count)) => return count != self.init_temps ,
_ => {},
}
}
log::error!("Temp read failed!!!");
return false
}
pub fn update_temp_count(&mut self) -> u64 {
self.go_to_lifecycle_menu();
self.usb_tty.write_to_device(Command::ReadTemp);
for _ in 0..10 {
match self.usb_tty.read_from_device(None){
Response::TempCount(Some(count)) => {
log::trace!("Count for device {} updated to {}",self.serial,count);
self.temps = count;
return count
},
_ => {},
}
}
self.usb_tty.write_to_device(Command::ReadTemp);
for _ in 0..10{
match self.usb_tty.read_from_device(None){
Response::TempCount(Some(count)) => {
log::trace!("Count for device {} updated to {}",self.serial,count);
self.temps = count;
return count
},
_ => {},
}
}
log::error!("Update temp count on device {} failed!!!",self.serial);
return 0;
}
pub fn init_temp_count(&mut self){
self.go_to_lifecycle_menu();
self.usb_tty.write_to_device(Command::ReadTemp);
for _ in 0..10 {
match self.usb_tty.read_from_device(None){
Response::TempCount(Some(count)) => {
log::trace!("init temp count set to {} on device {}",count,self.serial);
self.init_temps = count;
return
},
_ => {},
}
};
self.usb_tty.write_to_device(Command::ReadTemp);
for _ in 0..10{
match self.usb_tty.read_from_device(None){
Response::TempCount(Some(count)) => {
log::trace!("init temp count set to {} on device {}",count,self.serial);
self.init_temps = count;
return
},
_ => {},
}
};
log::error!("init temp count failed on device {}!!!",self.serial);
}
fn is_bp_running(&mut self) -> bool {
self.go_to_lifecycle_menu();
self.usb_tty.write_to_device(Command::CheckBPState);
loop {
match self.usb_tty.read_from_device(None){
Response::BPOn => return true,
Response::BPOff => return false,
_ => return false,
}
}
}
pub fn reboot(&mut self) -> () {
self.usb_tty.write_to_device(Command::Quit);
self.usb_tty.write_to_device(Command::Reboot);
let mut successful_reboot:bool = false;
//let mut exited_menu:bool = false;
loop{
match self.usb_tty.read_from_device(None){
Response::LoginPrompt => break,
Response::Rebooting => {
log::trace!("Successful reboot detected for device {}.",self.serial);
successful_reboot = true;
//This error message is turning out to be more false positive than anything
//else. Reboots can sometimes dump both reboot flag and shutdown flag at once.
//if !exited_menu { log::info!("Unusual reboot detected for device {}. Please check logs.",self.serial); }
},
Response::ShuttingDown => {
log::trace!("Exiting debug menu on device {}.",self.serial);
//exited_menu = true;
},
_ => {}
}
};
if successful_reboot { self.reboots += 1; }
self.current_state = State::LoginPrompt;
}
pub fn test_cycle(&mut self, bp_cycles: Option<u64>) -> () {
let local_bp_cycles: u64 = bp_cycles.unwrap_or(3);
if self.current_state != State::LoginPrompt { self.reboot(); }
self.go_to_lifecycle_menu();
_ = self.usb_tty.read_from_device(Some("["));
self.update_temp_count();
for _bp_count in 1..=local_bp_cycles{
log::info!("Running bp {} on device {} ...",(self.bps+1),self.serial);
self.start_bp();
let bp_start:bool = self.is_bp_running();
log::trace!("Has bp started on device {}? : {:?}",self.serial,bp_start);
if bp_start{
log::trace!("Starting temp on device {}",self.serial);
self.start_temp();
thread::sleep(TEMP_WAIT);
log::trace!("Stopping temp on device {}",self.serial);
self.stop_temp();
};
while self.is_bp_running() {};
let bp_end = self.is_bp_running();
log::trace!("Has bp ended on device {}? : {:?}",self.serial,bp_end);
if bp_start != bp_end {
self.bps +=1;
log::trace!("Increasing bp count for device {} to {}",self.serial,self.bps);
self.save_values();
}
}
log::info!("Rebooting {} for the {}th time",self.serial, self.reboots);
self.reboot();
self.save_values();
}
}