ebyte_e32_ui/
lib.rs

1//! Ebyte module control.
2
3#![doc = include_str!("../README.md")]
4
5use anyhow::Context;
6use ebyte_e32::{mode::Normal, Ebyte};
7use embedded_hal::{
8    blocking::delay,
9    digital::v2::{InputPin, OutputPin},
10    serial::{self, Read, Write},
11};
12use linux_embedded_hal::{
13    gpio_cdev::{Chip, LineRequestFlags},
14    serial_core::{BaudRate, CharSize, FlowControl, PortSettings, SerialPort},
15    serial_unix::TTYPort,
16    CdevPin as Pin, {Delay, Serial},
17};
18use nb::block;
19use rustyline::{error::ReadlineError, Editor};
20use std::fmt::Debug;
21
22use {
23    arguments::{Args, Mode},
24    config::{load, StopBits},
25};
26
27/// Configuration from `Config.toml`.
28pub mod config;
29
30/// Command line interface.
31pub mod arguments;
32
33/// Setup the hardware, then load some parameters,
34/// update them if needed, then listen, send, or read model data.
35///
36/// # Panics
37/// Failed initialization of the module driver
38/// or communicating with the module may cause a panic.
39pub fn create(args: &Args) -> anyhow::Result<Ebyte<Serial, Pin, Pin, Pin, Delay, Normal>> {
40    let config = load(&args.config).context("Failed to get config")?;
41    let baud_rate = BaudRate::from_speed(config.baudrate as usize);
42    let stop_bits = StopBits::try_from(config.stop_bits)
43        .context("Failed to parse stop bits")?
44        .into();
45    let settings: PortSettings = PortSettings {
46        baud_rate,
47        char_size: CharSize::Bits8,
48        parity: config.parity.into(),
49        stop_bits,
50        flow_control: FlowControl::FlowNone,
51    };
52
53    let mut serial = TTYPort::open(&config.serial_path)
54        .with_context(|| format!("Failed to open TTY {}", config.serial_path.display()))?;
55    serial
56        .configure(&settings)
57        .context("Failed to set up serial port")?;
58    let serial = Serial(serial);
59
60    let mut gpiochip = Chip::new(&config.gpiochip_path)
61        .with_context(|| format!("Failed to open gpiochip {}", config.gpiochip_path.display()))?;
62
63    let aux = gpiochip
64        .get_line(config.aux_pin)
65        .context("Failed to get AUX line")?
66        .request(LineRequestFlags::INPUT, 0, "ebyte-e32-ui")
67        .context("Failed to request settings for AUX pin")?;
68    let aux = Pin::new(aux).context("Failed to create AUX CDEV pin")?;
69
70    let m0 = gpiochip
71        .get_line(config.m0_pin)
72        .context("Failed to get M0 line")?
73        .request(LineRequestFlags::OUTPUT, 0, "ebyte-e32-ui")
74        .context("Failed to request settings for M0 pin")?;
75    let m0 = Pin::new(m0).context("Failed to create M0 CDEV pin")?;
76
77    let m1 = gpiochip
78        .get_line(config.m1_pin)
79        .context("Failed to get M1 line")?
80        .request(LineRequestFlags::OUTPUT, 0, "ebyte-e32-ui")
81        .context("Failed to request settings for M1 pin")?;
82    let m1 = Pin::new(m1).context("Failed to create M1 CDEV pin")?;
83
84    Ok(Ebyte::new(serial, aux, m0, m1, Delay).expect("Failed to initialize driver"))
85}
86
87pub fn run<S, AUX, M0, M1, D>(
88    args: &Args,
89    mut ebyte: Ebyte<S, AUX, M0, M1, D, Normal>,
90) -> anyhow::Result<()>
91where
92    S: serial::Read<u8> + serial::Write<u8>,
93    <S as serial::Read<u8>>::Error: Debug,
94    <S as serial::Write<u8>>::Error: Debug,
95    AUX: InputPin,
96    M0: OutputPin,
97    M1: OutputPin,
98    D: delay::DelayMs<u32>,
99{
100    match args.mode {
101        Mode::Send => send(ebyte),
102        Mode::Listen => loop {
103            let b = block!(ebyte.read()).expect("Failed to read");
104            print!("{}", b as char);
105            std::io::Write::flush(&mut std::io::stdout()).expect("Failed to flush");
106        },
107        Mode::ReadModelData => {
108            println!("Reading model data");
109            let model_data = ebyte.model_data().expect("Failed to read model data");
110            println!("{model_data:#?}");
111            Ok(())
112        }
113        Mode::ReadParameters => {
114            println!("Reading parameter data");
115            let parameters = ebyte.parameters().expect("Failed to read parameter data");
116            println!("{parameters:#?}");
117            Ok(())
118        }
119        Mode::Configure(ref parameters) => configure(ebyte, parameters),
120    }
121}
122
123fn send<S, AUX, M0, M1, D>(
124    mut ebyte: Ebyte<S, AUX, M0, M1, D, ebyte_e32::mode::Normal>,
125) -> anyhow::Result<()>
126where
127    S: serial::Read<u8> + serial::Write<u8>,
128    <S as serial::Read<u8>>::Error: Debug,
129    <S as serial::Write<u8>>::Error: Debug,
130    AUX: InputPin,
131    M0: OutputPin,
132    M1: OutputPin,
133    D: delay::DelayMs<u32>,
134{
135    let mut prompt = Editor::<()>::new().expect("Failed to set up prompt");
136    loop {
137        match prompt.readline("Enter message >> ") {
138            Ok(line) => {
139                if line == "exit" || line == "quit" {
140                    break;
141                }
142                prompt.add_history_entry(&line);
143
144                for b in line.as_bytes() {
145                    block!(ebyte.write(*b)).expect("Failed to write");
146                    print!("{}", *b as char);
147                    std::io::Write::flush(&mut std::io::stdout()).expect("Failed to flush");
148                }
149                block!(ebyte.write(b'\n')).expect("Failed to write");
150                println!();
151            }
152            Err(ReadlineError::Interrupted) => {
153                println!("CTRL-C");
154                break;
155            }
156            Err(ReadlineError::Eof) => {
157                println!("CTRL-D");
158                break;
159            }
160            Err(err) => {
161                println!("Error: {err:?}");
162                break;
163            }
164        }
165    }
166    Ok(())
167}
168
169/// Apply new parameters to the Ebyte module, optionally persisting them.
170/// 1. Read old parameters from module.
171/// 2. Check if the given parameters and the loaded ones are equal, if so, bail.
172/// 3. Apply new parameters.
173/// 4. Read them back, warning if they are not equal to the given parameters.
174fn configure<S, AUX, M0, M1, D>(
175    mut ebyte: Ebyte<S, AUX, M0, M1, D, ebyte_e32::mode::Normal>,
176    parameters: &arguments::Parameters,
177) -> anyhow::Result<()>
178where
179    S: serial::Read<u8> + serial::Write<u8>,
180    <S as serial::Read<u8>>::Error: Debug,
181    <S as serial::Write<u8>>::Error: Debug,
182    AUX: InputPin,
183    M0: OutputPin,
184    M1: OutputPin,
185    D: delay::DelayMs<u32>,
186{
187    println!("Loading existing parameters");
188    let old_params = ebyte
189        .parameters()
190        .expect("Failed to read existing parameters");
191    println!("Loaded parameters: {old_params:#?}");
192
193    // Create Ebyte parameters from argument parameters.
194    let new_params = ebyte_e32::Parameters::from(parameters);
195
196    if new_params == old_params {
197        println!("Leaving parameters unchanged");
198    } else {
199        println!(
200            "Updating parameters (persistence: {:?})",
201            parameters.persistence
202        );
203        ebyte
204            .set_parameters(&new_params, parameters.persistence)
205            .expect("Failed to set new parameters");
206
207        // Check if it worked.
208        let current_params = ebyte
209            .parameters()
210            .expect("Failed to read current parameters");
211        if current_params != new_params {
212            eprintln!("Error: parameters unchanged: {current_params:#?}");
213        }
214    }
215    Ok(())
216}