Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/homeassistant/button.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! Tools for publishing a [Home Assistant button](https://www.home-assistant.io/integrations/button.mqtt/).
use core::ops::Deref;

use serde::Serialize;

use crate::{homeassistant::Component, Error, Topic};

/// The type of button.
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
#[allow(missing_docs)]
pub enum ButtonClass {
Identify,
Restart,
Update,
}

/// A button that can be pressed.
#[derive(Serialize)]
pub struct Button {
/// The type of button.
pub device_class: Option<ButtonClass>,
}

impl Component for Button {
type State = ();

fn platform() -> &'static str {
"button"
}

async fn publish_state<T: Deref<Target = str>>(
&self,
_topic: &Topic<T>,
_state: Self::State,
) -> Result<(), Error> {
// Buttons don't have a state
Err(Error::Invalid)
}
}
6 changes: 1 addition & 5 deletions src/homeassistant/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,6 @@ impl Serialize for LightState<'_> {

/// A light entity
pub struct Light<'a, const C: usize, const E: usize> {
/// A command topic that Home Assistant can use to control the light.
/// It will be sent a [`LightState`] payload.
pub command_topic: Option<Topic<&'a str>>,
/// The color modes supported by the light.
pub supported_color_modes: [SupportedColorMode; C],
/// Any effects that can be used.
Expand All @@ -338,7 +335,7 @@ impl<const C: usize, const E: usize> Serialize for Light<'_, C, E> {
where
S: Serializer,
{
let mut len = 3;
let mut len = 2;

if C > 0 {
len += 1;
Expand All @@ -350,7 +347,6 @@ impl<const C: usize, const E: usize> Serialize for Light<'_, C, E> {

let mut serializer = serializer.serialize_struct("Light", len)?;

serializer.serialize_field("cmd_t", &self.command_topic)?;
serializer.serialize_field("schema", "json")?;

if C > 0 {
Expand Down
18 changes: 15 additions & 3 deletions src/homeassistant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
//! unique_id: Some("motion"),
//! name: "Motion",
//! availability: AvailabilityTopics::All([DEVICE_AVAILABILITY_TOPIC]),
//! state_topic: MOTION_STATE_TOPIC,
//! state_topic: Some(MOTION_STATE_TOPIC),
//! command_topic: None,
//! component: BinarySensor {
//! device_class: Some(BinarySensorClass::Motion),
//! },
Expand Down Expand Up @@ -49,6 +50,7 @@ use crate::{
};

pub mod binary_sensor;
pub mod button;
pub mod light;
pub mod sensor;
mod ser;
Expand Down Expand Up @@ -206,7 +208,9 @@ pub struct Entity<'a, const A: usize, C: Component> {
/// determine this entity's availability.
pub availability: AvailabilityTopics<'a, A>,
/// The state topic that this entity's state is published to.
pub state_topic: Topic<&'a str>,
pub state_topic: Option<Topic<&'a str>>,
/// The command topic that this entity receives commands from.
pub command_topic: Option<Topic<&'a str>>,
/// The specific entity.
pub component: C,
}
Expand All @@ -233,8 +237,16 @@ impl<const A: usize, C: Component> Entity<'_, A, C> {
}

/// Publishes this entity's state to the broker.
///
/// # Errors
///
/// - [`Error::Invalid`] if the entity doesn't have a state topic.
pub async fn publish_state(&self, state: C::State) -> Result<(), Error> {
self.component.publish_state(&self.state_topic, state).await
if let Some(topic) = self.state_topic {
self.component.publish_state(&topic, state).await
} else {
Err(Error::Invalid)
}
}
}

Expand Down
21 changes: 19 additions & 2 deletions src/homeassistant/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,13 @@ impl<const A: usize, C: Component, S: Serializer> Serializer for DiscoverySerial
name: &'static str,
mut len: usize,
) -> Result<Self::SerializeStruct, Self::Error> {
len += 6;
len += 5;
if self.discovery.state_topic.is_some() {
len += 1;
}
if self.discovery.command_topic.is_some() {
len += 1;
}
if self.discovery.unique_id.is_some() {
len += 1;
}
Expand All @@ -104,7 +110,18 @@ impl<const A: usize, C: Component, S: Serializer> Serializer for DiscoverySerial
serializer.serialize_field("obj_id", self.discovery.object_id)?;

serializer.serialize_field("name", self.discovery.name)?;
serializer.serialize_field("stat_t", &self.discovery.state_topic)?;

if let Some(t) = self.discovery.state_topic {
serializer.serialize_field("stat_t", &t)?;
} else {
serializer.skip_field("stat_t")?;
}

if let Some(t) = self.discovery.command_topic {
serializer.serialize_field("cmd_t", &t)?;
} else {
serializer.skip_field("cmd_t")?;
}

match &self.discovery.availability {
AvailabilityTopics::None => {
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ pub enum Error {
TooLarge,
/// A packet or payload could not be decoded or encoded.
PacketError,
/// An invalid or unsupported operation was attempted.
Invalid,
}

#[allow(clippy::large_enum_variant)]
Expand Down
Loading