Skip to content

Commit

Permalink
feat: mqtt publishing
Browse files Browse the repository at this point in the history
  • Loading branch information
akosnad committed Nov 23, 2024
1 parent b97e10f commit ea42412
Show file tree
Hide file tree
Showing 10 changed files with 429 additions and 73 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/.vscode
/.embuild
/target
/Cargo.lock
target/

/config.yml
/ota.bin
Expand Down
41 changes: 41 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 9 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ version = "0.1.0"
authors = ["akosnad"]
edition = "2021"

[workspace]
members = [
"hass-types"
]

[dependencies]
esp-backtrace = { version = "0.14.0", features = [
"esp32",
Expand Down Expand Up @@ -36,7 +41,8 @@ embassy-futures = "0.1.1"
atat = { version = "0.23.0", default-features = false, features = ["atat_derive", "derive", "log", "serde_at"] }
serde_at = { version = "0.23.0", features = ["alloc"] }
embassy-net-ppp = { version = "0.1.0", features = ["log"] }
rust-mqtt = { version = "0.3.0", default-features = false, features = ["no_std"] }
rust-mqtt = { git = "https://github.com/akosnad/rust-mqtt.git", default-features = false, features = ["no_std"] }
hass-types = { path = "hass-types" }

[profile.dev]
# Rust debug is too slow.
Expand All @@ -55,9 +61,8 @@ overflow-checks = false
[build-dependencies]
serde = { version = "1.0.210", features = ["derive"] }
serde_yaml = "0.9.34"
hass-types = { path = "hass-types" }
uneval = "0.2.4"

[package.metadata.espflash]
partition_table = "partitions.csv"

[patch.crates-io]
rust-mqtt = { path = "../rust-mqtt" }
6 changes: 6 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use hass_types::DeviceTracker;

#[derive(serde::Deserialize)]
struct Config {
wifi_ssid: String,
wifi_password: String,
apn: String,
device_tracker_config: DeviceTracker,
}

impl Config {
Expand All @@ -23,4 +26,7 @@ fn main() {
serde_yaml::from_str::<Config>(&config_string).expect("config.yml is not valid")
};
config.export_vars();

uneval::to_out_dir(config.device_tracker_config, "device_tracker_config.rs")
.expect("Failed to write device_tracker_config.rs");
}
9 changes: 9 additions & 0 deletions hass-types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "hass-types"
version = "0.1.0"
authors = ["akosnad"]
edition = "2021"

[dependencies]
heapless = { version = "0.8.0", features = ["serde"] }
serde = { version = "1.0.215", default-features = false, features = ["derive", "alloc"] }
71 changes: 71 additions & 0 deletions hass-types/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#![no_std]

use alloc::{format, string::String};
use heapless::Vec;
use serde::{Deserialize, Serialize};

extern crate alloc;

pub const DISCOVERY_PREFIX: &str = "homeassistant";

pub trait Discoverable {
fn discovery_topic(&self) -> Topic;
}

#[derive(Debug, Clone, Copy, Deserialize, Serialize, Default)]
pub enum AvailabilityMode {
#[serde(rename = "all")]
All,
#[serde(rename = "any")]
Any,
#[default]
#[serde(rename = "latest")]
Latest,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Availability {
pub payload_available: Option<String>,
pub payload_not_available: Option<String>,
pub topic: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Device {
pub hw_version: Option<String>,
pub sw_version: Option<String>,
pub name: Option<String>,
pub identifiers: Vec<String, 5>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UniqueId(pub String);

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Topic(pub String);

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DeviceTracker {
/// Only first availability is used by us, the rest are ignored.
pub availability: Vec<Availability, 5>,
pub availability_mode: Option<AvailabilityMode>,
pub device: Option<Device>,
pub unique_id: UniqueId,
pub name: String,
pub json_attributes_topic: Topic,
}
impl Discoverable for DeviceTracker {
fn discovery_topic(&self) -> Topic {
Topic(format!(
"{}/device_tracker/{}/config",
DISCOVERY_PREFIX, self.unique_id.0
))
}
}

#[derive(Debug, Clone, Copy, Serialize)]
pub struct DeviceTrackerAttributes {
pub longitude: f64,
pub latitude: f64,
pub gps_accuracy: Option<f64>,
}
17 changes: 17 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use alloc::vec;
use embassy_sync::once_lock::OnceLock;
use hass_types::*;

static CONFIG: OnceLock<SystemConfig> = OnceLock::new();

#[derive(Debug, Clone)]
pub struct SystemConfig {
pub device_tracker: DeviceTracker,
}
impl SystemConfig {
pub fn get() -> &'static Self {
CONFIG.get_or_init(|| Self {
device_tracker: include!(concat!(env!("OUT_DIR"), "/device_tracker_config.rs")),
})
}
}
11 changes: 11 additions & 0 deletions src/gps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
use embassy_time::{Duration, Timer};
use embedded_io_async::{Read as _, ReadReady as _, Write as _};
use esp_hal::{peripherals::UART1, uart::Uart, Async};
use hass_types::DeviceTrackerAttributes;
use log::{info, trace};
use ublox::{GpsFix, PacketRef};

Expand All @@ -28,6 +29,16 @@ impl From<ublox::NavPvtRef<'_>> for GpsCoords {
}
}

impl From<GpsCoords> for DeviceTrackerAttributes {
fn from(coords: GpsCoords) -> Self {
Self {
longitude: coords.lon,
latitude: coords.lat,
gps_accuracy: Some(coords.horiz_accuracy as f64),
}
}
}

#[derive(Debug, Default)]
enum GpsState {
#[default]
Expand Down
84 changes: 17 additions & 67 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use alloc::boxed::Box;
use core::str::FromStr;
use embassy_executor::{task, Spawner};
use embassy_net::{tcp::TcpSocket, ConfigV4, Ipv4Address, StackResources, StaticConfigV4};
use embassy_net::{ConfigV4, Ipv4Address, StackResources, StaticConfigV4};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_time::{Duration, Timer};
use esp_backtrace as _;
Expand All @@ -21,14 +21,14 @@ use esp_wifi::wifi::{
utils::create_network_interface, ClientConfiguration, Configuration, WifiController,
WifiDevice, WifiEvent, WifiStaDevice, WifiState,
};
use log::info;
use rust_mqtt::{client::client::MqttClient, utils::rng_generator::CountingRng};
use static_cell::make_static;

extern crate alloc;

mod config;
mod gps;
mod modem;
mod mqtt;

use gps::Gps;

Expand Down Expand Up @@ -221,73 +221,18 @@ async fn main_task(spawner: Spawner) {
.expect("Failed to spawn modem stack config setter");

// MQTT
let mut mqtt_rx = [0u8; 128];
let mut mqtt_tx = [0u8; 128];
let mut mqtt_sock = TcpSocket::new(wifi_stack, &mut mqtt_rx, &mut mqtt_tx);
mqtt_sock.set_timeout(Some(Duration::from_secs(10)));
let endpoint = (Ipv4Address::new(10, 20, 0, 1), 1883);
loop {
Timer::after(Duration::from_secs(5)).await;
if let Err(e) = mqtt_sock.connect(endpoint).await {
log::error!("Failed to connect to MQTT broker: {:?}", e);
continue;
}
info!("MQTT socket connected");

let mut config = rust_mqtt::client::client_config::ClientConfig::new(
rust_mqtt::client::client_config::MqttVersion::MQTTv5,
CountingRng(20000),
);
config.add_client_id("voyagesp");
const MQTT_BUF_SIZE: usize = 128;
config.max_packet_size = (MQTT_BUF_SIZE as u32) - 1;
let mut client_tx = [0u8; MQTT_BUF_SIZE];
let mut client_rx = [0u8; MQTT_BUF_SIZE];
let mut client = MqttClient::<_, 5, _>::new(
&mut mqtt_sock,
&mut client_tx,
MQTT_BUF_SIZE,
&mut client_rx,
MQTT_BUF_SIZE,
config,
);

if let Err(e) = client.connect_to_broker().await {
log::error!("Failed to connect to MQTT broker: {:?}", e);
continue;
}
let mqtt = make_static!(mqtt::Mqtt::new(wifi_stack, modem_stack, rng));
spawner
.spawn(mqtt_task(mqtt))
.expect("Failed to spawn MQTT task");

loop {
let gps_data = {
let raw = match gps.get_coords().await {
Some(data) => data,
None => {
Timer::after(Duration::from_secs(2)).await;
continue;
}
};
match serde_json::to_string(&raw) {
Ok(data) => data,
Err(e) => {
log::error!("Failed to serialize GPS data: {:?}", e);
continue;
}
}
};
let result = client
.send_message(
"voyagesp",
gps_data.as_bytes(),
rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS0,
false,
)
loop {
if let Some(gps_data) = gps.get_coords().await {
log::info!("GPS data: {:?}", gps_data);
mqtt.send_event(mqtt::Event::DeviceTrackerStateChange(gps_data.into()))
.await;
if let Err(e) = result {
log::error!("Failed to send message: {:?}", e);
break;
}
Timer::after(Duration::from_secs(2)).await;
}
Timer::after(Duration::from_secs(5)).await;
}
}

Expand Down Expand Up @@ -388,3 +333,8 @@ async fn gps_task(gps: &'static Gps<GPS_BAUD>) {
async fn modem_task(modem: &'static modem::Modem) {
modem.run().await;
}

#[task]
async fn mqtt_task(mqtt: &'static mqtt::Mqtt) {
mqtt.run().await;
}
Loading

0 comments on commit ea42412

Please sign in to comment.