From dd7abc42736004081539f5390b82f86f593db9ab Mon Sep 17 00:00:00 2001 From: Cosmin Constantin Lazar Date: Sun, 21 Apr 2024 21:20:49 +0200 Subject: [PATCH 1/9] Bump rumqttc to 0.24.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1aeeee2..5c6c9ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" chrono = { version = "0.4.26", features = ["serde"] } config = { version = "0.14.0", features = ["toml"] } reqwest = { version = "0.12.3", features = ["json"] } -rumqttc = "0.23.0" +rumqttc = "0.24.0" serde = { version = "1.0.183", features = ["derive"] } serde_json = "1.0.107" tokio = { version = "1.34.0", features = ["full"] } From addd48d400781f42edafc1075ea90eab2e419925 Mon Sep 17 00:00:00 2001 From: Cosmin Constantin Lazar Date: Sun, 21 Apr 2024 21:21:01 +0200 Subject: [PATCH 2/9] Proper default value --- config/default.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/default.toml b/config/default.toml index 39f0b21..1ffb8d6 100644 --- a/config/default.toml +++ b/config/default.toml @@ -11,7 +11,7 @@ base_url = "https://portal-api.kredslob.dk" # You can specify either the Id of the address or fully qualify the address # [affaldvarme.address] -# id = "bc334dbe-250e-4cf1-af89-f3e940867845" +# id = "07514448_100_______" #[affaldvarme.address] # street_name = "Kongevejen" From f79b814e7e3450beefd9e677d0e8a89e4161267e Mon Sep 17 00:00:00 2001 From: Cosmin Constantin Lazar Date: Sun, 21 Apr 2024 21:21:36 +0200 Subject: [PATCH 3/9] Introduce HADevice --- src/homeassistant/mod.rs | 63 ++++++++++++++++++++++++++++------------ src/lib.rs | 20 ++++--------- src/main.rs | 7 ++--- src/mitaffald/mod.rs | 34 ++++++++++------------ tests/full_flow.rs | 4 +-- tests/full_flow_insta.rs | 5 +--- tests/mqtt/mod.rs | 2 +- 7 files changed, 73 insertions(+), 62 deletions(-) diff --git a/src/homeassistant/mod.rs b/src/homeassistant/mod.rs index d5477aa..efa8aaf 100644 --- a/src/homeassistant/mod.rs +++ b/src/homeassistant/mod.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use crate::mitaffald::Container; use crate::settings::MQTTConfig; use rumqttc::{AsyncClient, LastWill, MqttOptions}; @@ -20,8 +22,48 @@ impl From for MqttOptions { } } -pub struct HASensor { - pub container_id: String, +#[derive(Default)] +pub struct HADevice { + sensors: HashMap, + is_initialized: bool, +} + +impl HADevice { + pub async fn report( + &mut self, + container: Container, + client: &mut AsyncClient, + ) -> Result<(), rumqttc::ClientError> { + if !self.is_initialized { + self.register_device_availability(client).await?; + self.is_initialized = true; + } + + let sensor_id = HASensor::generate_sensor_id(&container); + self.sensors + .entry(sensor_id.clone()) + .or_insert_with(|| HASensor::new(&container)) + .report(container, client) + .await + } + + async fn register_device_availability( + &self, + client: &mut AsyncClient, + ) -> Result<(), rumqttc::ClientError> { + client + .publish( + HA_AVAILABILITY_TOPIC, + rumqttc::QoS::AtLeastOnce, + true, + "online", + ) + .await + } +} + +struct HASensor { + container_id: String, configure_topic: String, state_topic: String, is_initialized: bool, @@ -58,7 +100,6 @@ impl HASensor { ) -> Result<(), rumqttc::ClientError> { if !self.is_initialized { self.register_sensor(&container, client).await?; - self.register_sensor_availability(client).await?; self.is_initialized = true; } @@ -91,7 +132,7 @@ impl HASensor { "sw_version": "1.0", "model": "Standard", "manufacturer": "Your Garbage Bin Manufacturer" - }}, + }}, "icon": "mdi:recycle" }}"#, sensor_name = container.name, @@ -110,20 +151,6 @@ impl HASensor { .await } - async fn register_sensor_availability( - &self, - client: &mut AsyncClient, - ) -> Result<(), rumqttc::ClientError> { - client - .publish( - HA_AVAILABILITY_TOPIC, - rumqttc::QoS::AtLeastOnce, - true, - "online", - ) - .await - } - async fn register_sensor_value( &self, container: &Container, diff --git a/src/lib.rs b/src/lib.rs index bd4c492..853a7d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,14 @@ -use std::collections::HashMap; - -use homeassistant::HASensor; use mitaffald::{get_containers, Container}; use rumqttc::AsyncClient; use settings::Settings; +use std::collections::{hash_map::Entry, HashMap}; pub mod homeassistant; pub mod mitaffald; pub mod settings; -pub async fn sync_data( - settings: Settings, - sensor_map: &mut HashMap, -) -> Result<(), String> { +pub async fn sync_data(settings: Settings) -> Result<(), String> { + let mut device = homeassistant::HADevice::default(); let (mut client, mut connection) = AsyncClient::new(settings.mqtt.into(), 200); let mut has_errors = false; @@ -23,12 +19,12 @@ pub async fn sync_data( HashMap::::new(), |mut accumulator, item| { match accumulator.entry(item.name.clone()) { - std::collections::hash_map::Entry::Occupied(mut existing) => { + Entry::Occupied(mut existing) => { if existing.get().date > item.date { existing.insert(item); } } - std::collections::hash_map::Entry::Vacant(v) => { + Entry::Vacant(v) => { v.insert(item); } } @@ -38,11 +34,7 @@ pub async fn sync_data( ) .into_values() { - let report_result = sensor_map - .entry(container.name.clone()) - .or_insert_with(|| HASensor::new(&container)) - .report(container, &mut client) - .await; + let report_result = device.report(container, &mut client).await; has_errors = has_errors || report_result.is_err(); } diff --git a/src/main.rs b/src/main.rs index e426c25..fe8d3f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,11 @@ -use ha_mitaffald::homeassistant::HASensor; use ha_mitaffald::settings::Settings; use ha_mitaffald::sync_data; -use std::collections::HashMap; #[tokio::main] async fn main() { let settings = Settings::new().expect("Failed to read settings"); - let mut sensor_map: HashMap = HashMap::new(); - let report = sync_data(settings, &mut sensor_map).await; + let report = sync_data(settings).await; if let Err(x) = report { eprintln!( @@ -16,4 +13,6 @@ async fn main() { x ); } + + tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; } diff --git a/src/mitaffald/mod.rs b/src/mitaffald/mod.rs index e7377c1..a8cdcf5 100644 --- a/src/mitaffald/mod.rs +++ b/src/mitaffald/mod.rs @@ -8,20 +8,18 @@ use url::Url; use self::settings::{AddressId, TraditionalAddress}; pub async fn get_containers(config: AffaldVarmeConfig) -> Result, String> { - let response = fetch_remote_response(config) - .await - .map_err(|err| format!("Error connecting: {:?}", err))?; + let response = fetch_remote_response(config).await?; if !response.status().is_success() { return Err(format!("Unexpected status code: {:?}", response.status())); } response - .json::() + .json::() .await .map_err(|err| format!("Error reading response content: {:?}", err)) - .and_then(|respon| { - respon + .and_then(|response| { + response .0 .into_iter() .next() @@ -34,10 +32,10 @@ pub async fn get_containers(config: AffaldVarmeConfig) -> Result, } #[derive(Deserialize)] -struct Respon(Vec); +struct Response(Vec); #[derive(Deserialize)] -struct Response { +struct StandCollectionPlan { #[allow(dead_code)] #[serde(rename = "standId")] stand_id: String, @@ -55,25 +53,25 @@ struct PlannedLoad { fractions: Vec, } -async fn fetch_remote_response( - config: AffaldVarmeConfig, -) -> Result { - let remote_url = build_remote_url(config).await; +async fn fetch_remote_response(config: AffaldVarmeConfig) -> Result { + let remote_url = build_remote_url(config).await?; - reqwest::get(remote_url).await + reqwest::get(remote_url) + .await + .map_err(|err| format!("Error connecting: {:?}", err)) } -async fn build_remote_url(config: AffaldVarmeConfig) -> Url { +async fn build_remote_url(config: AffaldVarmeConfig) -> Result { let mut url_builder = config.base_url.clone(); let address_id = match config.address { Address::Id(x) => x.id.clone(), - Address::FullySpecified(x) => lookup_address(x).await.unwrap().id.clone(), + Address::FullySpecified(x) => lookup_address(x).await?.id.clone(), }; url_builder.set_path(format!("api/calendar/address/{}", dbg!(address_id)).as_str()); - url_builder + Ok(url_builder) } async fn lookup_address(address: TraditionalAddress) -> Result { @@ -122,8 +120,8 @@ impl Container { } } -impl From for Vec { - fn from(response: Response) -> Self { +impl From for Vec { + fn from(response: StandCollectionPlan) -> Self { response .planned_loads .into_iter() diff --git a/tests/full_flow.rs b/tests/full_flow.rs index 336f819..1472c79 100644 --- a/tests/full_flow.rs +++ b/tests/full_flow.rs @@ -5,7 +5,6 @@ use crate::mqtt::CollectingClient; use assert_json_diff::assert_json_include; use fluent_asserter::{assert_that, create_asserter}; use ha_mitaffald::{ - homeassistant::HASensor, mitaffald::settings::{Address, AddressId, AffaldVarmeConfig}, settings::Settings, sync_data, @@ -54,8 +53,7 @@ async fn smoke_test() { let mut collecting_client = CollectingClient::new(); collecting_client.start(&settings.mqtt); - let mut sensor_map: HashMap = HashMap::new(); - let sync_result = sync_data(settings, &mut sensor_map).await; + let sync_result = sync_data(settings).await; assert!( sync_result.is_ok(), diff --git a/tests/full_flow_insta.rs b/tests/full_flow_insta.rs index 8eb71c0..0c4e867 100644 --- a/tests/full_flow_insta.rs +++ b/tests/full_flow_insta.rs @@ -3,7 +3,6 @@ mod mqtt; use crate::mqtt::CollectingClient; use ha_mitaffald::{ - homeassistant::HASensor, mitaffald::settings::{Address, AddressId, AffaldVarmeConfig}, settings::Settings, sync_data, @@ -11,7 +10,6 @@ use ha_mitaffald::{ use hivemq::HiveMQContainer; use rumqttc::Publish; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::time::Duration; use testcontainers::clients; use url::Url; @@ -52,8 +50,7 @@ async fn smoke_test_insta() { let mut collecting_client = CollectingClient::new(); collecting_client.start(&settings.mqtt); - let mut sensor_map: HashMap = HashMap::new(); - let sync_result = sync_data(settings, &mut sensor_map).await; + let sync_result = sync_data(settings).await; assert!( sync_result.is_ok(), diff --git a/tests/mqtt/mod.rs b/tests/mqtt/mod.rs index fbaf5e6..b36a7f6 100644 --- a/tests/mqtt/mod.rs +++ b/tests/mqtt/mod.rs @@ -32,7 +32,7 @@ impl CollectingClient { let (tx, rx) = std::sync::mpsc::channel::<()>(); let handle = std::thread::spawn(move || { - let (mut client, mut connection) = Client::new(config.into(), 100); + let (client, mut connection) = Client::new(config.into(), 100); client.subscribe("#", QoS::AtLeastOnce).unwrap(); loop { From 9fe48d2c1bf1698ed9f302fe3cd71cdee68e1537 Mon Sep 17 00:00:00 2001 From: Cosmin Constantin Lazar Date: Sun, 21 Apr 2024 21:22:00 +0200 Subject: [PATCH 4/9] Remove unused test files --- .../remote_responses/addressid_not_found.html | 308 ---------------- .../traditionaladdress_not_found.html | 338 ------------------ 2 files changed, 646 deletions(-) delete mode 100644 src/mitaffald/remote_responses/addressid_not_found.html delete mode 100644 src/mitaffald/remote_responses/traditionaladdress_not_found.html diff --git a/src/mitaffald/remote_responses/addressid_not_found.html b/src/mitaffald/remote_responses/addressid_not_found.html deleted file mode 100644 index cf306e6..0000000 --- a/src/mitaffald/remote_responses/addressid_not_found.html +++ /dev/null @@ -1,308 +0,0 @@ - - - - - - - - - MitAffald – Din digitale selvbetjeningsløsning for affald - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - -
- -
- - - -
- - -
Søgningen gav intet resultat
- - - -
-
-

Ekstra service til dit affald

-
-
- -
-
-
-
    -
  • Bestille ekstra tømning af beholder
  • -
  • Få hentet ekstra restaffald i sække
  • -
  • Bestille vask af dine beholdere
  • -
  • Få repareret din beholder
  • -
-
-

Bestil nemt og hurtigt

- -
-
-
-
-

Bestil storskraldsbilen

-
-
- -
-
-
-
-

- - Ret eller annuller bestilling - -

-
- -  Find -
-
-
-
-
-
- Få afhentet dit storskrald gratis ved - din hoveddør -
-
-

- - Bestil storskraldsbilen - -

- - -
-
-
-
-
- - - -
-
-
-
-
-
-
-

Denne løsning er til privatkunder med egne affaldsbeholdere.

-
-

Se hvornår din beholder bliver tømt

-

- Indtast dit vejnavn i det første felt. Vælg det korrekte vejnavn og postnummer. - Indtast dit husnummer i næste felt. -

- - -
- -
-
-
-
-
-
- - -
-
-
- - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/mitaffald/remote_responses/traditionaladdress_not_found.html b/src/mitaffald/remote_responses/traditionaladdress_not_found.html deleted file mode 100644 index c72fdab..0000000 --- a/src/mitaffald/remote_responses/traditionaladdress_not_found.html +++ /dev/null @@ -1,338 +0,0 @@ - - - - - - - - MitAffald – Din digitale selvbetjeningsløsning for affald - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- -
- -
- -
- 100, Kongevejen 8000 Aarhus C - : fejl ved opslag på adressen. Kontakt venligst KundeService Affald på mail: kundeservicegenbrug@kredslob.dk eller telefonnummer 77 88 10 10. -
- -
-
-

Ekstra service til dit affald

-
-
- -
-
-
-
    -
  • Bestille ekstra tømning af beholder
  • -
  • Få hentet ekstra restaffald i sække
  • -
  • Bestille vask af dine beholdere
  • -
  • Få repareret din beholder
  • -
-
-

- Bestil nemt og hurtigt -

- -
-
-
-
-

Bestil storskraldsbilen

-
-
- -
-
-
-
-

- - Ret eller annuller bestilling - -

-
- -  Find -
-
-
-
-
-
- Få afhentet dit storskrald gratis ved - din hoveddør -
-
-

- - Bestil storskraldsbilen - -

- -
-
-
-
-
- - - -
-
-
-
-
-
-
-
-

Denne løsning er til privatkunder med egne affaldsbeholdere.

-
-

- Se hvornår din beholder - bliver tømt - -

-

- Indtast dit vejnavn i det første felt. Vælg det korrekte vejnavn og postnummer. - Indtast dit husnummer i næste felt. -

- - -
- -
-
- - - -
-
-
-
-
- -
-
-
- - - - - - - - - - - - - - - - - From 8468f61789fe0c80b5cdfa5269d2935ebbeeb36e Mon Sep 17 00:00:00 2001 From: Cosmin Constantin Lazar Date: Sun, 21 Apr 2024 21:28:58 +0200 Subject: [PATCH 5/9] Update integration tests --- tests/full_flow.rs | 457 ------------------ tests/full_flow_insta.rs | 2 +- .../full_flow_insta__smoke_test_insta.snap | 34 +- 3 files changed, 10 insertions(+), 483 deletions(-) delete mode 100644 tests/full_flow.rs diff --git a/tests/full_flow.rs b/tests/full_flow.rs deleted file mode 100644 index 1472c79..0000000 --- a/tests/full_flow.rs +++ /dev/null @@ -1,457 +0,0 @@ -mod hivemq; -mod mqtt; - -use crate::mqtt::CollectingClient; -use assert_json_diff::assert_json_include; -use fluent_asserter::{assert_that, create_asserter}; -use ha_mitaffald::{ - mitaffald::settings::{Address, AddressId, AffaldVarmeConfig}, - settings::Settings, - sync_data, -}; -use hivemq::HiveMQContainer; -use rumqttc::Publish; -use serde_json::Value; -use std::time::Duration; -use std::{collections::HashMap, iter::repeat}; -use testcontainers::clients; -use url::Url; - -#[tokio::test] -async fn smoke_test() { - let docker = clients::Cli::default(); - let mqtt_server = docker.run(HiveMQContainer::default()); - let mqtt_server_port = mqtt_server.get_host_port_ipv4(1883); - - let mut mit_affald_server = mockito::Server::new_async().await; - let mit_affald_server_url = Url::parse(&mit_affald_server.url()).unwrap(); - let address_id = "123".to_string(); - let mit_affald_server = mit_affald_server - .mock( - "GET", - format!("/api/calendar/address/{}", address_id).as_str(), - ) - .with_status(200) - .with_body_from_file("src/mitaffald/remote_responses/container_information.json") - .create_async() - .await; - - let settings = Settings { - affaldvarme: AffaldVarmeConfig { - address: Address::Id(AddressId { id: address_id }), - base_url: mit_affald_server_url, - }, - mqtt: ha_mitaffald::settings::MQTTConfig { - client_id: "test".to_string(), - host: "localhost".to_string(), - port: mqtt_server_port, - username: "".to_owned(), - password: "".to_owned(), - }, - }; - - let mut collecting_client = CollectingClient::new(); - collecting_client.start(&settings.mqtt); - - let sync_result = sync_data(settings).await; - - assert!( - sync_result.is_ok(), - "Error synchronizing: {:?}", - sync_result.err() - ); - - let collect_result = collecting_client.wait_for_messages(27, Duration::from_secs(60)); - - assert!( - collect_result.is_ok(), - "Error waiting for messages: {}", - collect_result.unwrap_err() - ); - - mit_affald_server.assert_async().await; - - let actual = actual(collect_result.unwrap()); - let expected = expectations(); - - expected.into_iter().for_each(|(key, expected)| { - assert_that!(&actual).contains_key(&key.to_owned()); - let actual = actual.get(key).unwrap(); - assert_eq!(actual.len(), expected.len()); - - expected.iter().zip(actual).for_each(|(expected, actual)| { - let actual_json = serde_json::from_str::(actual); - let expected_json = serde_json::from_str::(expected); - - match (actual_json, expected_json) { - (Ok(actual), Ok(expected)) => { - //assert_json_include allows actual to contain more fields than expected (e.g. timestamps) - assert_json_include!(actual: actual, expected: expected) - } - _ => assert_eq!(actual, expected), - } - }); - }); -} - -fn actual(messages: Vec) -> HashMap> { - let mut actual: HashMap> = HashMap::new(); - for message in messages { - let topic = message.topic; - let payload = String::from_utf8(message.payload.to_vec()).unwrap(); - - actual.entry(topic).or_default().push(payload); - } - - actual -} - -fn expectations() -> HashMap<&'static str, Vec<&'static str>> { - let mut expectation: HashMap<&'static str, Vec<&'static str>> = HashMap::new(); - expectation.insert( - "homeassistant/sensor/ha_affaldvarme_Madaffald/config", - vec![r#"{ - "object_id": "ha_affaldvarme_Madaffald", - "unique_id": "ha_affaldvarme_Madaffald", - "name": "Madaffald", - "state_topic": "garbage_bin/Madaffald/status", - "json_attributes_topic": "garbage_bin/Madaffald/status", - "value_template": "{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}", - "availability_topic": "garbage_bin/availability", - "payload_available": "online", - "payload_not_available": "offline", - "unit_of_measurement": "days", - "device": { - "identifiers": [ - "ha_affaldvarme" - ], - "name": "Affaldvarme integration", - "sw_version": "1.0", - "model": "Standard", - "manufacturer": "Your Garbage Bin Manufacturer" - }, - "icon": "mdi:recycle" - }"#]); - - expectation.insert( - "garbage_bin/Madaffald/status", - vec![ - r#"{ - "name": "Madaffald", - "next_empty": "2024-04-26" - }"#, - ], - ); - - expectation.insert( - "homeassistant/sensor/ha_affaldvarme_Pap/config", - vec![ - r#"{ - "object_id": "ha_affaldvarme_Pap", - "unique_id": "ha_affaldvarme_Pap", - "name": "Pap", - "state_topic": "garbage_bin/Pap/status", - "json_attributes_topic": "garbage_bin/Pap/status", - "value_template": "{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}", - "availability_topic": "garbage_bin/availability", - "payload_available": "online", - "payload_not_available": "offline", - "unit_of_measurement": "days", - "device": { - "identifiers": [ - "ha_affaldvarme" - ], - "name": "Affaldvarme integration", - "sw_version": "1.0", - "model": "Standard", - "manufacturer": "Your Garbage Bin Manufacturer" - }, - "icon": "mdi:recycle" -}"#, - ], - ); - - expectation.insert( - "garbage_bin/Pap/status", - vec![ - r#"{ - "name": "Pap", - "next_empty": "2024-05-09" - }"#, - ], - ); - - expectation.insert( - "homeassistant/sensor/ha_affaldvarme_Tekstiler/config", - vec![ - r#"{ - "object_id": "ha_affaldvarme_Tekstiler", - "unique_id": "ha_affaldvarme_Tekstiler", - "name": "Tekstiler", - "state_topic": "garbage_bin/Tekstiler/status", - "json_attributes_topic": "garbage_bin/Tekstiler/status", - "value_template": "{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}", - "availability_topic": "garbage_bin/availability", - "payload_available": "online", - "payload_not_available": "offline", - "unit_of_measurement": "days", - "device": { - "identifiers": [ - "ha_affaldvarme" - ], - "name": "Affaldvarme integration", - "sw_version": "1.0", - "model": "Standard", - "manufacturer": "Your Garbage Bin Manufacturer" - }, - "icon": "mdi:recycle" - }"#, - ], - ); - - expectation.insert( - "garbage_bin/Tekstiler/status", - vec![ - r#"{ - "name": "Tekstiler", - "next_empty": "2024-05-09" - }"#, - ], - ); - - expectation.insert( - "homeassistant/sensor/ha_affaldvarme_Plast/config", - vec![ - r#"{ - "object_id": "ha_affaldvarme_Plast", - "unique_id": "ha_affaldvarme_Plast", - "name": "Plast", - "state_topic": "garbage_bin/Plast/status", - "json_attributes_topic": "garbage_bin/Plast/status", - "value_template": "{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}", - "availability_topic": "garbage_bin/availability", - "payload_available": "online", - "payload_not_available": "offline", - "unit_of_measurement": "days", - "device": { - "identifiers": [ - "ha_affaldvarme" - ], - "name": "Affaldvarme integration", - "sw_version": "1.0", - "model": "Standard", - "manufacturer": "Your Garbage Bin Manufacturer" - }, - "icon": "mdi:recycle" - }"#, - ], - ); - - expectation.insert( - "garbage_bin/Plast/status", - vec![ - r#"{ - "name": "Plast", - "next_empty": "2024-04-18" - }"#, - ], - ); - - expectation.insert( - "homeassistant/sensor/ha_affaldvarme_Glas/config", - vec![ - r#"{ - "object_id": "ha_affaldvarme_Glas", - "unique_id": "ha_affaldvarme_Glas", - "name": "Glas", - "state_topic": "garbage_bin/Glas/status", - "json_attributes_topic": "garbage_bin/Glas/status", - "value_template": "{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}", - "availability_topic": "garbage_bin/availability", - "payload_available": "online", - "payload_not_available": "offline", - "unit_of_measurement": "days", - "device": { - "identifiers": [ - "ha_affaldvarme" - ], - "name": "Affaldvarme integration", - "sw_version": "1.0", - "model": "Standard", - "manufacturer": "Your Garbage Bin Manufacturer" - }, - "icon": "mdi:recycle" - }"#, - ], - ); - - expectation.insert( - "garbage_bin/Glas/status", - vec![ - r#"{ - "name": "Glas", - "next_empty": "2024-04-18" - }"#, - ], - ); - - expectation.insert( - "homeassistant/sensor/ha_affaldvarme_Metal/config", - vec![ - r#"{ - "object_id": "ha_affaldvarme_Metal", - "unique_id": "ha_affaldvarme_Metal", - "name": "Metal", - "state_topic": "garbage_bin/Metal/status", - "json_attributes_topic": "garbage_bin/Metal/status", - "value_template": "{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}", - "availability_topic": "garbage_bin/availability", - "payload_available": "online", - "payload_not_available": "offline", - "unit_of_measurement": "days", - "device": { - "identifiers": [ - "ha_affaldvarme" - ], - "name": "Affaldvarme integration", - "sw_version": "1.0", - "model": "Standard", - "manufacturer": "Your Garbage Bin Manufacturer" - }, - "icon": "mdi:recycle" - }"#, - ], - ); - - expectation.insert( - "garbage_bin/Metal/status", - vec![ - r#"{ - "name": "Metal", - "next_empty": "2024-04-18" - }"#, - ], - ); - - expectation.insert( - "homeassistant/sensor/ha_affaldvarme_Restaffald/config", - vec![ - r#"{ - "object_id": "ha_affaldvarme_Restaffald", - "unique_id": "ha_affaldvarme_Restaffald", - "name": "Restaffald", - "state_topic": "garbage_bin/Restaffald/status", - "json_attributes_topic": "garbage_bin/Restaffald/status", - "value_template": "{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}", - "availability_topic": "garbage_bin/availability", - "payload_available": "online", - "payload_not_available": "offline", - "unit_of_measurement": "days", - "device": { - "identifiers": [ - "ha_affaldvarme" - ], - "name": "Affaldvarme integration", - "sw_version": "1.0", - "model": "Standard", - "manufacturer": "Your Garbage Bin Manufacturer" - }, - "icon": "mdi:recycle" - }"#, - ], - ); - - expectation.insert( - "garbage_bin/Restaffald/status", - vec![ - r#"{ - "name": "Restaffald", - "next_empty": "2024-04-26" - }"#, - ], - ); - - expectation.insert( - "homeassistant/sensor/ha_affaldvarme_Papir/config", - vec![ - r#"{ - "object_id": "ha_affaldvarme_Papir", - "unique_id": "ha_affaldvarme_Papir", - "name": "Papir", - "state_topic": "garbage_bin/Papir/status", - "json_attributes_topic": "garbage_bin/Papir/status", - "value_template": "{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}", - "availability_topic": "garbage_bin/availability", - "payload_available": "online", - "payload_not_available": "offline", - "unit_of_measurement": "days", - "device": { - "identifiers": [ - "ha_affaldvarme" - ], - "name": "Affaldvarme integration", - "sw_version": "1.0", - "model": "Standard", - "manufacturer": "Your Garbage Bin Manufacturer" - }, - "icon": "mdi:recycle" - }"#, - ], - ); - - expectation.insert( - "garbage_bin/Papir/status", - vec![ - r#"{ - "name": "Papir", - "next_empty": "2024-05-09" - }"#, - ], - ); - - expectation.insert( - "homeassistant/sensor/ha_affaldvarme_Mad__og_drikkekartoner/config", - vec![ - r#"{ - "object_id": "ha_affaldvarme_Mad__og_drikkekartoner", - "unique_id": "ha_affaldvarme_Mad__og_drikkekartoner", - "name": "Mad- og drikkekartoner", - "state_topic": "garbage_bin/Mad__og_drikkekartoner/status", - "json_attributes_topic": "garbage_bin/Mad__og_drikkekartoner/status", - "value_template": "{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}", - "availability_topic": "garbage_bin/availability", - "payload_available": "online", - "payload_not_available": "offline", - "unit_of_measurement": "days", - "device": { - "identifiers": [ - "ha_affaldvarme" - ], - "name": "Affaldvarme integration", - "sw_version": "1.0", - "model": "Standard", - "manufacturer": "Your Garbage Bin Manufacturer" - }, - "icon": "mdi:recycle" - }"#, - ], - ); - - expectation.insert( - "garbage_bin/Mad__og_drikkekartoner/status", - vec![ - r#"{ - "name": "Mad- og drikkekartoner", - "next_empty": "2024-04-18" - }"#, - ], - ); - - //expectation.insert("garbage_bin/availability", vec!["online", "online"]); - expectation.insert( - "garbage_bin/availability", - repeat("online").take(9).collect(), - ); - - expectation -} diff --git a/tests/full_flow_insta.rs b/tests/full_flow_insta.rs index 0c4e867..90d0cbc 100644 --- a/tests/full_flow_insta.rs +++ b/tests/full_flow_insta.rs @@ -58,7 +58,7 @@ async fn smoke_test_insta() { sync_result.err() ); - let collect_result = collecting_client.wait_for_messages(27, Duration::from_secs(60)); + let collect_result = collecting_client.wait_for_messages(19, Duration::from_secs(60)); assert!( collect_result.is_ok(), diff --git a/tests/snapshots/full_flow_insta__smoke_test_insta.snap b/tests/snapshots/full_flow_insta__smoke_test_insta.snap index d559d6a..e427fb5 100644 --- a/tests/snapshots/full_flow_insta__smoke_test_insta.snap +++ b/tests/snapshots/full_flow_insta__smoke_test_insta.snap @@ -22,37 +22,21 @@ expression: actual payload: "\n { \n \"name\": \"Tekstiler\",\n \"next_empty\": \"2024-05-09\",\n \"last_update\": \"[REDACTED]\"\n }" - topic: garbage_bin/availability payload: online -- topic: garbage_bin/availability - payload: online -- topic: garbage_bin/availability - payload: online -- topic: garbage_bin/availability - payload: online -- topic: garbage_bin/availability - payload: online -- topic: garbage_bin/availability - payload: online -- topic: garbage_bin/availability - payload: online -- topic: garbage_bin/availability - payload: online -- topic: garbage_bin/availability - payload: online - topic: homeassistant/sensor/ha_affaldvarme_Glas/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Glas\",\n \"unique_id\": \"ha_affaldvarme_Glas\",\n \"name\": \"Glas\",\n \"state_topic\": \"garbage_bin/Glas/status\",\n \"json_attributes_topic\": \"garbage_bin/Glas/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n }, \n \"icon\": \"mdi:recycle\"\n }" + payload: "{\n \"object_id\": \"ha_affaldvarme_Glas\",\n \"unique_id\": \"ha_affaldvarme_Glas\",\n \"name\": \"Glas\",\n \"state_topic\": \"garbage_bin/Glas/status\",\n \"json_attributes_topic\": \"garbage_bin/Glas/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" - topic: homeassistant/sensor/ha_affaldvarme_Mad__og_drikkekartoner/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Mad__og_drikkekartoner\",\n \"unique_id\": \"ha_affaldvarme_Mad__og_drikkekartoner\",\n \"name\": \"Mad- og drikkekartoner\",\n \"state_topic\": \"garbage_bin/Mad__og_drikkekartoner/status\",\n \"json_attributes_topic\": \"garbage_bin/Mad__og_drikkekartoner/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n }, \n \"icon\": \"mdi:recycle\"\n }" + payload: "{\n \"object_id\": \"ha_affaldvarme_Mad__og_drikkekartoner\",\n \"unique_id\": \"ha_affaldvarme_Mad__og_drikkekartoner\",\n \"name\": \"Mad- og drikkekartoner\",\n \"state_topic\": \"garbage_bin/Mad__og_drikkekartoner/status\",\n \"json_attributes_topic\": \"garbage_bin/Mad__og_drikkekartoner/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" - topic: homeassistant/sensor/ha_affaldvarme_Madaffald/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Madaffald\",\n \"unique_id\": \"ha_affaldvarme_Madaffald\",\n \"name\": \"Madaffald\",\n \"state_topic\": \"garbage_bin/Madaffald/status\",\n \"json_attributes_topic\": \"garbage_bin/Madaffald/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n }, \n \"icon\": \"mdi:recycle\"\n }" + payload: "{\n \"object_id\": \"ha_affaldvarme_Madaffald\",\n \"unique_id\": \"ha_affaldvarme_Madaffald\",\n \"name\": \"Madaffald\",\n \"state_topic\": \"garbage_bin/Madaffald/status\",\n \"json_attributes_topic\": \"garbage_bin/Madaffald/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" - topic: homeassistant/sensor/ha_affaldvarme_Metal/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Metal\",\n \"unique_id\": \"ha_affaldvarme_Metal\",\n \"name\": \"Metal\",\n \"state_topic\": \"garbage_bin/Metal/status\",\n \"json_attributes_topic\": \"garbage_bin/Metal/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n }, \n \"icon\": \"mdi:recycle\"\n }" + payload: "{\n \"object_id\": \"ha_affaldvarme_Metal\",\n \"unique_id\": \"ha_affaldvarme_Metal\",\n \"name\": \"Metal\",\n \"state_topic\": \"garbage_bin/Metal/status\",\n \"json_attributes_topic\": \"garbage_bin/Metal/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" - topic: homeassistant/sensor/ha_affaldvarme_Pap/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Pap\",\n \"unique_id\": \"ha_affaldvarme_Pap\",\n \"name\": \"Pap\",\n \"state_topic\": \"garbage_bin/Pap/status\",\n \"json_attributes_topic\": \"garbage_bin/Pap/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n }, \n \"icon\": \"mdi:recycle\"\n }" + payload: "{\n \"object_id\": \"ha_affaldvarme_Pap\",\n \"unique_id\": \"ha_affaldvarme_Pap\",\n \"name\": \"Pap\",\n \"state_topic\": \"garbage_bin/Pap/status\",\n \"json_attributes_topic\": \"garbage_bin/Pap/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" - topic: homeassistant/sensor/ha_affaldvarme_Papir/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Papir\",\n \"unique_id\": \"ha_affaldvarme_Papir\",\n \"name\": \"Papir\",\n \"state_topic\": \"garbage_bin/Papir/status\",\n \"json_attributes_topic\": \"garbage_bin/Papir/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n }, \n \"icon\": \"mdi:recycle\"\n }" + payload: "{\n \"object_id\": \"ha_affaldvarme_Papir\",\n \"unique_id\": \"ha_affaldvarme_Papir\",\n \"name\": \"Papir\",\n \"state_topic\": \"garbage_bin/Papir/status\",\n \"json_attributes_topic\": \"garbage_bin/Papir/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" - topic: homeassistant/sensor/ha_affaldvarme_Plast/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Plast\",\n \"unique_id\": \"ha_affaldvarme_Plast\",\n \"name\": \"Plast\",\n \"state_topic\": \"garbage_bin/Plast/status\",\n \"json_attributes_topic\": \"garbage_bin/Plast/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n }, \n \"icon\": \"mdi:recycle\"\n }" + payload: "{\n \"object_id\": \"ha_affaldvarme_Plast\",\n \"unique_id\": \"ha_affaldvarme_Plast\",\n \"name\": \"Plast\",\n \"state_topic\": \"garbage_bin/Plast/status\",\n \"json_attributes_topic\": \"garbage_bin/Plast/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" - topic: homeassistant/sensor/ha_affaldvarme_Restaffald/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Restaffald\",\n \"unique_id\": \"ha_affaldvarme_Restaffald\",\n \"name\": \"Restaffald\",\n \"state_topic\": \"garbage_bin/Restaffald/status\",\n \"json_attributes_topic\": \"garbage_bin/Restaffald/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n }, \n \"icon\": \"mdi:recycle\"\n }" + payload: "{\n \"object_id\": \"ha_affaldvarme_Restaffald\",\n \"unique_id\": \"ha_affaldvarme_Restaffald\",\n \"name\": \"Restaffald\",\n \"state_topic\": \"garbage_bin/Restaffald/status\",\n \"json_attributes_topic\": \"garbage_bin/Restaffald/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" - topic: homeassistant/sensor/ha_affaldvarme_Tekstiler/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Tekstiler\",\n \"unique_id\": \"ha_affaldvarme_Tekstiler\",\n \"name\": \"Tekstiler\",\n \"state_topic\": \"garbage_bin/Tekstiler/status\",\n \"json_attributes_topic\": \"garbage_bin/Tekstiler/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n }, \n \"icon\": \"mdi:recycle\"\n }" + payload: "{\n \"object_id\": \"ha_affaldvarme_Tekstiler\",\n \"unique_id\": \"ha_affaldvarme_Tekstiler\",\n \"name\": \"Tekstiler\",\n \"state_topic\": \"garbage_bin/Tekstiler/status\",\n \"json_attributes_topic\": \"garbage_bin/Tekstiler/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" From 1cf9b7572daa04d7202c3aa0fda0d3e26da07cd9 Mon Sep 17 00:00:00 2001 From: Cosmin Constantin Lazar Date: Sun, 21 Apr 2024 21:36:32 +0200 Subject: [PATCH 6/9] Minor improvement --- src/lib.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 853a7d8..dfcf28f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,9 +10,8 @@ pub mod settings; pub async fn sync_data(settings: Settings) -> Result<(), String> { let mut device = homeassistant::HADevice::default(); let (mut client, mut connection) = AsyncClient::new(settings.mqtt.into(), 200); - let mut has_errors = false; - for container in get_containers(settings.affaldvarme) + let containers_to_report = get_containers(settings.affaldvarme) .await? .into_iter() .fold( @@ -32,12 +31,17 @@ pub async fn sync_data(settings: Settings) -> Result<(), String> { accumulator }, ) - .into_values() - { - let report_result = device.report(container, &mut client).await; + .into_values(); - has_errors = has_errors || report_result.is_err(); - } + let has_errors = { + let mut has_errors = false; + for container in containers_to_report { + let report_result = device.report(container, &mut client).await; + + has_errors = has_errors || report_result.is_err(); + } + has_errors + }; //calling disconnect() causes an error in the connection iterator if let Err(x) = client.disconnect().await { From e30840af02ec7711294f081e5b2f0ffbc3d7e943 Mon Sep 17 00:00:00 2001 From: Cosmin Constantin Lazar Date: Sun, 21 Apr 2024 22:00:10 +0200 Subject: [PATCH 7/9] Minor changes --- src/homeassistant/mod.rs | 53 ++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/homeassistant/mod.rs b/src/homeassistant/mod.rs index efa8aaf..bf530db 100644 --- a/src/homeassistant/mod.rs +++ b/src/homeassistant/mod.rs @@ -34,31 +34,42 @@ impl HADevice { container: Container, client: &mut AsyncClient, ) -> Result<(), rumqttc::ClientError> { - if !self.is_initialized { - self.register_device_availability(client).await?; - self.is_initialized = true; - } - let sensor_id = HASensor::generate_sensor_id(&container); - self.sensors + let report_result = self + .sensors .entry(sensor_id.clone()) .or_insert_with(|| HASensor::new(&container)) .report(container, client) - .await + .await; + + if report_result.is_ok() { + self.register_device_availability(client).await?; + } + report_result } async fn register_device_availability( - &self, + &mut self, client: &mut AsyncClient, ) -> Result<(), rumqttc::ClientError> { - client + if self.is_initialized { + return Ok(()); + } + + let publish_result = client .publish( HA_AVAILABILITY_TOPIC, rumqttc::QoS::AtLeastOnce, true, "online", ) - .await + .await; + + if publish_result.is_ok() { + self.is_initialized = true; + } + + publish_result } } @@ -98,13 +109,9 @@ impl HASensor { container: Container, client: &mut AsyncClient, ) -> Result<(), rumqttc::ClientError> { - if !self.is_initialized { - self.register_sensor(&container, client).await?; - self.is_initialized = true; - } + self.register_sensor(&container, client).await?; - self.register_sensor_value(&container, client).await?; - Ok(()) + self.register_sensor_value(&container, client).await } async fn register_sensor( @@ -112,6 +119,10 @@ impl HASensor { container: &Container, client: &mut AsyncClient, ) -> Result<(), rumqttc::ClientError> { + if self.is_initialized { + return Ok(()); + } + let payload = format!( r#"{{ "object_id": "ha_affaldvarme_{id}", @@ -141,14 +152,20 @@ impl HASensor { id = self.container_id, ); - client + let publish_result = client .publish( &self.configure_topic, rumqttc::QoS::AtLeastOnce, false, payload, ) - .await + .await; + + if publish_result.is_ok() { + self.is_initialized = true; + } + + publish_result } async fn register_sensor_value( From 73a796545073480e3335e0d2690112e4a7d571aa Mon Sep 17 00:00:00 2001 From: Cosmin Constantin Lazar Date: Sun, 21 Apr 2024 22:11:34 +0200 Subject: [PATCH 8/9] Use json! insted of n00b string interpolation --- src/homeassistant/mod.rs | 74 +++++++++---------- .../full_flow_insta__smoke_test_insta.snap | 36 ++++----- 2 files changed, 53 insertions(+), 57 deletions(-) diff --git a/src/homeassistant/mod.rs b/src/homeassistant/mod.rs index bf530db..94e87a0 100644 --- a/src/homeassistant/mod.rs +++ b/src/homeassistant/mod.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use crate::mitaffald::Container; use crate::settings::MQTTConfig; use rumqttc::{AsyncClient, LastWill, MqttOptions}; +use serde_json::json; const HA_AVAILABILITY_TOPIC: &str = "garbage_bin/availability"; @@ -123,33 +124,27 @@ impl HASensor { return Ok(()); } - let payload = format!( - r#"{{ - "object_id": "ha_affaldvarme_{id}", - "unique_id": "ha_affaldvarme_{id}", - "name": "{sensor_name}", - "state_topic": "{state_topic}", - "json_attributes_topic": "{state_topic}", - "value_template": "{{{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}}}", - "availability_topic": "{availability_topic}", - "payload_available": "online", - "payload_not_available": "offline", - "unit_of_measurement": "days", - "device": {{ - "identifiers": [ - "ha_affaldvarme" - ], - "name": "Affaldvarme integration", - "sw_version": "1.0", - "model": "Standard", - "manufacturer": "Your Garbage Bin Manufacturer" - }}, - "icon": "mdi:recycle" - }}"#, - sensor_name = container.name, - state_topic = self.state_topic, - availability_topic = HA_AVAILABILITY_TOPIC, - id = self.container_id, + let payload = json!( + { + "object_id": format!("ha_affaldvarme_{}", self.container_id), + "unique_id": format!("ha_affaldvarme_{}", self.container_id), + "name": container.name, + "state_topic": self.state_topic, + "json_attributes_topic": self.state_topic, + "value_template": "{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}", + "availability_topic": HA_AVAILABILITY_TOPIC, + "payload_available": "online", + "payload_not_available": "offline", + "unit_of_measurement": "days", + "device": { + "identifiers": ["ha_affaldvarme"], + "name": "Affaldvarme integration", + "sw_version": "1.0", + "model": "Standard", + "manufacturer": "Your Garbage Bin Manufacturer" + }, + "icon": "mdi:recycle" + } ); let publish_result = client @@ -157,7 +152,7 @@ impl HASensor { &self.configure_topic, rumqttc::QoS::AtLeastOnce, false, - payload, + serde_json::to_string(&payload).expect("Failed to serialize"), ) .await; @@ -173,20 +168,21 @@ impl HASensor { container: &Container, client: &mut AsyncClient, ) -> Result<(), rumqttc::ClientError> { - let payload = format!( - r#" - {{ - "name": "{sensor_name}", - "next_empty": "{next_empty}", - "last_update": "{last_update}" - }}"#, - sensor_name = container.name, - next_empty = container.date.format("%Y-%m-%d"), - last_update = chrono::Local::now().to_rfc3339(), + let payload = json!( + { + "name": container.name, + "next_empty": container.date.format("%Y-%m-%d").to_string(), + "last_update": chrono::Local::now().to_rfc3339() + } ); client - .publish(&self.state_topic, rumqttc::QoS::AtLeastOnce, false, payload) + .publish( + &self.state_topic, + rumqttc::QoS::AtLeastOnce, + false, + serde_json::to_string(&payload).expect("Failed to serialize"), + ) .await } } diff --git a/tests/snapshots/full_flow_insta__smoke_test_insta.snap b/tests/snapshots/full_flow_insta__smoke_test_insta.snap index e427fb5..676d39d 100644 --- a/tests/snapshots/full_flow_insta__smoke_test_insta.snap +++ b/tests/snapshots/full_flow_insta__smoke_test_insta.snap @@ -3,40 +3,40 @@ source: tests/full_flow_insta.rs expression: actual --- - topic: garbage_bin/Glas/status - payload: "\n { \n \"name\": \"Glas\",\n \"next_empty\": \"2024-04-18\",\n \"last_update\": \"[REDACTED]\"\n }" + payload: "{\"last_update\": \"[REDACTED]\",\"name\":\"Glas\",\"next_empty\":\"2024-04-18\"}" - topic: garbage_bin/Mad__og_drikkekartoner/status - payload: "\n { \n \"name\": \"Mad- og drikkekartoner\",\n \"next_empty\": \"2024-04-18\",\n \"last_update\": \"[REDACTED]\"\n }" + payload: "{\"last_update\": \"[REDACTED]\",\"name\":\"Mad- og drikkekartoner\",\"next_empty\":\"2024-04-18\"}" - topic: garbage_bin/Madaffald/status - payload: "\n { \n \"name\": \"Madaffald\",\n \"next_empty\": \"2024-04-26\",\n \"last_update\": \"[REDACTED]\"\n }" + payload: "{\"last_update\": \"[REDACTED]\",\"name\":\"Madaffald\",\"next_empty\":\"2024-04-26\"}" - topic: garbage_bin/Metal/status - payload: "\n { \n \"name\": \"Metal\",\n \"next_empty\": \"2024-04-18\",\n \"last_update\": \"[REDACTED]\"\n }" + payload: "{\"last_update\": \"[REDACTED]\",\"name\":\"Metal\",\"next_empty\":\"2024-04-18\"}" - topic: garbage_bin/Pap/status - payload: "\n { \n \"name\": \"Pap\",\n \"next_empty\": \"2024-05-09\",\n \"last_update\": \"[REDACTED]\"\n }" + payload: "{\"last_update\": \"[REDACTED]\",\"name\":\"Pap\",\"next_empty\":\"2024-05-09\"}" - topic: garbage_bin/Papir/status - payload: "\n { \n \"name\": \"Papir\",\n \"next_empty\": \"2024-05-09\",\n \"last_update\": \"[REDACTED]\"\n }" + payload: "{\"last_update\": \"[REDACTED]\",\"name\":\"Papir\",\"next_empty\":\"2024-05-09\"}" - topic: garbage_bin/Plast/status - payload: "\n { \n \"name\": \"Plast\",\n \"next_empty\": \"2024-04-18\",\n \"last_update\": \"[REDACTED]\"\n }" + payload: "{\"last_update\": \"[REDACTED]\",\"name\":\"Plast\",\"next_empty\":\"2024-04-18\"}" - topic: garbage_bin/Restaffald/status - payload: "\n { \n \"name\": \"Restaffald\",\n \"next_empty\": \"2024-04-26\",\n \"last_update\": \"[REDACTED]\"\n }" + payload: "{\"last_update\": \"[REDACTED]\",\"name\":\"Restaffald\",\"next_empty\":\"2024-04-26\"}" - topic: garbage_bin/Tekstiler/status - payload: "\n { \n \"name\": \"Tekstiler\",\n \"next_empty\": \"2024-05-09\",\n \"last_update\": \"[REDACTED]\"\n }" + payload: "{\"last_update\": \"[REDACTED]\",\"name\":\"Tekstiler\",\"next_empty\":\"2024-05-09\"}" - topic: garbage_bin/availability payload: online - topic: homeassistant/sensor/ha_affaldvarme_Glas/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Glas\",\n \"unique_id\": \"ha_affaldvarme_Glas\",\n \"name\": \"Glas\",\n \"state_topic\": \"garbage_bin/Glas/status\",\n \"json_attributes_topic\": \"garbage_bin/Glas/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Glas/status\",\"name\":\"Glas\",\"object_id\":\"ha_affaldvarme_Glas\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Glas/status\",\"unique_id\":\"ha_affaldvarme_Glas\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Mad__og_drikkekartoner/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Mad__og_drikkekartoner\",\n \"unique_id\": \"ha_affaldvarme_Mad__og_drikkekartoner\",\n \"name\": \"Mad- og drikkekartoner\",\n \"state_topic\": \"garbage_bin/Mad__og_drikkekartoner/status\",\n \"json_attributes_topic\": \"garbage_bin/Mad__og_drikkekartoner/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Mad__og_drikkekartoner/status\",\"name\":\"Mad- og drikkekartoner\",\"object_id\":\"ha_affaldvarme_Mad__og_drikkekartoner\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Mad__og_drikkekartoner/status\",\"unique_id\":\"ha_affaldvarme_Mad__og_drikkekartoner\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Madaffald/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Madaffald\",\n \"unique_id\": \"ha_affaldvarme_Madaffald\",\n \"name\": \"Madaffald\",\n \"state_topic\": \"garbage_bin/Madaffald/status\",\n \"json_attributes_topic\": \"garbage_bin/Madaffald/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Madaffald/status\",\"name\":\"Madaffald\",\"object_id\":\"ha_affaldvarme_Madaffald\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Madaffald/status\",\"unique_id\":\"ha_affaldvarme_Madaffald\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Metal/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Metal\",\n \"unique_id\": \"ha_affaldvarme_Metal\",\n \"name\": \"Metal\",\n \"state_topic\": \"garbage_bin/Metal/status\",\n \"json_attributes_topic\": \"garbage_bin/Metal/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Metal/status\",\"name\":\"Metal\",\"object_id\":\"ha_affaldvarme_Metal\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Metal/status\",\"unique_id\":\"ha_affaldvarme_Metal\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Pap/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Pap\",\n \"unique_id\": \"ha_affaldvarme_Pap\",\n \"name\": \"Pap\",\n \"state_topic\": \"garbage_bin/Pap/status\",\n \"json_attributes_topic\": \"garbage_bin/Pap/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Pap/status\",\"name\":\"Pap\",\"object_id\":\"ha_affaldvarme_Pap\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Pap/status\",\"unique_id\":\"ha_affaldvarme_Pap\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Papir/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Papir\",\n \"unique_id\": \"ha_affaldvarme_Papir\",\n \"name\": \"Papir\",\n \"state_topic\": \"garbage_bin/Papir/status\",\n \"json_attributes_topic\": \"garbage_bin/Papir/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Papir/status\",\"name\":\"Papir\",\"object_id\":\"ha_affaldvarme_Papir\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Papir/status\",\"unique_id\":\"ha_affaldvarme_Papir\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Plast/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Plast\",\n \"unique_id\": \"ha_affaldvarme_Plast\",\n \"name\": \"Plast\",\n \"state_topic\": \"garbage_bin/Plast/status\",\n \"json_attributes_topic\": \"garbage_bin/Plast/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Plast/status\",\"name\":\"Plast\",\"object_id\":\"ha_affaldvarme_Plast\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Plast/status\",\"unique_id\":\"ha_affaldvarme_Plast\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Restaffald/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Restaffald\",\n \"unique_id\": \"ha_affaldvarme_Restaffald\",\n \"name\": \"Restaffald\",\n \"state_topic\": \"garbage_bin/Restaffald/status\",\n \"json_attributes_topic\": \"garbage_bin/Restaffald/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Restaffald/status\",\"name\":\"Restaffald\",\"object_id\":\"ha_affaldvarme_Restaffald\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Restaffald/status\",\"unique_id\":\"ha_affaldvarme_Restaffald\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Tekstiler/config - payload: "{\n \"object_id\": \"ha_affaldvarme_Tekstiler\",\n \"unique_id\": \"ha_affaldvarme_Tekstiler\",\n \"name\": \"Tekstiler\",\n \"state_topic\": \"garbage_bin/Tekstiler/status\",\n \"json_attributes_topic\": \"garbage_bin/Tekstiler/status\",\n \"value_template\": \"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\",\n \"availability_topic\": \"garbage_bin/availability\",\n \"payload_available\": \"online\",\n \"payload_not_available\": \"offline\",\n \"unit_of_measurement\": \"days\",\n \"device\": {\n \"identifiers\": [\n \"ha_affaldvarme\"\n ],\n \"name\": \"Affaldvarme integration\",\n \"sw_version\": \"1.0\",\n \"model\": \"Standard\",\n \"manufacturer\": \"Your Garbage Bin Manufacturer\"\n },\n \"icon\": \"mdi:recycle\"\n }" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Tekstiler/status\",\"name\":\"Tekstiler\",\"object_id\":\"ha_affaldvarme_Tekstiler\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Tekstiler/status\",\"unique_id\":\"ha_affaldvarme_Tekstiler\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" From a8554a0362530d905c4c69a81aa2679e61875f8f Mon Sep 17 00:00:00 2001 From: Cosmin Constantin Lazar Date: Sun, 21 Apr 2024 23:05:52 +0200 Subject: [PATCH 9/9] Encode requirements into type system --- src/homeassistant/mod.rs | 124 ++++++++++++------ src/lib.rs | 4 +- tests/full_flow_insta.rs | 2 +- .../full_flow_insta__smoke_test_insta.snap | 20 +-- 4 files changed, 101 insertions(+), 49 deletions(-) diff --git a/src/homeassistant/mod.rs b/src/homeassistant/mod.rs index 94e87a0..78d7f24 100644 --- a/src/homeassistant/mod.rs +++ b/src/homeassistant/mod.rs @@ -6,6 +6,8 @@ use rumqttc::{AsyncClient, LastWill, MqttOptions}; use serde_json::json; const HA_AVAILABILITY_TOPIC: &str = "garbage_bin/availability"; +const HA_PAYLOAD_AVAILABLE: &str = "online"; +const HA_PAYLOAD_NOT_AVAILABLE: &str = "offline"; impl From for MqttOptions { fn from(val: MQTTConfig) -> Self { @@ -14,7 +16,7 @@ impl From for MqttOptions { .set_credentials(val.username, val.password) .set_last_will(LastWill::new( HA_AVAILABILITY_TOPIC, - "offline", + HA_PAYLOAD_NOT_AVAILABLE, rumqttc::QoS::AtLeastOnce, true, )); @@ -23,54 +25,104 @@ impl From for MqttOptions { } } -#[derive(Default)] -pub struct HADevice { +pub struct CreatedState; +pub struct InitializedState { sensors: HashMap, - is_initialized: bool, } -impl HADevice { - pub async fn report( +pub struct HADevice { + state: T, +} + +impl Default for HADevice { + fn default() -> Self { + HADevice { + state: CreatedState, + } + } +} + +impl HADevice { + pub async fn initialize( + mut self, + client: &mut AsyncClient, + ) -> Result, String> { + self.register_device(client) + .await + .map_err(|e| e.to_string())?; + + self.register_device_availability(client) + .await + .map_err(|e| e.to_string())?; + + Ok(HADevice { + state: InitializedState { + sensors: HashMap::new(), + }, + }) + } + + async fn register_device( &mut self, - container: Container, client: &mut AsyncClient, ) -> Result<(), rumqttc::ClientError> { - let sensor_id = HASensor::generate_sensor_id(&container); - let report_result = self - .sensors - .entry(sensor_id.clone()) - .or_insert_with(|| HASensor::new(&container)) - .report(container, client) - .await; + let payload = json!( + { + "unique_id": "ha_affaldvarme_device", + "name": "Affaldvarme Device", + "state_topic": HA_AVAILABILITY_TOPIC, + "availability_topic": HA_AVAILABILITY_TOPIC, + "payload_available": HA_PAYLOAD_AVAILABLE, + "payload_not_available": HA_PAYLOAD_NOT_AVAILABLE, + "device": { + "identifiers": ["ha_affaldvarme"], + "name": "Affaldvarme integration", + "sw_version": "1.0", + "model": "Standard", + "manufacturer": "Your humble rust developer" + } + } + ); - if report_result.is_ok() { - self.register_device_availability(client).await?; - } - report_result + client + .publish( + "homeassistant/sensor/ha_affaldvarme_device/config", + rumqttc::QoS::AtLeastOnce, + true, + serde_json::to_string(&payload).expect("Failed to serialize"), + ) + .await } async fn register_device_availability( &mut self, client: &mut AsyncClient, ) -> Result<(), rumqttc::ClientError> { - if self.is_initialized { - return Ok(()); - } - - let publish_result = client + client .publish( HA_AVAILABILITY_TOPIC, rumqttc::QoS::AtLeastOnce, true, - "online", + HA_PAYLOAD_AVAILABLE, ) - .await; - - if publish_result.is_ok() { - self.is_initialized = true; - } + .await + } +} - publish_result +impl HADevice { + pub async fn report( + &mut self, + container: Container, + client: &mut AsyncClient, + ) -> Result<(), String> { + let sensor_id = HASensor::generate_sensor_id(&container); + self.state + .sensors + .entry(sensor_id.clone()) + .or_insert_with(|| HASensor::new(&container)) + .report(container, client) + .await + .map_err(|e| e.to_string()) } } @@ -105,7 +157,7 @@ impl HASensor { .collect() } - pub async fn report( + async fn report( &mut self, container: Container, client: &mut AsyncClient, @@ -133,15 +185,11 @@ impl HASensor { "json_attributes_topic": self.state_topic, "value_template": "{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}", "availability_topic": HA_AVAILABILITY_TOPIC, - "payload_available": "online", - "payload_not_available": "offline", + "payload_available": HA_PAYLOAD_AVAILABLE, + "payload_not_available": HA_PAYLOAD_NOT_AVAILABLE, "unit_of_measurement": "days", "device": { - "identifiers": ["ha_affaldvarme"], - "name": "Affaldvarme integration", - "sw_version": "1.0", - "model": "Standard", - "manufacturer": "Your Garbage Bin Manufacturer" + "identifiers": ["ha_affaldvarme"] }, "icon": "mdi:recycle" } diff --git a/src/lib.rs b/src/lib.rs index dfcf28f..e487cde 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,8 +8,10 @@ pub mod mitaffald; pub mod settings; pub async fn sync_data(settings: Settings) -> Result<(), String> { - let mut device = homeassistant::HADevice::default(); let (mut client, mut connection) = AsyncClient::new(settings.mqtt.into(), 200); + let device = homeassistant::HADevice::default(); + + let mut device = device.initialize(&mut client).await?; let containers_to_report = get_containers(settings.affaldvarme) .await? diff --git a/tests/full_flow_insta.rs b/tests/full_flow_insta.rs index 90d0cbc..eede55a 100644 --- a/tests/full_flow_insta.rs +++ b/tests/full_flow_insta.rs @@ -58,7 +58,7 @@ async fn smoke_test_insta() { sync_result.err() ); - let collect_result = collecting_client.wait_for_messages(19, Duration::from_secs(60)); + let collect_result = collecting_client.wait_for_messages(20, Duration::from_secs(60)); assert!( collect_result.is_ok(), diff --git a/tests/snapshots/full_flow_insta__smoke_test_insta.snap b/tests/snapshots/full_flow_insta__smoke_test_insta.snap index 676d39d..8487376 100644 --- a/tests/snapshots/full_flow_insta__smoke_test_insta.snap +++ b/tests/snapshots/full_flow_insta__smoke_test_insta.snap @@ -23,20 +23,22 @@ expression: actual - topic: garbage_bin/availability payload: online - topic: homeassistant/sensor/ha_affaldvarme_Glas/config - payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Glas/status\",\"name\":\"Glas\",\"object_id\":\"ha_affaldvarme_Glas\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Glas/status\",\"unique_id\":\"ha_affaldvarme_Glas\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"]},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Glas/status\",\"name\":\"Glas\",\"object_id\":\"ha_affaldvarme_Glas\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Glas/status\",\"unique_id\":\"ha_affaldvarme_Glas\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Mad__og_drikkekartoner/config - payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Mad__og_drikkekartoner/status\",\"name\":\"Mad- og drikkekartoner\",\"object_id\":\"ha_affaldvarme_Mad__og_drikkekartoner\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Mad__og_drikkekartoner/status\",\"unique_id\":\"ha_affaldvarme_Mad__og_drikkekartoner\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"]},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Mad__og_drikkekartoner/status\",\"name\":\"Mad- og drikkekartoner\",\"object_id\":\"ha_affaldvarme_Mad__og_drikkekartoner\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Mad__og_drikkekartoner/status\",\"unique_id\":\"ha_affaldvarme_Mad__og_drikkekartoner\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Madaffald/config - payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Madaffald/status\",\"name\":\"Madaffald\",\"object_id\":\"ha_affaldvarme_Madaffald\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Madaffald/status\",\"unique_id\":\"ha_affaldvarme_Madaffald\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"]},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Madaffald/status\",\"name\":\"Madaffald\",\"object_id\":\"ha_affaldvarme_Madaffald\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Madaffald/status\",\"unique_id\":\"ha_affaldvarme_Madaffald\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Metal/config - payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Metal/status\",\"name\":\"Metal\",\"object_id\":\"ha_affaldvarme_Metal\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Metal/status\",\"unique_id\":\"ha_affaldvarme_Metal\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"]},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Metal/status\",\"name\":\"Metal\",\"object_id\":\"ha_affaldvarme_Metal\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Metal/status\",\"unique_id\":\"ha_affaldvarme_Metal\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Pap/config - payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Pap/status\",\"name\":\"Pap\",\"object_id\":\"ha_affaldvarme_Pap\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Pap/status\",\"unique_id\":\"ha_affaldvarme_Pap\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"]},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Pap/status\",\"name\":\"Pap\",\"object_id\":\"ha_affaldvarme_Pap\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Pap/status\",\"unique_id\":\"ha_affaldvarme_Pap\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Papir/config - payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Papir/status\",\"name\":\"Papir\",\"object_id\":\"ha_affaldvarme_Papir\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Papir/status\",\"unique_id\":\"ha_affaldvarme_Papir\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"]},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Papir/status\",\"name\":\"Papir\",\"object_id\":\"ha_affaldvarme_Papir\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Papir/status\",\"unique_id\":\"ha_affaldvarme_Papir\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Plast/config - payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Plast/status\",\"name\":\"Plast\",\"object_id\":\"ha_affaldvarme_Plast\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Plast/status\",\"unique_id\":\"ha_affaldvarme_Plast\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"]},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Plast/status\",\"name\":\"Plast\",\"object_id\":\"ha_affaldvarme_Plast\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Plast/status\",\"unique_id\":\"ha_affaldvarme_Plast\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Restaffald/config - payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Restaffald/status\",\"name\":\"Restaffald\",\"object_id\":\"ha_affaldvarme_Restaffald\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Restaffald/status\",\"unique_id\":\"ha_affaldvarme_Restaffald\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"]},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Restaffald/status\",\"name\":\"Restaffald\",\"object_id\":\"ha_affaldvarme_Restaffald\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Restaffald/status\",\"unique_id\":\"ha_affaldvarme_Restaffald\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" - topic: homeassistant/sensor/ha_affaldvarme_Tekstiler/config - payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your Garbage Bin Manufacturer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Tekstiler/status\",\"name\":\"Tekstiler\",\"object_id\":\"ha_affaldvarme_Tekstiler\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Tekstiler/status\",\"unique_id\":\"ha_affaldvarme_Tekstiler\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"]},\"icon\":\"mdi:recycle\",\"json_attributes_topic\":\"garbage_bin/Tekstiler/status\",\"name\":\"Tekstiler\",\"object_id\":\"ha_affaldvarme_Tekstiler\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/Tekstiler/status\",\"unique_id\":\"ha_affaldvarme_Tekstiler\",\"unit_of_measurement\":\"days\",\"value_template\":\"{{ (strptime(value_json.next_empty, '%Y-%m-%d').date() - now().date()).days }}\"}" +- topic: homeassistant/sensor/ha_affaldvarme_device/config + payload: "{\"availability_topic\":\"garbage_bin/availability\",\"device\":{\"identifiers\":[\"ha_affaldvarme\"],\"manufacturer\":\"Your humble rust developer\",\"model\":\"Standard\",\"name\":\"Affaldvarme integration\",\"sw_version\":\"1.0\"},\"name\":\"Affaldvarme Device\",\"payload_available\":\"online\",\"payload_not_available\":\"offline\",\"state_topic\":\"garbage_bin/availability\",\"unique_id\":\"ha_affaldvarme_device\"}"