diff --git a/Cargo.lock b/Cargo.lock index d851208..82a078d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,6 +348,21 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" +[[package]] +name = "embassy-ha" +version = "0.2.0" +source = "git+https://github.com/ToBinio/embassy-ha#3060b85d3496066f07a8c08ae540abbef7bd574f" +dependencies = [ + "embassy-futures", + "embassy-net", + "embassy-sync 0.7.2", + "embassy-time", + "embedded-io-async 0.6.1", + "heapless 0.9.2", + "serde", + "serde-json-core", +] + [[package]] name = "embassy-hal-internal" version = "0.3.0" @@ -1008,6 +1023,7 @@ checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ "defmt 0.3.100", "hash32", + "serde", "stable_deref_trait", ] @@ -1414,6 +1430,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-json-core" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b81787e655bd59cecadc91f7b6b8651330b2be6c33246039a65e5cd6f4e0828" +dependencies = [ + "heapless 0.8.0", + "ryu", + "serde", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -1712,6 +1739,7 @@ dependencies = [ "dotenvy", "embassy-embedded-hal", "embassy-executor", + "embassy-ha", "embassy-net", "embassy-sync 0.7.2", "embassy-time", diff --git a/Cargo.toml b/Cargo.toml index 7e1961d..e52944e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,6 +68,7 @@ smoltcp = { version = "0.12.0", default-features = false, features = [ "socket-udp", "dns-max-server-count-4", ] } +embassy-ha = { git= "https://github.com/ToBinio/embassy-ha" } [build-dependencies] dotenvy = "0.15" diff --git a/build.rs b/build.rs index 3d2d3be..1beb8b2 100644 --- a/build.rs +++ b/build.rs @@ -1,11 +1,17 @@ +macro_rules! set_env { + ($name:expr) => { + if let Ok(value) = std::env::var($name) { + println!("cargo:rustc-env={}={}", $name, value); + } + }; +} + fn main() { dotenvy::dotenv().ok(); - if let Ok(ssid) = std::env::var("SSID") { - println!("cargo:rustc-env=SSID={ssid}"); - } - if let Ok(password) = std::env::var("PASSWORD") { - println!("cargo:rustc-env=PASSWORD={password}"); - } + + set_env!("SSID"); + set_env!("PASSWORD"); + set_env!("MQTT_ADDRESS"); linker_be_nice(); // make sure linkall.x is the last linker script (otherwise might cause problems with flip-link) diff --git a/src/ha.rs b/src/ha.rs new file mode 100644 index 0000000..45eb963 --- /dev/null +++ b/src/ha.rs @@ -0,0 +1,99 @@ +use embassy_executor::Spawner; +use embassy_net::Stack; +use static_cell::StaticCell; + +pub struct HaSensors { + pub temperature: embassy_ha::Sensor<'static>, + pub humidity: embassy_ha::Sensor<'static>, + pub pressure: embassy_ha::Sensor<'static>, + pub gas_resistance: embassy_ha::Sensor<'static>, + pub co2: embassy_ha::Sensor<'static>, +} + +pub fn init_ha(stack: Stack<'static>, spawner: &Spawner) -> HaSensors { + static HA_RESOURCES: StaticCell = StaticCell::new(); + let ha_device = embassy_ha::new( + HA_RESOURCES.init(Default::default()), + embassy_ha::DeviceConfig { + device_id: "indoor-weather-station", + device_name: "Indoor Weather Station", + manufacturer: "Custom", + model: "Indoor Weather Station", + }, + ); + + let temperature = embassy_ha::create_sensor( + &ha_device, + "temp-sensor", + embassy_ha::SensorConfig { + class: embassy_ha::SensorClass::Temperature, + state_class: embassy_ha::StateClass::Measurement, + unit: Some(embassy_ha::constants::HA_UNIT_TEMPERATURE_CELSIUS), + ..Default::default() + }, + ); + + let humidity = embassy_ha::create_sensor( + &ha_device, + "humidity-sensor", + embassy_ha::SensorConfig { + class: embassy_ha::SensorClass::Humidity, + state_class: embassy_ha::StateClass::Measurement, + unit: Some(embassy_ha::constants::HA_UNIT_PERCENTAGE), + ..Default::default() + }, + ); + + let pressure = embassy_ha::create_sensor( + &ha_device, + "pressure-sensor", + embassy_ha::SensorConfig { + class: embassy_ha::SensorClass::Pressure, + state_class: embassy_ha::StateClass::Measurement, + unit: Some(embassy_ha::constants::HA_UNIT_PRESSURE_HPA), + ..Default::default() + }, + ); + + let gas_resistance = embassy_ha::create_sensor( + &ha_device, + "gas-resistance-sensor", + embassy_ha::SensorConfig { + common: embassy_ha::EntityCommonConfig { + name: Some("Gas Resistance"), + ..Default::default() + }, + class: embassy_ha::SensorClass::Generic, + state_class: embassy_ha::StateClass::Measurement, + unit: Some("Ω"), + ..Default::default() + }, + ); + + let co2 = embassy_ha::create_sensor( + &ha_device, + "co2-sensor", + embassy_ha::SensorConfig { + class: embassy_ha::SensorClass::CarbonDioxide, + state_class: embassy_ha::StateClass::Measurement, + unit: Some("ppm"), + ..Default::default() + }, + ); + + spawner.spawn(ha_task(stack, ha_device)).unwrap(); + + HaSensors { + temperature, + humidity, + pressure, + co2, + gas_resistance, + } +} + +#[embassy_executor::task] +async fn ha_task(stack: embassy_net::Stack<'static>, device: embassy_ha::Device<'static>) { + const MQTT_ADDRESS: &str = env!("MQTT_ADDRESS"); + embassy_ha::connect_and_run(stack, device, MQTT_ADDRESS).await; +} diff --git a/src/lib.rs b/src/lib.rs index 31371c0..c62d1a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ #![no_std] +pub mod ha; pub mod wifi; diff --git a/src/main.rs b/src/main.rs index 68298a9..ad331c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ use esp_hal::timer::timg::TimerGroup; use lcd_lcm1602_i2c::async_lcd::Lcd; use scd4x::Scd4xAsync; use static_cell::StaticCell; +use weather_station::ha::{HaSensors, init_ha}; use weather_station::wifi::init_wifi; extern crate alloc; @@ -84,9 +85,11 @@ async fn main(spawner: Spawner) { let timg0 = TimerGroup::new(peripherals.TIMG0); esp_rtos::start(timg0.timer0); - init_wifi(peripherals.WIFI, &spawner).await; + let stack = init_wifi(peripherals.WIFI, &spawner).await; esp_println::println!("WiFi initialized!"); + let ha_sensors = init_ha(stack, &spawner); + let button_pin = Input::new( peripherals.GPIO23, InputConfig::default().with_pull(esp_hal::gpio::Pull::Up), @@ -119,7 +122,7 @@ async fn main(spawner: Spawner) { .spawn(listen_button(button_pin, event_channel.sender())) .unwrap(); spawner - .spawn(display_data(i2c_v5, event_channel.receiver())) + .spawn(process_data(i2c_v5, ha_sensors, event_channel.receiver())) .unwrap(); esp_println::println!("Tasks initialized!"); @@ -135,8 +138,6 @@ async fn read_bme_680(i2c_bus: &'static I2c1BusV33, sender: EventSender) { .unwrap(); loop { - Timer::after(Duration::from_secs(5)).await; - let data = sensor.measure().await.unwrap(); sender .send(Events::BME680 { @@ -146,6 +147,8 @@ async fn read_bme_680(i2c_bus: &'static I2c1BusV33, sender: EventSender) { gas_resistance: data.gas_resistance.unwrap_or(0.), }) .await; + + Timer::after(Duration::from_secs(60)).await; } } @@ -159,7 +162,7 @@ async fn read_scd_41(i2c_bus: &'static I2c1BusV33, sender: EventSender) { sensor.start_periodic_measurement().await.unwrap(); loop { - Timer::after(Duration::from_secs(5)).await; + Timer::after(Duration::from_secs(60)).await; let data = sensor.measurement().await.unwrap(); sender @@ -234,7 +237,11 @@ impl DisplayMode { } #[embassy_executor::task] -async fn display_data(mut i2c: I2c<'static, Async>, receiver: EventReceiver) { +async fn process_data( + mut i2c: I2c<'static, Async>, + mut ha_sensors: HaSensors, + receiver: EventReceiver, +) { let mut data = Data { temp: 0.0, humidity: 0.0, @@ -267,9 +274,16 @@ async fn display_data(mut i2c: I2c<'static, Async>, receiver: EventReceiver) { data.humidity = humidity; data.pressure = pressure / 100.0; data.gas_resistance = gas_resistance; + + ha_sensors.temperature.publish(data.temp); + ha_sensors.humidity.publish(data.humidity); + ha_sensors.pressure.publish(data.pressure); + ha_sensors.gas_resistance.publish(data.gas_resistance); } Events::SCD41 { co2, .. } => { data.co2 = co2; + + ha_sensors.co2.publish(data.co2 as f32); } Events::ButtonPress => { mode = mode.next(); diff --git a/src/wifi.rs b/src/wifi.rs index 4fc7d12..8ecad63 100644 --- a/src/wifi.rs +++ b/src/wifi.rs @@ -10,26 +10,10 @@ use esp_radio::wifi::{ }; use static_cell::StaticCell; -async fn wait_for_connection(stack: Stack<'_>) { - println!("Waiting for link to be up"); - loop { - if stack.is_link_up() { - break; - } - Timer::after(Duration::from_millis(500)).await; - } - - println!("Waiting to get IP address..."); - loop { - if let Some(config) = stack.config_v4() { - println!("Got IP: {}", config.address); - break; - } - Timer::after(Duration::from_millis(500)).await; - } -} - -pub async fn init_wifi(wifi: esp_hal::peripherals::WIFI<'static>, spawner: &Spawner) { +pub async fn init_wifi( + wifi: esp_hal::peripherals::WIFI<'static>, + spawner: &Spawner, +) -> Stack<'static> { static RADIO_INIT: StaticCell> = StaticCell::new(); let radio_init = RADIO_INIT.init(esp_radio::init().expect("Failed to initialize Wi-Fi/BLE controller")); @@ -55,7 +39,7 @@ pub async fn init_wifi(wifi: esp_hal::peripherals::WIFI<'static>, spawner: &Spaw spawner.spawn(connection(wifi_controller)).ok(); spawner.spawn(net_task(runner)).ok(); - wait_for_connection(stack).await; + stack } #[embassy_executor::task]