diff options
| author | diogo464 <[email protected]> | 2025-12-05 19:29:52 +0000 |
|---|---|---|
| committer | diogo464 <[email protected]> | 2025-12-05 19:29:52 +0000 |
| commit | 3a3d635adddf5cb16a93827e688e061613a083d7 (patch) | |
| tree | e6be0a075f80d5cfb11b1882e0086727bfe699e9 /src | |
| parent | b609a315e7921dcc712da6955890f4dc7c2c4b9f (diff) | |
reworked logging
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 62 | ||||
| -rw-r--r-- | src/log.rs | 115 |
2 files changed, 152 insertions, 25 deletions
| @@ -2,7 +2,6 @@ | |||
| 2 | 2 | ||
| 3 | use core::{cell::RefCell, task::Waker}; | 3 | use core::{cell::RefCell, task::Waker}; |
| 4 | 4 | ||
| 5 | use defmt::Format; | ||
| 6 | use embassy_sync::waitqueue::AtomicWaker; | 5 | use embassy_sync::waitqueue::AtomicWaker; |
| 7 | use heapless::{ | 6 | use heapless::{ |
| 8 | Vec, VecView, | 7 | Vec, VecView, |
| @@ -10,6 +9,9 @@ use heapless::{ | |||
| 10 | }; | 9 | }; |
| 11 | use serde::Serialize; | 10 | use serde::Serialize; |
| 12 | 11 | ||
| 12 | pub mod log; | ||
| 13 | pub use log::Format; | ||
| 14 | |||
| 13 | pub mod constants; | 15 | pub mod constants; |
| 14 | 16 | ||
| 15 | mod binary_state; | 17 | mod binary_state; |
| @@ -59,7 +61,8 @@ impl Error { | |||
| 59 | } | 61 | } |
| 60 | } | 62 | } |
| 61 | 63 | ||
| 62 | #[derive(Debug, Format, Clone, Copy, Serialize)] | 64 | #[derive(Debug, Clone, Copy, Serialize)] |
| 65 | #[cfg_attr(feature = "defmt", derive(Format))] | ||
| 63 | struct DeviceDiscovery<'a> { | 66 | struct DeviceDiscovery<'a> { |
| 64 | identifiers: &'a [&'a str], | 67 | identifiers: &'a [&'a str], |
| 65 | name: &'a str, | 68 | name: &'a str, |
| @@ -67,7 +70,8 @@ struct DeviceDiscovery<'a> { | |||
| 67 | model: &'a str, | 70 | model: &'a str, |
| 68 | } | 71 | } |
| 69 | 72 | ||
| 70 | #[derive(Debug, Format, Serialize)] | 73 | #[derive(Debug, Serialize)] |
| 74 | #[cfg_attr(feature = "defmt", derive(Format))] | ||
| 71 | struct EntityDiscovery<'a> { | 75 | struct EntityDiscovery<'a> { |
| 72 | #[serde(rename = "unique_id")] | 76 | #[serde(rename = "unique_id")] |
| 73 | id: &'a str, | 77 | id: &'a str, |
| @@ -490,11 +494,11 @@ impl<'a> Device<'a> { | |||
| 490 | pub async fn run<T: Transport>(&mut self, transport: &mut T) -> Result<(), Error> { | 494 | pub async fn run<T: Transport>(&mut self, transport: &mut T) -> Result<(), Error> { |
| 491 | let mut client = embedded_mqtt::Client::new(self.mqtt_resources, transport); | 495 | let mut client = embedded_mqtt::Client::new(self.mqtt_resources, transport); |
| 492 | if let Err(err) = client.connect(self.config.device_id).await { | 496 | if let Err(err) = client.connect(self.config.device_id).await { |
| 493 | defmt::error!("mqtt connect failed with: {:?}", defmt::Debug2Format(&err)); | 497 | crate::log::error!("mqtt connect failed with: {:?}", crate::log::Debug2Format(&err)); |
| 494 | return Err(Error::new("mqtt connection failed")); | 498 | return Err(Error::new("mqtt connection failed")); |
| 495 | } | 499 | } |
| 496 | 500 | ||
| 497 | defmt::debug!("sending discover messages"); | 501 | crate::log::debug!("sending discover messages"); |
| 498 | let device_discovery = DeviceDiscovery { | 502 | let device_discovery = DeviceDiscovery { |
| 499 | identifiers: &[self.config.device_id], | 503 | identifiers: &[self.config.device_id], |
| 500 | name: self.config.device_name, | 504 | name: self.config.device_name, |
| @@ -561,7 +565,7 @@ impl<'a> Device<'a> { | |||
| 561 | mode: entity_config.mode, | 565 | mode: entity_config.mode, |
| 562 | device: &device_discovery, | 566 | device: &device_discovery, |
| 563 | }; | 567 | }; |
| 564 | defmt::debug!("discovery for entity '{}': {}", entity_config.id, discovery); | 568 | crate::log::debug!("discovery for entity '{}': {:?}", entity_config.id, discovery); |
| 565 | 569 | ||
| 566 | self.discovery_buffer | 570 | self.discovery_buffer |
| 567 | .resize(self.discovery_buffer.capacity(), 0) | 571 | .resize(self.discovery_buffer.capacity(), 0) |
| @@ -572,25 +576,25 @@ impl<'a> Device<'a> { | |||
| 572 | } | 576 | } |
| 573 | 577 | ||
| 574 | let discovery_topic = self.discovery_topic_buffer.as_str(); | 578 | let discovery_topic = self.discovery_topic_buffer.as_str(); |
| 575 | defmt::debug!("sending discovery to topic '{}'", discovery_topic); | 579 | crate::log::debug!("sending discovery to topic '{}'", discovery_topic); |
| 576 | if let Err(err) = client | 580 | if let Err(err) = client |
| 577 | .publish(discovery_topic, &self.discovery_buffer) | 581 | .publish(discovery_topic, &self.discovery_buffer) |
| 578 | .await | 582 | .await |
| 579 | { | 583 | { |
| 580 | defmt::error!( | 584 | crate::log::error!( |
| 581 | "mqtt discovery publish failed with: {:?}", | 585 | "mqtt discovery publish failed with: {:?}", |
| 582 | defmt::Debug2Format(&err) | 586 | crate::log::Debug2Format(&err) |
| 583 | ); | 587 | ); |
| 584 | return Err(Error::new("mqtt discovery publish failed")); | 588 | return Err(Error::new("mqtt discovery publish failed")); |
| 585 | } | 589 | } |
| 586 | 590 | ||
| 587 | let command_topic = self.command_topic_buffer.as_str(); | 591 | let command_topic = self.command_topic_buffer.as_str(); |
| 588 | defmt::debug!("subscribing to command topic '{}'", command_topic); | 592 | crate::log::debug!("subscribing to command topic '{}'", command_topic); |
| 589 | if let Err(err) = client.subscribe(command_topic).await { | 593 | if let Err(err) = client.subscribe(command_topic).await { |
| 590 | defmt::error!( | 594 | crate::log::error!( |
| 591 | "mqtt subscribe to '{}' failed with: {:?}", | 595 | "mqtt subscribe to '{}' failed with: {:?}", |
| 592 | command_topic, | 596 | command_topic, |
| 593 | defmt::Debug2Format(&err) | 597 | crate::log::Debug2Format(&err) |
| 594 | ); | 598 | ); |
| 595 | return Err(Error::new( | 599 | return Err(Error::new( |
| 596 | "mqtt subscription to entity command topic failed", | 600 | "mqtt subscription to entity command topic failed", |
| @@ -641,7 +645,7 @@ impl<'a> Device<'a> { | |||
| 641 | }) => write!(self.publish_buffer, "{}", value) | 645 | }) => write!(self.publish_buffer, "{}", value) |
| 642 | .expect("publish buffer too small for number state payload"), | 646 | .expect("publish buffer too small for number state payload"), |
| 643 | _ => { | 647 | _ => { |
| 644 | defmt::warn!( | 648 | crate::log::warn!( |
| 645 | "entity '{}' requested state publish but its storage does not support it", | 649 | "entity '{}' requested state publish but its storage does not support it", |
| 646 | entity.config.id | 650 | entity.config.id |
| 647 | ); | 651 | ); |
| @@ -660,10 +664,10 @@ impl<'a> Device<'a> { | |||
| 660 | 664 | ||
| 661 | let state_topic = self.state_topic_buffer.as_str(); | 665 | let state_topic = self.state_topic_buffer.as_str(); |
| 662 | if let Err(err) = client.publish(state_topic, self.publish_buffer).await { | 666 | if let Err(err) = client.publish(state_topic, self.publish_buffer).await { |
| 663 | defmt::error!( | 667 | crate::log::error!( |
| 664 | "mqtt state publish on topic '{}' failed with: {:?}", | 668 | "mqtt state publish on topic '{}' failed with: {:?}", |
| 665 | state_topic, | 669 | state_topic, |
| 666 | defmt::Debug2Format(&err) | 670 | crate::log::Debug2Format(&err) |
| 667 | ); | 671 | ); |
| 668 | return Err(Error::new("mqtt publish failed")); | 672 | return Err(Error::new("mqtt publish failed")); |
| 669 | } | 673 | } |
| @@ -675,7 +679,7 @@ impl<'a> Device<'a> { | |||
| 675 | embassy_futures::select::Either::First(packet) => match packet { | 679 | embassy_futures::select::Either::First(packet) => match packet { |
| 676 | Ok(embedded_mqtt::Packet::Publish(publish)) => publish, | 680 | Ok(embedded_mqtt::Packet::Publish(publish)) => publish, |
| 677 | Err(err) => { | 681 | Err(err) => { |
| 678 | defmt::error!("mqtt receive failed with: {:?}", defmt::Debug2Format(&err)); | 682 | crate::log::error!("mqtt receive failed with: {:?}", crate::log::Debug2Format(&err)); |
| 679 | return Err(Error::new("mqtt receive failed")); | 683 | return Err(Error::new("mqtt receive failed")); |
| 680 | } | 684 | } |
| 681 | _ => continue, | 685 | _ => continue, |
| @@ -708,7 +712,7 @@ impl<'a> Device<'a> { | |||
| 708 | 712 | ||
| 709 | let mut read_buffer = [0u8; 128]; | 713 | let mut read_buffer = [0u8; 128]; |
| 710 | if publish.data_len > read_buffer.len() { | 714 | if publish.data_len > read_buffer.len() { |
| 711 | defmt::warn!( | 715 | crate::log::warn!( |
| 712 | "mqtt publish payload on topic {} is too large ({} bytes), ignoring it", | 716 | "mqtt publish payload on topic {} is too large ({} bytes), ignoring it", |
| 713 | publish.topic, | 717 | publish.topic, |
| 714 | publish.data_len | 718 | publish.data_len |
| @@ -716,7 +720,7 @@ impl<'a> Device<'a> { | |||
| 716 | continue; | 720 | continue; |
| 717 | } | 721 | } |
| 718 | 722 | ||
| 719 | defmt::debug!( | 723 | crate::log::debug!( |
| 720 | "mqtt receiving {} bytes of data on topic {}", | 724 | "mqtt receiving {} bytes of data on topic {}", |
| 721 | publish.data_len, | 725 | publish.data_len, |
| 722 | publish.topic | 726 | publish.topic |
| @@ -724,9 +728,9 @@ impl<'a> Device<'a> { | |||
| 724 | 728 | ||
| 725 | let data_len = publish.data_len; | 729 | let data_len = publish.data_len; |
| 726 | if let Err(err) = client.receive_data(&mut read_buffer[..data_len]).await { | 730 | if let Err(err) = client.receive_data(&mut read_buffer[..data_len]).await { |
| 727 | defmt::error!( | 731 | crate::log::error!( |
| 728 | "mqtt receive data failed with: {:?}", | 732 | "mqtt receive data failed with: {:?}", |
| 729 | defmt::Debug2Format(&err) | 733 | crate::log::Debug2Format(&err) |
| 730 | ); | 734 | ); |
| 731 | return Err(Error::new("mqtt receive data failed")); | 735 | return Err(Error::new("mqtt receive data failed")); |
| 732 | } | 736 | } |
| @@ -734,7 +738,7 @@ impl<'a> Device<'a> { | |||
| 734 | let command = match str::from_utf8(&read_buffer[..data_len]) { | 738 | let command = match str::from_utf8(&read_buffer[..data_len]) { |
| 735 | Ok(command) => command, | 739 | Ok(command) => command, |
| 736 | Err(_) => { | 740 | Err(_) => { |
| 737 | defmt::warn!("mqtt message contained invalid utf-8, ignoring it"); | 741 | crate::log::warn!("mqtt message contained invalid utf-8, ignoring it"); |
| 738 | continue; | 742 | continue; |
| 739 | } | 743 | } |
| 740 | }; | 744 | }; |
| @@ -745,7 +749,7 @@ impl<'a> Device<'a> { | |||
| 745 | match &mut data.storage { | 749 | match &mut data.storage { |
| 746 | EntityStorage::Button(button_storage) => { | 750 | EntityStorage::Button(button_storage) => { |
| 747 | if command != constants::HA_BUTTON_PAYLOAD_PRESS { | 751 | if command != constants::HA_BUTTON_PAYLOAD_PRESS { |
| 748 | defmt::warn!( | 752 | crate::log::warn!( |
| 749 | "button '{}' received unexpected command '{}', expected '{}', ignoring it", | 753 | "button '{}' received unexpected command '{}', expected '{}', ignoring it", |
| 750 | data.config.id, | 754 | data.config.id, |
| 751 | command, | 755 | command, |
| @@ -760,7 +764,7 @@ impl<'a> Device<'a> { | |||
| 760 | let command = match command.parse::<BinaryState>() { | 764 | let command = match command.parse::<BinaryState>() { |
| 761 | Ok(command) => command, | 765 | Ok(command) => command, |
| 762 | Err(_) => { | 766 | Err(_) => { |
| 763 | defmt::warn!( | 767 | crate::log::warn!( |
| 764 | "switch '{}' received invalid command '{}', expected 'ON' or 'OFF', ignoring it", | 768 | "switch '{}' received invalid command '{}', expected 'ON' or 'OFF', ignoring it", |
| 765 | data.config.id, | 769 | data.config.id, |
| 766 | command | 770 | command |
| @@ -768,16 +772,24 @@ impl<'a> Device<'a> { | |||
| 768 | continue; | 772 | continue; |
| 769 | } | 773 | } |
| 770 | }; | 774 | }; |
| 775 | let timestamp = embassy_time::Instant::now(); | ||
| 776 | if switch_storage.publish_on_command { | ||
| 777 | data.publish = true; | ||
| 778 | switch_storage.state = Some(SwitchState { | ||
| 779 | value: command, | ||
| 780 | timestamp, | ||
| 781 | }); | ||
| 782 | } | ||
| 771 | switch_storage.command = Some(SwitchCommand { | 783 | switch_storage.command = Some(SwitchCommand { |
| 772 | value: command, | 784 | value: command, |
| 773 | timestamp: embassy_time::Instant::now(), | 785 | timestamp, |
| 774 | }); | 786 | }); |
| 775 | } | 787 | } |
| 776 | EntityStorage::Number(number_storage) => { | 788 | EntityStorage::Number(number_storage) => { |
| 777 | let command = match command.parse::<f32>() { | 789 | let command = match command.parse::<f32>() { |
| 778 | Ok(command) => command, | 790 | Ok(command) => command, |
| 779 | Err(_) => { | 791 | Err(_) => { |
| 780 | defmt::warn!( | 792 | crate::log::warn!( |
| 781 | "number '{}' received invalid command '{}', expected a valid number, ignoring it", | 793 | "number '{}' received invalid command '{}', expected a valid number, ignoring it", |
| 782 | data.config.id, | 794 | data.config.id, |
| 783 | command | 795 | command |
diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..1f7bebd --- /dev/null +++ b/src/log.rs | |||
| @@ -0,0 +1,115 @@ | |||
| 1 | //! Logging abstraction that works with both defmt and tracing. | ||
| 2 | //! | ||
| 3 | //! This module provides logging macros that can use either `defmt` (for embedded targets) | ||
| 4 | //! or `tracing` (for desktop/testing) depending on the enabled cargo features. | ||
| 5 | //! | ||
| 6 | //! ## Features | ||
| 7 | //! | ||
| 8 | //! - `defmt`: Use defmt for logging | ||
| 9 | //! - `tracing`: Use tracing for logging | ||
| 10 | //! - Neither: Logging is compiled out (no-op) | ||
| 11 | //! | ||
| 12 | //! ## Usage | ||
| 13 | //! | ||
| 14 | //! ```rust,ignore | ||
| 15 | //! use crate::log::{trace, debug, info, warn, error}; | ||
| 16 | //! | ||
| 17 | //! info!("Application started"); | ||
| 18 | //! debug!("Value: {}", 42); | ||
| 19 | //! warn!("Something unexpected: {:?}", some_value); | ||
| 20 | //! ``` | ||
| 21 | |||
| 22 | // Re-export Format trait when using defmt | ||
| 23 | #[cfg(feature = "defmt")] | ||
| 24 | pub use defmt::Format; | ||
| 25 | |||
| 26 | // For tracing or no logging, we provide a stub Format trait | ||
| 27 | #[cfg(not(feature = "defmt"))] | ||
| 28 | pub trait Format {} | ||
| 29 | |||
| 30 | // When using defmt, also provide Debug2Format for std types | ||
| 31 | #[cfg(feature = "defmt")] | ||
| 32 | pub use defmt::Debug2Format; | ||
| 33 | |||
| 34 | // For tracing or no logging, Debug2Format is a passthrough | ||
| 35 | #[cfg(not(feature = "defmt"))] | ||
| 36 | #[inline] | ||
| 37 | pub fn Debug2Format<T>(value: &T) -> &T { | ||
| 38 | value | ||
| 39 | } | ||
| 40 | |||
| 41 | // Logging macros that dispatch to the appropriate backend or no-op | ||
| 42 | // If both features are enabled, defmt takes precedence | ||
| 43 | |||
| 44 | #[macro_export] | ||
| 45 | macro_rules! trace { | ||
| 46 | ($($arg:tt)*) => { | ||
| 47 | #[cfg(feature = "defmt")] | ||
| 48 | defmt::trace!($($arg)*); | ||
| 49 | |||
| 50 | #[cfg(all(feature = "tracing", not(feature = "defmt")))] | ||
| 51 | tracing::trace!($($arg)*); | ||
| 52 | |||
| 53 | #[cfg(not(any(feature = "defmt", feature = "tracing")))] | ||
| 54 | { let _ = (); } // no-op | ||
| 55 | }; | ||
| 56 | } | ||
| 57 | |||
| 58 | #[macro_export] | ||
| 59 | macro_rules! debug { | ||
| 60 | ($($arg:tt)*) => { | ||
| 61 | #[cfg(feature = "defmt")] | ||
| 62 | defmt::debug!($($arg)*); | ||
| 63 | |||
| 64 | #[cfg(all(feature = "tracing", not(feature = "defmt")))] | ||
| 65 | tracing::debug!($($arg)*); | ||
| 66 | |||
| 67 | #[cfg(not(any(feature = "defmt", feature = "tracing")))] | ||
| 68 | { let _ = (); } // no-op | ||
| 69 | }; | ||
| 70 | } | ||
| 71 | |||
| 72 | #[macro_export] | ||
| 73 | macro_rules! info { | ||
| 74 | ($($arg:tt)*) => { | ||
| 75 | #[cfg(feature = "defmt")] | ||
| 76 | defmt::info!($($arg)*); | ||
| 77 | |||
| 78 | #[cfg(all(feature = "tracing", not(feature = "defmt")))] | ||
| 79 | tracing::info!($($arg)*); | ||
| 80 | |||
| 81 | #[cfg(not(any(feature = "defmt", feature = "tracing")))] | ||
| 82 | { let _ = (); } // no-op | ||
| 83 | }; | ||
| 84 | } | ||
| 85 | |||
| 86 | #[macro_export] | ||
| 87 | macro_rules! warn { | ||
| 88 | ($($arg:tt)*) => { | ||
| 89 | #[cfg(feature = "defmt")] | ||
| 90 | defmt::warn!($($arg)*); | ||
| 91 | |||
| 92 | #[cfg(all(feature = "tracing", not(feature = "defmt")))] | ||
| 93 | tracing::warn!($($arg)*); | ||
| 94 | |||
| 95 | #[cfg(not(any(feature = "defmt", feature = "tracing")))] | ||
| 96 | { let _ = (); } // no-op | ||
| 97 | }; | ||
| 98 | } | ||
| 99 | |||
| 100 | #[macro_export] | ||
| 101 | macro_rules! error { | ||
| 102 | ($($arg:tt)*) => { | ||
| 103 | #[cfg(feature = "defmt")] | ||
| 104 | defmt::error!($($arg)*); | ||
| 105 | |||
| 106 | #[cfg(all(feature = "tracing", not(feature = "defmt")))] | ||
| 107 | tracing::error!($($arg)*); | ||
| 108 | |||
| 109 | #[cfg(not(any(feature = "defmt", feature = "tracing")))] | ||
| 110 | { let _ = (); } // no-op | ||
| 111 | }; | ||
| 112 | } | ||
| 113 | |||
| 114 | // Re-export the macros at the module level for easier use | ||
| 115 | pub use crate::{trace, debug, info, warn, error}; | ||
