diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..6fad019 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.aarch64-unknown-linux-musl] +linker = "lld" diff --git a/.forgejo/workflows/buildcheck.yaml b/.forgejo/workflows/buildcheck.yaml deleted file mode 100644 index dc1b053..0000000 --- a/.forgejo/workflows/buildcheck.yaml +++ /dev/null @@ -1,43 +0,0 @@ -name: Basic Cargo Checks -run-name: ${{ github.actor }} is testing -on: [push] -jobs: - docker-check: - runs-on: docker - steps: - - name: Grab misc. dependencies - run: | - apt update && apt install -y librust-libudev-sys-dev build-essential - - name: Check out repository code - uses: actions/checkout@v3 - - name: Grab Rust toolchain - uses: https://github.com/actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - target: aarch64-unknown-linux-gnu - override: true - - name: Run check - uses: https://github.com/actions-rs/cargo@v1 - with: - command: check - docker-build: - runs-on: docker - steps: - - name: Grab misc. dependencies - run: | - apt update && apt install -y librust-libudev-sys-dev build-essential - - name: Check out repository code - uses: actions/checkout@v3 - - name: Grab Rust toolchain - uses: https://github.com/actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - target: aarch64-unknown-linux-gnu - override: true - - name: Run check - uses: https://github.com/actions-rs/cargo@v1 - with: - command: build - args: --release diff --git a/.gitignore b/.gitignore index 806522a..1f4a548 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /target /logs -/output \ No newline at end of file +/output +.cargo +.rustup diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..9a514ad --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,21 @@ +pipeline: + build: + image: nixos/nix:latest + commands: + - nix-shell --run 'cargo build --release --target aarch64-unknown-linux-musl' + publish: + when: + event: tag + image: woodpeckerci/plugin-gitea-release + settings: + base_url: https://git.blizzard.systems + files: + - "target/aarch64-unknown-linux-musl/release/seymour_life" + checksum: + - "target/aarch64-unknown-linux-musl/release/seymour_life" + api_key: + from_secret: API_KEY + target: ${CI_COMMIT_TAG} + draft: true + prerelease: true + title: .woodpecker/title.txt diff --git a/.woodpecker/title.txt b/.woodpecker/title.txt new file mode 100644 index 0000000..f706a60 --- /dev/null +++ b/.woodpecker/title.txt @@ -0,0 +1 @@ +v2.3.2 diff --git a/Cargo.lock b/Cargo.lock index 3c9061b..f61b6a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,26 +382,6 @@ version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" -[[package]] -name = "libudev" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0" -dependencies = [ - "libc", - "libudev-sys", -] - -[[package]] -name = "libudev-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" -dependencies = [ - "libc", - "pkg-config", -] - [[package]] name = "link-cplusplus" version = "1.0.8" @@ -486,12 +466,6 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -630,7 +604,6 @@ dependencies = [ "IOKit-sys", "bitflags", "cfg-if", - "libudev", "mach 0.3.2", "nix", "regex", @@ -639,7 +612,7 @@ dependencies = [ [[package]] name = "seymour_life" -version = "2.3.1" +version = "2.3.2" dependencies = [ "chrono", "clap", diff --git a/Cargo.toml b/Cargo.toml index a8598f7..81c36cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,18 @@ +#This feature is currently limited to nightly versions of cargo. #cargo-features = ["per-package-target"] [package] name = "seymour_life" -version = "2.3.1" +version = "2.3.2" edition = "2021" +#This feature is currently limited to nightly versions of cargo. +#forced-target="aarch64-unknown-linux-musl" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] rppal = "0.14.1" -serialport = "4.2.0" +serialport = { version = "4.2.0", default-features = false } log = "0.4" fern = "0.6.2" chrono = "0.4.24" diff --git a/README.md b/README.md index ec1f967..b720aa5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![status-badge](https://ci.blizzard.systems/api/badges/blizzardfinnegan/seymourLifeRust/status.svg?branch=stable)](https://ci.blizzard.systems/blizzardfinnegan/seymourLifeRust) # Seymour Life This is a personal/professional project, which makes use of serial communications (tty over USB via UART) and Raspberry Pi GPIO to simulate long term use of a Seymour device. @@ -23,9 +24,7 @@ Note that this command MUST be run as `sudo`/root, due to the way it interacts w ## Build From Source -Note: *At this time, this project can only reliably be built on Linux. Build instructions for Windows will be written eventually.* - -To build this project from source, first, download the repository. This can be done by using the Download ZIP button, or running the following command in a terminal where `git` is installed: +To build this project from source *ON A RASPBERRY PI*, first, download the repository. This can be done by using the Download ZIP button, or running the following command in a terminal where `git` is installed: ```bash git clone https://git.blizzard.systems/blizzardfinnegan/seymourLifeRust ``` @@ -44,28 +43,19 @@ sudo ./target/release/seymour_life You can also build without the `--release` flag, which wil take less time, but will be less optimised for the hardware. If you do this, substitue `./target/release/seymour_life` for `./target/debug/seymour_life` in the above command. -### Build Dependencies -The following dependencies are also necessary for building this project: -- `pkg-config` -- `libudev` +## Cross-Compilation -See below for platform specific requirements. +Cross compilation is possible with this project, if you do not have a Raspberry Pi available specifically for compilation. Compilation directly on the Pi is rather intensive, and takes significantly longer than cross-compiling. -#### Debian-based -This applies for all distributions of Linux using the `apt` package manager, including but not limited to Debian, Ubuntu, Raspbian/Raspberry Pi OS, and Linux Mint. +If you are compiling on Linux, cross-compilation has a dependency of `lld`. This can be found in your distribution's package manager, or directly distributed by LLVM. For Nix users, a predefined `shell.nix` file has been provided for your convenience. + +If you are compiling on Windows, to safely cross-compile, you must modify the `.cargo/config.toml` file. Replace `lld` with `rust-lld`, then cross-compilation should work properly. ```bash -sudo apt-get install librust-libudev-sys-dev librust-pkg-config-dev +cargo build --target aarch64-unknown-linux-musl +# OR +cargo build --release --target aarch64-unknown-linux-musl ``` -#### Fedora-based -This applies for all distributions of Linux using the `dnf` package manager, including but not limited to CentOS, Redhat Enterprise Linux (RHEL), and Fedora. -```bash -sudo dnf install rust-libudev-sys-devel rust-pkg-config-devel -``` -#### Nix -This applies to both NixOS, and any distribution where the [Nix package manager](https://nixos.org/download.html) can be installed. - -If you have the Nix package manager installed, this project comes with a `shell.nix` containing the necessary build dependencies. Simply run `nix-shell` to download the necessary dependencies. diff --git a/build_requirements.md b/build_requirements.md deleted file mode 100644 index d957683..0000000 --- a/build_requirements.md +++ /dev/null @@ -1,2 +0,0 @@ -librust-udev-sys-dev -pkg-config diff --git a/notes.md b/notes.md deleted file mode 100644 index db3d9d9..0000000 --- a/notes.md +++ /dev/null @@ -1,7 +0,0 @@ -Auto-serial: - -`echo 'y1q' | python3 -m debugmenu` contains the serial number. Search keyword is: "DtCtrlCfgDeviceSerialNum" -split on `\n`, collect into Vec<&str>. Iterate over Vec: - if doesn't contain colon, continue - split_once on colon - if first half is keyword, trim second half, remove `"` characters, save as serial diff --git a/shell.nix b/shell.nix index 9d75c26..478c392 100644 --- a/shell.nix +++ b/shell.nix @@ -1,4 +1,15 @@ {pkgs ? import {} }: - pkgs.mkShell { - nativeBuildInputs = with pkgs; [ cargo rustc pkg-config libudev-zero ]; + pkgs.mkShell rec { + buildInputs = with pkgs; [ lld rustup ]; + RUSTUP_HOME = toString ./.rustup; + CARGO_HOME = toString ./.cargo; + RUSTUP_TOOLCHAIN = "stable"; + HOST_ARCH = "x86_64-unknown-linux-gnu"; + CARGO_BUILD_TARGET = "aarch64-unknown-linux-musl"; + shellHook = '' + export PATH=$PATH:${CARGO_HOME}/bin + export PATH=$PATH:${RUSTUP_HOME}/toolchains/${RUSTUP_TOOLCHAIN}-${HOST_ARCH}/bin/ + + rustup target add "${CARGO_BUILD_TARGET}" + ''; } diff --git a/src/device.rs b/src/device.rs index 8c563dc..975f4f8 100755 --- a/src/device.rs +++ b/src/device.rs @@ -3,10 +3,11 @@ 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 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)] @@ -30,6 +31,7 @@ pub struct Device{ reboots: u64, temps: u64, init_temps: u64, + temp_offset: u64, bps: u64 } @@ -38,7 +40,7 @@ impl Device{ if ! Path::new(&OUTPUT_FOLDER).is_dir(){ _ = fs::create_dir(&OUTPUT_FOLDER); }; - log::debug!("{:?}",&self.serial); + //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); @@ -61,24 +63,29 @@ impl Device{ log::trace!("{:?}",file_contents); for line in file_lines { if line.len() > 0{ - log::trace!("{:?}",line); - let section_and_data:Vec<&str> = line.split(": ").collect(); + //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 = section_and_data[1].trim().parse::(); match possible_value{ Ok(value) => { - log::trace!("{:?} value: [{:?}]",section,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; + self.bps = value.clone(); + //log::trace!("BPS set to {:?}",self.bps); }, TEMP_SECTION => { - self.temps = value; + 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(_) => { @@ -113,10 +120,8 @@ impl Device{ Response::BPOn | Response::BPOff | Response::TempCount(_) | Response::DebugMenu=>{ usb_port.write_to_device(Command::Quit); - _ = usb_port.read_from_device(None); - usb_port.write_to_device(Command::Newline); match usb_port.read_from_device(None){ - Response::Rebooting => { + Response::ShuttingDown | Response::Rebooting => { while usb_port.read_from_device(None) != Response::LoginPrompt {} initial_state = State::LoginPrompt; }, @@ -152,6 +157,7 @@ impl Device{ current_state: initial_state, reboots: 0, temps: 0, + temp_offset: 0, init_temps: 0, bps: 0 }; @@ -195,7 +201,7 @@ impl Device{ log::error!("Unexpected response from device {}!",self.serial); log::debug!("brightness 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; + //break; }, }; }; @@ -222,7 +228,7 @@ impl Device{ log::error!("Unexpected response from device {}!", self.serial); log::debug!("brightness 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; + //break; }, }; }; @@ -266,7 +272,7 @@ impl Device{ 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; + //break; }, }; }; @@ -293,7 +299,7 @@ impl Device{ 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; + //break; }, }; }; @@ -318,21 +324,21 @@ impl Device{ return false } } - log::trace!("Writing to file: {:?}",self.output_file); if let Some(ref mut file_name) = self.output_file{ - log::debug!("Writing to 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); - log::trace!("Current temps: [{}]",self.temps); - log::trace!("Initial temps: [{}]",self.init_temps); - let saved_temps = self.temps - self.init_temps; + 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) => { @@ -524,18 +530,20 @@ impl Device{ pub fn reboot(&mut self) -> () { self.usb_tty.write_to_device(Command::Quit); let mut successful_reboot:bool = false; - let mut exited_menu: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; - if !exited_menu { log::info!("Unusual reboot detected for device {}. Please check logs.",self.serial); } + //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; + //exited_menu = true; }, _ => {} } @@ -570,7 +578,7 @@ impl Device{ log::trace!("Has bp ended on device {}? : {:?}",self.serial,bp_end); if bp_start != bp_end { self.bps +=1; - log::debug!("Increasing bp count for device {} to {}",self.serial,self.bps); + log::trace!("Increasing bp count for device {} to {}",self.serial,self.bps); self.save_values(); } } diff --git a/src/main.rs b/src/main.rs index 04e76f3..c307afc 100755 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use clap::Parser; #[derive(Parser,Debug)] #[command(author,version,about)] struct Args{ - /// Print all logs to screen. Sets iteration count to 50000 + /// Print all logs to screen, improves log verbosity. Sets iteration count to 50000 #[arg(short,long,action)] debug:bool, @@ -19,13 +19,13 @@ struct Args{ #[arg(short,long,action)] manual:bool, - /// Set iteration count from command line + /// Set iteration count from command line. Overrides debug iteration count. #[arg(short,long)] iterations:Option } -const VERSION:&str="2.3.1"; +const VERSION:&str="2.3.2"; const DEBUG_ITERATION_COUNT:u64=50000; fn int_input_filtering(prompt:Option<&str>) -> u64{ @@ -63,23 +63,22 @@ fn main(){ let args = Args::parse(); setup_logs(&args.debug); log::info!("Seymour Life Testing version: {}",VERSION); - if args.debug{ - log::debug!("Debug enabled!"); - } + log::trace!("Debug enabled!"); loop{ let mut iteration_count:u64 = 0; - if args.debug { - iteration_count = DEBUG_ITERATION_COUNT; - } - else if let Some(value) = args.iterations{ + if let Some(value) = args.iterations{ iteration_count = value; } + else if args.debug { + iteration_count = DEBUG_ITERATION_COUNT; + } else { while iteration_count < 1{ iteration_count = int_input_filtering(Some("Enter the number of iterations to complete: ")); } } + log::info!("Testing all available USB ports for connected devices. This may take several minutes, and devices may reboot several times."); let gpio = &mut GpioPins::new(); match std::fs::read_dir("/dev/serial/by-path"){ Ok(available_ttys)=>{ @@ -93,7 +92,7 @@ fn main(){ Ok(tty_real_ref)=>{ let tty_path = tty_real_ref.path(); let tty_name = tty_path.to_string_lossy(); - log::info!("Testing port {}. This may take a moment...",&tty_name); + log::debug!("Testing port {}",&tty_name); let possible_port = TTY::new(&tty_name); match possible_port{ Some(mut port) =>{ @@ -145,6 +144,7 @@ fn main(){ log::info!("Number of devices detected: {}",devices.len()); log::info!("--------------------------------------\n\n"); + log::info!("Setting up probe wells for all devices. This may take several minutes..."); for device in devices.iter_mut(){ if !serials_set || args.manual { device.brighten_screen(); @@ -157,7 +157,7 @@ fn main(){ device.set_pin_address(21); log::error!("Unable to find probe-well for device {}. Please ensure that the probe well is installed properly, and the calibration key is plugged in.",device.get_serial()); device.brighten_screen(); - return; + panic!(); } } @@ -217,14 +217,18 @@ pub fn setup_logs(debug:&bool){ message )) }) - .chain( - fern::Dispatch::new() - .level(log::LevelFilter::Trace) - .chain(fern::log_file( - format!("logs/{0}.log", - chrono_now.format("%Y-%m-%d_%H.%M").to_string() - )).unwrap()), - ) + .chain({ + let mut file_logger = fern::Dispatch::new(); + let date_format = chrono_now.format("%Y-%m-%d_%H.%M").to_string(); + let local_log_file = fern::log_file(format!("logs/{}.log",date_format)).unwrap(); + if *debug{ + file_logger = file_logger.level(log::LevelFilter::Trace); + } + else { + file_logger = file_logger.level(log::LevelFilter::Debug); + } + file_logger.chain(local_log_file) + }) .chain({ let mut stdout_logger = fern::Dispatch::new(); if *debug { diff --git a/src/tty.rs b/src/tty.rs index 42a680c..c1e8afe 100755 --- a/src/tty.rs +++ b/src/tty.rs @@ -7,7 +7,7 @@ use serialport::SerialPort; use derivative::Derivative; const BAUD_RATE:u32 = 115200; -const SERIAL_READ_TIMEOUT: std::time::Duration = Duration::from_millis(500); +const SERIAL_TIMEOUT: std::time::Duration = Duration::from_millis(500); #[derive(Eq,Derivative,Debug)] @@ -88,7 +88,7 @@ const RESPONSES:[(&str,Response);13] = [ pub struct TTY{ tty: Box, - failed_read_count: u8 + last: Command, } impl std::fmt::Debug for TTY{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result{ @@ -112,23 +112,24 @@ impl std::fmt::Debug for TTY{ impl TTY{ pub fn new(serial_location:&str) -> Option{ - let possible_tty = serialport::new(serial_location,BAUD_RATE).timeout(SERIAL_READ_TIMEOUT).open(); + let possible_tty = serialport::new(serial_location,BAUD_RATE).timeout(SERIAL_TIMEOUT).open(); if let Ok(tty) = possible_tty{ - Some(TTY { - tty, - failed_read_count: 0 - }) + Some(TTY{tty,last:Command::Quit}) } 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())); + if command == self.last{ + log::trace!("retry send {}",self.tty.name().unwrap_or("unknown".to_string())); + }else{ + log::debug!("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.last = command; _ = 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)); + std::thread::sleep(SERIAL_TIMEOUT); return output; } @@ -150,7 +151,6 @@ impl TTY{ 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(){ @@ -165,7 +165,6 @@ impl TTY{ 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)) } @@ -192,17 +191,7 @@ impl TTY{ 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); - } + log::trace!("Read an empty string from device {:?}. Possible read error.", self); return Response::Empty; }; }