mod motor; use crate::motor::{Motor, MotorConfig, MotorPins}; use anyhow::{Context, anyhow}; use clap::{Parser, Subcommand}; use rppal::gpio::Gpio; use std::fs; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; #[derive(Parser)] struct Opt { #[clap(short, long, default_value = "/etc/motor.toml")] config: PathBuf, #[clap(subcommand)] command: Command, } #[derive(Subcommand)] enum Command { /// Reset motor pins, making sure the motor is off Reset, /// Turn the motor Turn { /// The direction to turn #[clap(subcommand)] direction: Direction, /// The number of full stepper motor steps to turn, if not specified the motor will turn /// forever #[clap(long, short)] steps: Option, }, } #[derive(Clone, Copy, Subcommand)] pub enum Direction { Left, Right, } fn load_config(path: &Path) -> anyhow::Result { fs::read_to_string(path) .context("Failed to open/read file") .and_then(|file| toml::from_str(&file).context("Failed to deserialize config")) .context(anyhow!("Error opening reading file at {path:?}")) } fn main() -> anyhow::Result<()> { let opt = Opt::parse(); let config = load_config(&opt.config)?; let exit = Box::leak(Box::new(AtomicBool::new(false))); ctrlc::set_handler(|| { exit.store(true, Ordering::Release); }) .expect("Error setting Ctrl-C handler"); let gpio = Gpio::new().context("Failed to access GPIOs")?; match &opt.command { Command::Reset => { let _ = MotorPins::new(&gpio, config.pin_mappings); } Command::Turn { direction, steps } => { let mut motor = Motor::new(&gpio, &config)?; for i in 0.. { if exit.load(Ordering::Acquire) { break; } motor.spin_step(*direction); if i % 100 == 0 { println!("{i}"); } if let Some(steps) = steps && i >= *steps { break; } } } } Ok(()) }