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>