Compare commits

...

7 Commits

Author SHA1 Message Date
Priec
871c9bddd1 removed unused library 2025-10-04 23:03:51 +02:00
Priec
a3954ea3e8 talking to the broker properly now 2025-10-04 23:00:56 +02:00
Priec
948602bd1e ipv4 or ipv6 parsed from .env file 2025-10-04 22:03:45 +02:00
Priec
cfcdb56fe8 static cell usage 2025-10-04 18:21:50 +02:00
Priec
99b1bf6c3d mqtt client polished 2025-10-04 16:17:45 +02:00
Priec
980fb67f85 better way to do it 2025-10-03 22:41:38 +02:00
Priec
0eab3bbaba compiled rust mqtt 2025-10-03 22:32:40 +02:00
10 changed files with 256 additions and 73 deletions

View File

@@ -7,6 +7,7 @@ ESP_LOG="info"
[build]
rustflags = [
"-C", "link-arg=-nostartfiles",
"-C", "link-arg=-Tdefmt.x",
"-Z", "stack-protector=all",
]

View File

@@ -1,2 +1,4 @@
SSID = "nazov_wifi_siete"
PASSWORD = "heslo_od_wifi"
BROKER_IP= "5.196.78.28"
BROKER_PORT= "1883"

59
final/Cargo.lock generated
View File

@@ -205,6 +205,16 @@ dependencies = [
"thiserror",
]
[[package]]
name = "defmt-rtt"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2cac3b8a5644a9e02b75085ebad3b6deafdbdbdec04bb25086523828aa4dfd1"
dependencies = [
"critical-section",
"defmt 1.0.1",
]
[[package]]
name = "delegate"
version = "0.13.4"
@@ -1167,6 +1177,31 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "projekt_final"
version = "0.1.0"
dependencies = [
"critical-section",
"defmt-rtt",
"dotenvy",
"embassy-executor",
"embassy-net",
"embassy-time 0.5.0",
"embedded-io",
"embedded-io-async",
"esp-alloc",
"esp-backtrace",
"esp-bootloader-esp-idf",
"esp-hal",
"esp-hal-embassy",
"esp-println",
"esp-wifi",
"log",
"rust-mqtt",
"smoltcp",
"static_cell",
]
[[package]]
name = "quote"
version = "1.0.41"
@@ -1379,30 +1414,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "test1"
version = "0.1.0"
dependencies = [
"critical-section",
"dotenvy",
"embassy-executor",
"embassy-net",
"embassy-time 0.5.0",
"embedded-io",
"embedded-io-async",
"esp-alloc",
"esp-backtrace",
"esp-bootloader-esp-idf",
"esp-hal",
"esp-hal-embassy",
"esp-println",
"esp-wifi",
"log",
"rust-mqtt",
"smoltcp",
"static_cell",
]
[[package]]
name = "thiserror"
version = "2.0.17"

View File

@@ -1,11 +1,11 @@
[package]
edition = "2021"
name = "test1"
name = "projekt_final"
rust-version = "1.86"
version = "0.1.0"
[[bin]]
name = "test1"
name = "projekt_final"
path = "./src/bin/main.rs"
[dependencies]
@@ -19,6 +19,7 @@ log = "0.4.27"
embassy-net = { version = "0.7.0", features = [
"dhcpv4",
"proto-ipv6",
"log",
"medium-ethernet",
"tcp",
@@ -57,6 +58,7 @@ smoltcp = { version = "0.12.0", default-features = false, features = [
"proto-dhcpv4",
"proto-dns",
"proto-ipv4",
"proto-ipv6",
"socket-dns",
"socket-icmp",
"socket-raw",
@@ -65,6 +67,7 @@ smoltcp = { version = "0.12.0", default-features = false, features = [
] }
static_cell = "2.1.1"
rust-mqtt = { version = "0.3.0", default-features = false, features = ["no_std"] }
defmt-rtt = "1.0.0"
[build-dependencies]
dotenvy = "0.15.7"

View File

@@ -5,6 +5,7 @@ fn main() {
println!("cargo:rerun-if-changed={}", dotenv_path.display());
}
// Pass WIFI credentials into firmware
if let Ok(ssid) = std::env::var("SSID") {
println!("cargo:rustc-env=SSID={}", ssid);
}
@@ -12,8 +13,16 @@ fn main() {
println!("cargo:rustc-env=PASSWORD={}", password);
}
// Export BROKER_IP and PORT as string envs also (optional, for debugging)
if let Ok(ip) = std::env::var("BROKER_IP") {
println!("cargo:rustc-env=BROKER_IP={}", ip);
}
if let Ok(port) = std::env::var("BROKER_PORT") {
println!("cargo:rustc-env=BROKER_PORT={}", port);
}
linker_be_nice();
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
// make sure linkall.x is the last linker script
println!("cargo:rustc-link-arg=-Tlinkall.x");
}

View File

@@ -17,7 +17,8 @@ use esp_wifi::{
wifi::{ClientConfiguration, Configuration, WifiController, WifiDevice, WifiEvent, WifiState},
};
use log::info;
mod mqtt;
use projekt_final::mqtt::client::mqtt_task;
use defmt_rtt as _;
extern crate alloc;
@@ -90,8 +91,8 @@ async fn main(spawner: Spawner) -> ! {
Timer::after(Duration::from_millis(500)).await;
}
// Stack is ready - Add rust-mqtt task here
// spawner.spawn(mqtt_task(stack)).ok();
spawner.spawn(mqtt_task(stack)).expect("failed to spawn MQTT task");
info!("MQTT task started");
loop {
Timer::after(Duration::from_secs(60)).await;

View File

@@ -1 +1,3 @@
#![no_std]
pub mod mqtt;

View File

@@ -1,53 +1,83 @@
use embassy_time::{Duration, Timer};
// src/mqtt/client.rs
use embassy_net::{tcp::TcpSocket, Stack};
use esp_wifi::wifi::WifiDevice;
use rust_mqtt::client::client::MqttClient;
use rust_mqtt::packet::v5::{connect::ConnectPacket, publish::PublishPacket};
use rust_mqtt::utils::rng_generator::CountingRng32;
use embassy_time::{Duration, Timer};
use log::info;
use rust_mqtt::client::client::MqttClient;
use rust_mqtt::client::client_config::{ClientConfig, MqttVersion};
use rust_mqtt::packet::v5::publish_packet::QualityOfService;
use rust_mqtt::utils::rng_generator::CountingRng;
use static_cell::ConstStaticCell;
use crate::mqtt::config::mqtt_broker_endpoint;
// TCP socket buffers (for embassy-net TcpSocket)
static TCP_RX_BUFFER: ConstStaticCell<[u8; 2048]> = ConstStaticCell::new([0; 2048]);
static TCP_TX_BUFFER: ConstStaticCell<[u8; 2048]> = ConstStaticCell::new([0; 2048]);
// MQTT client buffers (separate from the TcpSockets buffers)
static MQTT_TX_BUF: ConstStaticCell<[u8; 1024]> = ConstStaticCell::new([0; 1024]);
static MQTT_RX_BUF: ConstStaticCell<[u8; 1024]> = ConstStaticCell::new([0; 1024]);
#[embassy_executor::task]
pub async fn mqtt_task(stack: &'static Stack<WifiDevice<'static>>) {
pub async fn mqtt_task(stack: Stack<'static>) {
info!("MQTT task starting...");
static mut RX_BUFFER: [u8; 2048] = [0; 2048];
static mut TX_BUFFER: [u8; 2048] = [0; 2048];
let tcp_rx = TCP_RX_BUFFER.take();
let tcp_tx = TCP_TX_BUFFER.take();
let mut socket = TcpSocket::new(stack, tcp_rx, tcp_tx);
let mut socket = TcpSocket::new(stack, unsafe { &mut RX_BUFFER }, unsafe { &mut TX_BUFFER });
// TODO: replace with your broker IP
use embassy_net::Ipv4Address;
let broker_ip = Ipv4Address::new(192, 168, 1, 100);
let broker_port = 1883;
if let Err(e) = socket.connect((broker_ip, broker_port)).await {
info!("TCP connect failed: {:?}", e);
return;
}
info!("Connected to MQTT broker");
let rng = CountingRng32::new();
let mut client: MqttClient<_, _, 1024, 1024> = MqttClient::new(socket, rng);
let connect = ConnectPacket::new("esp32-client");
if client.connect(connect).await.is_ok() {
info!("MQTT CONNECT sent");
}
let publish = PublishPacket::new("esp32/topic", b"hello from esp32", 0);
if client.publish(publish).await.is_ok() {
info!("MQTT PUBLISH sent");
}
// Keep polling
loop {
match client.poll().await {
Ok(_) => {}
Err(e) => {
info!("MQTT error: {:?}", e);
break;
}
match socket.connect(mqtt_broker_endpoint()).await {
Ok(_) => info!("Connected TCP to MQTT broker"),
Err(e) => {
info!("TCP connect failed: {:?}", e);
return;
}
Timer::after(Duration::from_secs(5)).await;
}
let mqtt_tx = MQTT_TX_BUF.take();
let mqtt_rx = MQTT_RX_BUF.take();
// Config
let rng = CountingRng(0);
let mut cfg: ClientConfig<'static, 8, _> = ClientConfig::new(MqttVersion::MQTTv5, rng);
cfg.keep_alive = 60;
cfg.add_client_id("esp32-client");
// cfg.add_username("user");
// cfg.add_password("pass");
let mqtt_tx_len = mqtt_tx.len();
let mqtt_rx_len = mqtt_rx.len();
let mut client = MqttClient::new(socket, mqtt_tx, mqtt_tx_len, mqtt_rx, mqtt_rx_len, cfg);
// Connect
match client.connect_to_broker().await {
Ok(_) => info!("MQTT CONNACK received"),
Err(reason) => {
info!("MQTT connect failed: {:?}", reason);
return;
}
}
// Publish simple message
match client
.send_message(
"esp32/topic",
b"hello from esp32",
QualityOfService::QoS1,
false,
)
.await
{
Ok(_) => info!("MQTT PUBLISH sent"),
Err(reason) => info!("MQTT publish failed: {:?}", reason),
}
loop {
if let Err(reason) = client.send_ping().await {
info!("MQTT ping failed: {:?}", reason);
break;
}
Timer::after(Duration::from_secs(30)).await;
}
}

122
final/src/mqtt/config.rs Normal file
View File

@@ -0,0 +1,122 @@
// src/mqtt/config.rs
#![allow(dead_code)]
use embassy_net::{IpAddress, Ipv4Address, Ipv6Address};
// Compile-time values injected by build.rs
const BROKER_IP: &str = env!("BROKER_IP");
const BROKER_PORT: &str = env!("BROKER_PORT");
pub fn mqtt_broker_endpoint() -> (IpAddress, u16) {
(parse_ip(BROKER_IP), parse_port(BROKER_PORT))
}
fn parse_port(s: &str) -> u16 {
let p: u16 = s
.parse()
.unwrap_or_else(|_| panic!("BROKER_PORT must be a valid u16 (1..=65535)"));
assert!(p != 0, "BROKER_PORT cannot be 0");
p
}
fn parse_ip(s: &str) -> IpAddress {
if s.contains(':') {
IpAddress::Ipv6(parse_ipv6(s))
} else {
IpAddress::Ipv4(parse_ipv4(s))
}
}
fn parse_ipv4(s: &str) -> Ipv4Address {
let mut it = s.split('.');
let a = parse_octet(it.next(), 1);
let b = parse_octet(it.next(), 2);
let c = parse_octet(it.next(), 3);
let d = parse_octet(it.next(), 4);
assert!(it.next().is_none(), "Too many IPv4 octets");
Ipv4Address::new(a, b, c, d)
}
fn parse_octet(part: Option<&str>, idx: usize) -> u8 {
let p = part.unwrap_or_else(|| panic!("IPv4 missing octet {}", idx));
let v: u16 = p
.parse()
.unwrap_or_else(|_| panic!("Invalid IPv4 octet {}: {}", idx, p));
assert!(v <= 255, "IPv4 octet {} out of range: {}", idx, v);
v as u8
}
// Minimal IPv6 parser with '::' compression. Does not handle IPv4-embedded IPv6.
fn parse_ipv6(s: &str) -> Ipv6Address {
assert!(
!s.contains('.'),
"IPv4-embedded IPv6 like ::ffff:192.0.2.1 not supported; \
use pure hex IPv6"
);
let has_double = s.contains("::");
let (left_s, right_s) = if has_double {
let mut sp = s.splitn(2, "::");
(sp.next().unwrap_or(""), sp.next().unwrap_or(""))
} else {
(s, "")
};
let mut left = [0u16; 8];
let mut right = [0u16; 8];
let mut ll = 0usize;
let mut rl = 0usize;
if !left_s.is_empty() {
for part in left_s.split(':') {
left[ll] = parse_group(part);
ll += 1;
assert!(ll <= 8, "Too many IPv6 groups on the left");
}
}
if !right_s.is_empty() {
for part in right_s.split(':') {
right[rl] = parse_group(part);
rl += 1;
assert!(rl <= 8, "Too many IPv6 groups on the right");
}
}
let zeros = if has_double {
assert!(ll + rl < 8, "Invalid IPv6 '::' usage");
8 - (ll + rl)
} else {
assert!(ll == 8, "IPv6 must have 8 groups without '::'");
0
};
let mut g = [0u16; 8];
let mut idx = 0usize;
for i in 0..ll {
g[idx] = left[i];
idx += 1;
}
for _ in 0..zeros {
g[idx] = 0;
idx += 1;
}
for i in 0..rl {
g[idx] = right[i];
idx += 1;
}
assert!(idx == 8, "IPv6 did not resolve to 8 groups");
Ipv6Address::new(g[0], g[1], g[2], g[3], g[4], g[5], g[6], g[7])
}
fn parse_group(part: &str) -> u16 {
assert!(
!part.is_empty(),
"Empty IPv6 group (use '::' instead for compression)"
);
assert!(part.len() <= 4, "IPv6 group too long: {}", part);
u16::from_str_radix(part, 16)
.unwrap_or_else(|_| panic!("Invalid IPv6 hex group: {}", part))
}

View File

@@ -1,2 +1,4 @@
// src/mqtt/mod.rs
pub mod client;
pub mod config;