diff --git a/final/Cargo.toml b/final/Cargo.toml index 902f222..42a7a02 100644 --- a/final/Cargo.toml +++ b/final/Cargo.toml @@ -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", diff --git a/final/build.rs b/final/build.rs index 09afabe..8005431 100644 --- a/final/build.rs +++ b/final/build.rs @@ -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"); } diff --git a/final/src/mqtt/client.rs b/final/src/mqtt/client.rs index f53ee34..1e0d966 100644 --- a/final/src/mqtt/client.rs +++ b/final/src/mqtt/client.rs @@ -1,5 +1,5 @@ // src/mqtt/client.rs -use embassy_net::{tcp::TcpSocket, Ipv4Address, Stack}; +use embassy_net::{tcp::TcpSocket, Stack}; use embassy_time::{Duration, Timer}; use log::info; use rust_mqtt::client::client::MqttClient; @@ -8,6 +8,8 @@ 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]); @@ -20,18 +22,11 @@ static MQTT_RX_BUF: ConstStaticCell<[u8; 1024]> = ConstStaticCell::new([0; 1024] pub async fn mqtt_task(stack: Stack<'static>) { info!("MQTT task starting..."); - // Safely take buffers (only allowed once) let tcp_rx = TCP_RX_BUFFER.take(); let tcp_tx = TCP_TX_BUFFER.take(); - - // Pass Stack by value to embassy-net TcpSocket let mut socket = TcpSocket::new(stack, tcp_rx, tcp_tx); - // TODO broker details - let broker_ip = Ipv4Address::new(192, 168, 1, 100); - let broker_port = 1883; - - match socket.connect((broker_ip, broker_port)).await { + match socket.connect(mqtt_broker_endpoint()).await { Ok(_) => info!("Connected TCP to MQTT broker"), Err(e) => { info!("TCP connect failed: {:?}", e); @@ -39,24 +34,20 @@ pub async fn mqtt_task(stack: Stack<'static>) { } } - // Safely take MQTT buffers let mqtt_tx = MQTT_TX_BUF.take(); let mqtt_rx = MQTT_RX_BUF.take(); - let mqtt_tx_len = mqtt_tx.len(); - let mqtt_rx_len = mqtt_rx.len(); - - // RNG + // Config let rng = CountingRng(0); - - // Build config 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"); - // Build MQTT client + 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 @@ -82,7 +73,6 @@ pub async fn mqtt_task(stack: Stack<'static>) { Err(reason) => info!("MQTT publish failed: {:?}", reason), } - // Keep alive loop { if let Err(reason) = client.send_ping().await { info!("MQTT ping failed: {:?}", reason); diff --git a/final/src/mqtt/config.rs b/final/src/mqtt/config.rs new file mode 100644 index 0000000..c20d357 --- /dev/null +++ b/final/src/mqtt/config.rs @@ -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)) +} diff --git a/final/src/mqtt/mod.rs b/final/src/mqtt/mod.rs index 9cc7d66..ef0bd6d 100644 --- a/final/src/mqtt/mod.rs +++ b/final/src/mqtt/mod.rs @@ -1,2 +1,4 @@ +// src/mqtt/mod.rs pub mod client; +pub mod config;