Break a serialport loop with keyboard input

Hi! I wrote this code with the serialport-rs and dialoguer crate.

fn run(
    serial: &mut Box<dyn SerialPort>,
    faders: &HashMap<u8, u8>,
    midi: &mut MidiOutputConnection,
) {
    let stop = Arc::new(AtomicBool::new(false));
    let stop_me = stop.clone();
    let mut buf: [u8; 2] = [0; 2];
    let thread = thread::spawn(move || {
        let input = ask_for_quitting();
        if input == "q" {
            stop_me.store(true, Ordering::Relaxed);
        }
    });
    loop {
        serial.read(buf.as_mut_slice()).unwrap();
        let arduino_pin = &buf[0];
        let value = &buf[1];
        let Some(cc) = faders.get(&arduino_pin) else { continue };
        let message = midi_control::control_change(Channel::Ch1, *cc, *value);
        let message_byte: Vec<u8> = message.into();
        let _ = &mut midi.send(&message_byte).unwrap();
        if stop.load(Ordering::Relaxed) == true {
            break;
        }
    }
    thread.join().unwrap();
}

As you can see, I'm using an atomic bool to communicate between the key input thread and the serial loop. When I press "q" during the loop, the loop doesn't break. It breaks only when I get a new buffer read from the serial port.
How can I immediately interrupt the loop by pressing the key input?
Thanks.

Out of curiousity, have you put your terminal in "raw mode"?

By default a terminal will only send input to the program it is connected to when a newline is entered.

When I press "q" I have to press Enter too. It's by design from the dialogue crate in input mode.
I noticed that if I comment serial.read() then pressing Enter the program quit as expected.

I'm guessing the read() method is blocking and won't return until it receives data. Maybe check if there is an option for setting a timeout so the read() method will error out after a pre-defined time (e.g. 100ms) and give you a chance to check the stop flag again.

You are right, thanks! I changed my serial code and now it works:

let serial_read = serial.read(buf.as_mut_slice());
match serial_read {
    Ok(_) => {},
    Err(_e) => {
        if stop.load(Ordering::Relaxed) == true {
            break;
        }
    }
};

is there a more elegant way to do this?

No sorry...serialport jokes with me :smile: I accidentally move my potentiometer in arduino ad this break the loop, not the match pattern that I added...

unfortunately, the serialport crate only support reading the serial port via std::io::Read trait, you can't do much besides setting a timeout and polling the device for reading.

you can try tokio-serial, I don't know how the dialoguer crate would fit tokio though:

#[tokio::main]
async fn main() {
	let mut port = tokio_serial::new("COM1", 9600).open_native_async().unwrap();
	let stop = Arc::new(tokio::sync::Notify::new());
	let notifier = stop.clone();
	// you can spawn regular thread just fine, you don't have to spawn async task, 
	// I suppose dialoguer would work just fine, but I didn't test it
	tokio::task::spawn_blocking(move || {
		let input = ask_for_quitting();
		if input == "q" {
			notifier.notify_one();
		}
	});
	let mut buf = [0; 1024];
	loop {
		tokio::select! {
			io_result = port.read(&mut buf) => {
				let nbytes = io_result.unwrap();
				println!("received {} bytes", nbytes);
				let data = &buf[..nbytes];
				// use data
			}
			_ = stop.notified() => {
				println!("notified");
				break;
			}
		}
	}
}

Thanks @nerditation, I found a similar solution using atomic bool variables.

fn run(
    serial: &mut Box<dyn SerialPort>,
    faders: &HashMap<u8, u8>,
    midi: &mut MidiOutputConnection,
) {
    let stop = Arc::new(AtomicBool::new(false));
    let stop_me = stop.clone();
    let mut buf: [u8; 2] = [0; 2];
    let mut previous_buf: Vec<u8> = vec![0,0];
    let thread = thread::spawn(move || {
        let input = ask_for_quitting();
        if input == "q" {
            stop_me.store(true, Ordering::Relaxed);
        }
    });
    loop {
        let serial_read = serial.read(buf.as_mut_slice());
        match serial_read {
            Ok(_) => {
               // Other stuff
            },
            Err(_e) => {
                if stop.load(Ordering::Relaxed) == true {
                    break;
                }
            }
        };
    }
    thread.join().unwrap();
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.