Concorrência Segura com Portas Série em Rust

Integrar dispositivos seriais em aplicações concorrentes em Rust exige atenção especial à sincronização de acesso, já que a maioria das bibliotecas não oferece implementações Send e Sync nativas para instâncias de porta. Abaixo apresentamos três abordagens distintas — cada uma utliizando uma biblioteca diferente — com foco em segurança de thread, tratamento de codificação e controle robusto de tempo.

Abordagem 1: Uso de Arc<Mutex<T>> com serial

A primeira solução emprega o crate serial (versão 0.4), combinando Arc para compartilhamento entre threads e Mutex para exclusão mútua no acesso à porta. A codificação GBK é aplicada explicitamente tanto na escrita quanto na leitura, com tratamento tolerante a erros via DecoderTrap::Ignore e EncoderTrap::Ignore.

use std::io::{Read, Write}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; use serial::prelude::*; use encoding::{DecoderTrap, EncoderTrap}; use encoding::all::GBK;

fn main() { let config = serial::PortSettings { baud_rate: serial::Baud9600, char_size: serial::Bits8, parity: serial::ParityNone, stop_bits: serial::Stop1, flow_control: serial::FlowNone, };

let device = serial::open("COM41").expect("Falha ao abrir porta");
device.configure(&config).expect("Falha na configuração");
device.set_timeout(Duration::from_millis(10)).expect("Falha ao definir timeout");

let shared_port = Arc::new(Mutex::new(device));
let reader_port = Arc::clone(&shared_port);

let reader_handle = thread::spawn(move || {
    let mut buffer = [0u8; 256];
    for _ in 0..100 {
        match reader_port.lock().unwrap().read(&mut buffer) {
            Ok(bytes) if bytes > 0 => {
                let decoded = GBK.decode(&buffer[..bytes], DecoderTrap::Ignore)
                    .unwrap_or_else(|_| String::new());
                println!("Recebido: {}", decoded.trim());
            }
            _ => {
                println!("Nenhum dado disponível");
                thread::sleep(Duration::from_millis(300));
            }
        }
    }
});

for _ in 0..10 {
    {
        let mut port_ref = shared_port.lock().unwrap();
        let encoded_cmd = GBK.encode("at\r\n", EncoderTrap::Ignore)
            .expect("Falha ao codificar comando");
        port_ref.write_all(&encoded_cmd).ok();
    }
    thread::sleep(Duration::from_millis(300));
}

reader_handle.join().ok();

}


</div>### Abordagem 2: Clonagem segura com `serialport`

O crate `serialport` (v4.2) suporta clonagem nativa de instâncias de porta (`try_clone`), eliminando a necessidade de `Mutex` para leitura e escrita simultâneas. Isso simplifica o modelo de concorrência e reduz contenção. O exemplo abaixo usa um buffer fixo de 32 bytes e processa respostas como sequências brutas de bytes, exibindo tamanho e conteúdo recebidos.

<div class="code-block">```
[dependencies]
serialport = "4.2"
tokio = { version = "1", features = ["full"]" } # opcional, para extensões futuras

use std::{io::Read, time::Duration};
use serialport::{prelude::*, SerialPort};

fn main() {
    if let Ok(mut tx_port) = serialport::new("COM3", 115200)
        .timeout(Duration::from_millis(20))
        .open()
    {
        if let Ok(rx_port) = tx_port.try_clone() {
            std::thread::spawn(move || {
                let mut buf = [0u8; 32];
                loop {
                    match rx_port.read(&mut buf) {
                        Ok(n) => {
                            println!("Recebidos {} bytes: {:?}", n, &buf[..n]);
                        }
                        Err(e) => {
                            eprintln!("Erro na leitura: {}", e);
                            break;
                        }
                    }
                }
            });
        }

        for _ in 0..1000 {
            if let Ok(n) = tx_port.write(b"at\r\n") {
                println!("Enviados {} bytes", n);
            }
            thread::sleep(Duration::from_millis(10));
        }
    } else {
        eprintln!("Não foi possível inicializar a porta série");
    }
}

Embora não recomendado para código produtivo, essa abordagem ilustra o uso de variáveis estáticas mutáveis com unsafe para compartilhar uma instância de porta entre threads — útil em cenários embarcados ou de baixo nível. A biblioteca serial-rs é utilizada com ajuste fino de temporização via esleep, permitindo pausas em microssegundos para comunicação com dispositivos sensíveis a timing.

use std::io::{Read, Write};

static mut PORT_INSTANCE: Option<:windows::comport> = None;

fn main() { if let Ok(port) = serial_rs::windows::COMPort::new( "COM112".to_string(), Some(serial_rs::SerialPortSettings::default().baud(115200)), ) { unsafe { PORT_INSTANCE = Some(port) }; }

std::thread::spawn(|| {
    loop {
        unsafe {
            if let Some(ref mut p) = PORT_INSTANCE {
                let _ = p.write(b"1\n");
            }
        }
        esleep::us(1);
    }
});

std::thread::spawn(|| {
    let mut buf = [0u8; 1024];
    loop {
        unsafe {
            if let Some(ref mut p) = PORT_INSTANCE {
                if let Ok(n) = p.read(&mut buf) {
                    let s = String::from_utf8_lossy(&buf[..n]);
                    println!("Resposta: {}", s.trim());
                }
            }
        }
        esleep::us(100);
    }
});

loop {
    esleep::ms(200);
}

}</:windows::comport>


</div>

Tags: Rust serial-port Concurrency Multithreading serial-rs

Publicado em 10-25 15:06