A Espressif tem investido bastante na linguagem Rust nesses últimos anos, e já possui bibliotecas em Rust que utilizam a Espressif IoT Development Framework (ESP-IDF).
Neste artigo vamos ver os como utilizar Rust com ESP-IDF, criando um cliente MQTT utilizando ESP32.
Introdução
Os controladores ESP da Espressif têm um suporte mais amplo para Rust em comparação com outros controladores embarcados, como podemos ver no livro “The Rust on ESP Book” e no repositório geral do ESP para Rust. Enquanto muitas plataformas suportam apenas o desenvolvimento no-std, voltado ao Bare Metal, Espressif oferece suporte também para o desenvolvimento std, ou seja, utilização de mais camadas de abstração para o desenvolvimento, e possibilidade de utilizar funções padrão Rust.
No entanto, essa maior diversidade pode levar a uma desvantagem significativa: a confusão. A variedade de interfaces de crates (nome para bibliotecas em Rust) adicionadas e exemplos que misturam diferentes níveis de abstração é muito grande, o que gera dificuldade de adaptação ou até de entendimento. Confesso que também me sinto um pouco confuso diante da variedade de crates disponíveis.
Ao utilizamos o ESP-IDF, teremos uma maior quantidade de abstrações, como podemos ver na Figura 01 abaixo, e também, de maneira integrada, será utilizado FreeRTOS Kernel com ele.
MQTT
O MQTT, abreviação de Message Queuing Telemetry Transport, é um protocolo de comunicação eficiente e leve desenvolvido especialmente para a Internet das Coisas (IoT) e redes com recursos limitados.O MQTT tornou-se amplamente adotado devido à sua simplicidade, baixo consumo de recursos e capacidade de facilitar a comunicação em tempo real entre dispositivos. A necessidade de um protocolo de mensagens confiável e dimensionável, capaz de lidar com as limitações comuns dos dispositivos IoT, como poder de processamento limitado, largura de banda restrita e conectividade intermitente, foi o impulso por trás do desenvolvimento do MQTT.
Além de ser um protocolo de comunicação eficiente, o MQTT opera em um modelo de publicação e subscrição (publish-subscribe), onde os dispositivos se comunicam através de um componente intermediário chamado broker. O broker funciona como um intermediário entre os publishers, que são os dispositivos que enviam as mensagens, e os subscribers, que são os dispositivos que recebem as mensagens. Ele é responsável por rotear as mensagens dos publishers para os subscribers corretos, garantindo uma comunicação eficiente e escalável. Com essa arquitetura, o MQTT permite uma comunicação assíncrona entre os dispositivos, facilitando a troca de informações em tempo real em ambientes distribuídos, como é comum na Internet das Coisas (IoT).
Criando ambiente de desenvolvimento
Criaremos um ambiente de desenvolvimento no Linux Ubuntu 22.04. Os primeiros passos da criação do ambiente podem ser lidos no artigo “ESP32: Primeiros Passos com Rust”.
Após a execução dos passos iniciais mencionados no artigo, faz-se necessário instalar pacotes adicionais para utilização do ESP-IDF.
1 – Instalar binários adicionais no linux (caso já não os tenham instalados):
sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 libudev-dev pkg-config
2 – Pacotes dentro do cargo:
cargo install cargo-generate ldproxy
3 – Executar um sh para definicao de variaveis ambientes do ESP:
. $HOME/export-esp.sh
Detalhes adicionais ou caso precise instalar em outras versões de sistema operacional, utilize o link do esp-idf-template.
Criando um projeto
Agora que já estamos com as bibliotecas adicionais instaladas, vamos criar um projeto baseado no template do ESP-IDF:
cargo generate esp-rs/esp-idf-template cargo
Esse template pedirá algumas informações para criação do projeto para o modelo específico:
- Project name: Nome do projeto, no nosso caso esp32-esp-idf-mqtt
- MCU: No nosso caso esp32
- Configure advanced template options: True (para adicionarmos alguns crates)
- Enable STD support? : True (pois usaremos std)
- Configure project to use Dev Containers : False
- ESP-IDF- version: v5.1
- Configure project to support Wokwi Simulation : False
- Add CI Files to Github Action? : False
Será criado uma pasta chamada esp32-esp-idf-mqtt com os arquivos do projeto. Descrevi com mais detalhes os arquivos do projeto no artigo dos primeiros passos.
Para garantir que toda a instalação previamente feita está ok, executemos cargo build. Esse processo pode demorar um tempo dependendo do seu PC pois existe uma grande quantidade de pacotes necessários (~208 crates).
O Cargo criará uma pasta com os arquivos do projeto com o arquivo main.rs como pode-se ver abaixo. Adicionei alguns comentários para entender melhor o programa:
fn main() {
esp_idf_svc::sys::link_patches(); //link dos patches do esp-idf
esp_idf_svc::log::EspLogger::initialize_default(); // inicia logger do ESP
log::info!("Hello, world!"); // imprime Hello World.
}
Agora comecemos a desenvolver as duas partes importantes do projeto: Wifi e MQTT.
Caso queira ver o código completo, acesse através do link do github.
Configurando o Wifi
Para configurar o Wifi precisamos seguir os seguintes passos:
- Obter acesso aos periféricos
- Configurar driver Wifi
- Connectar com uma rede Wifi
- Obter um IP
Iniciemos 3 variáveis para acesso aos periféricos, system loop e nvs, para então criar uma instância do driver Wifi.
let peripherals = Peripherals::take().unwrap(); //obter acesso perifericos
let sysloop = EspSystemEventLoop::take().unwrap();//obter acesso ao systemloop
let nvs = EspDefaultNvsPartition::take().unwrap(); //obter acesso a Non-Volatile Storage, necessário para o driver Wifi
let mut wifi_driver = EspWifi::new(peripherals.modem, sysloop, Some(nvs)).unwrap();Configuremos o driver Wifi passando a referência de Configuration::Client, com SSID e senha. Nesse caso foi criado anteriormente, variáveis de ambiente chamadas SSID e PASSWORD, que são utilizadas na configuração.
wifi_driver.set_configuration(&Configuration::Client(ClientConfiguration {
ssid: SSID.try_into().unwrap(),
password: PASSWORD.try_into().unwrap(),
..Default::default()
})).unwrap();Uma vez configurado, iniciaremos o Wifi e tentaremos conectar com a rede. Colocou-se a tentativa de conexão dentro de um loop infinito, para que o próximo passo, seja somente executado uma vez que foi estabelecida uma conexão.
wifi_driver.start().unwrap();
println!("Wifi Iniciado? : {:?}", wifi_driver.is_started());
println!("Wifi Conectando... {:?}", wifi_driver.connect());
let mut c =0;
loop {
c+=1;
println!("Tentativa de Conexão #{c}");
let res = wifi_driver.is_connected();
match res {
Ok(connected) => {
if connected {
break;// sai do loop e vai para próximo passo
}
}
Err(err) => {
println!("{:?}", err);
loop {}
}
}
FreeRtos::delay_ms(1000u32);//delay antes de proxima verificacao de conexao
}
println!("{:?}", wifi_driver.is_connected());
Uma vez conectado ao Wifi, usaremos a mesma ideia para obter IP, e uma vez obtido um IP, acenderemos um LED para verificação visual.
c=0;
loop {
c+=1;
println!("Tentativa de obter IP do DHCP #{c}");
let res = wifi_driver.is_up();
match res {
Ok(connected) => {
if connected {
let ip =wifi_driver.sta_netif().get_ip_info();
println!("IP criado. {:?}", ip);
led.set_high().unwrap(); //liga LED para indicar wifi conectada
break;
}
}
Err(err) => {
println!("{:?}", err);
loop {}
}
}
FreeRtos::delay_ms(1000u32);
}
Configurando MQTT
Já com as configurações com sucesso do Wifi, criaremos uma instância de configuração de cliente MQTT e usaremos a url mqtt://mqtt.eclipseprojects.io que possui um broker gratuito na internet para testes.
//inicia configuração mqtt
let mqtt_config = MqttClientConfiguration::default();
let mqtt_url = "mqtt://mqtt.eclipseprojects.io"; Agora criemos uma conexão MQTT usando as configurações e url, e nesse caso, utilizamos uma função que necessita de uma função callback. Esse callback tratará os eventos MQTT Connected, Subscribed, Received, onde mostraremos uma mensagem de conectado, uma mensagem com o ID da subscrição ao broker, e por fim, uma vez recebido uma mensagem, imprimimos no log a mensagem e pisaremos o led, para verificação visual.
let client = EspMqttClient::new_cb(
mqtt_url,
&mqtt_config,
move |message_event| {
match message_event.payload(){
EventPayload::Connected(_) => {
println!("Conectado a {mqtt_url}");
},
EventPayload::Subscribed(id) => println!("Inscrito com id {id}"),
EventPayload::Received{data, ..} => {
if !data.is_empty() {
led.toggle().unwrap();
println!("Mensagem recebida: {}", std::str::from_utf8(data).unwrap());
FreeRtos::delay_ms(500u32);
led.toggle().unwrap();
}
}
_ => println!("Erro conectando a {mqtt_url}!"),
};
},
);
Por fim, faremos uma subscrição em um tópico específico (nesse caso profrs/led), e aguardamos uma mensagem.
let mut client = client.unwrap();
client.subscribe("profrs/led",QoS::AtLeastOnce).expect("erro ao subscrever no tópico!");
println!("Esperando mensagem...");Enviando para o ESP32 e testando
Agora só precisamos executar “cargo run” e o código será enviado para ESP32.
E após receber uma mensagem:
Conclusões
Como vimos nesse artigo, para a utilização do ESP-IDF, possuímos um template de instalação e configuração dos componentes básicos. Temos também, pelo nível de abstração gerado pelo ESP-IDF, componentes para WIFI, MQTT e funções do FreeRTOS.
A Espressif possui vários exemplos e crates em Rust para sistemas embarcados tanto no-std e std que podem ser usados em situações e necessidades diferentes.
Escreva nos comentários se já fez essa mesma implementação de outras maneiras,usando outros crates, e como tem sido seu aprendizado com Rust e ESP-IDF.





