Skip to main content

esphome_native_api/
esphomeserver.rs

1//! High-level ESPHome server implementation with entity management.
2//!
3//! This module provides the [`EspHomeServer`] abstraction, which simplifies working with
4//! ESPHome devices by managing entities. It builds on top of the
5//! lower-level [`crate::esphomeapi::EspHomeApi`] and handles entity registration and
6//! message routing automatically.
7//!
8//! # Examples
9//!
10//! ```rust,no_run
11//! use esphome_native_api::esphomeserver::{EspHomeServer, Entity, BinarySensor};
12//! use tokio::net::TcpStream;
13//!
14//! #[tokio::main]
15//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
16//!     let stream = TcpStream::connect("192.168.1.100:6053").await?;
17//!     
18//!     let mut server = EspHomeServer::builder()
19//!         .name("my-server".to_string())
20//!         .build();
21//!     
22//!     // Add entities
23//!     let sensor = Entity::BinarySensor(BinarySensor {
24//!         object_id: "door_sensor".to_string(),
25//!     });
26//!     server.add_entity("door_sensor", sensor);
27//!     
28//!     let (tx, mut rx) = server.start(stream).await?;
29//!     
30//!     Ok(())
31//! }
32//! ```
33
34#![allow(dead_code)]
35
36use log::debug;
37use log::error;
38use noise_protocol::CipherState;
39use noise_protocol::HandshakeState;
40use noise_rust_crypto::ChaCha20Poly1305;
41use noise_rust_crypto::Sha256;
42use noise_rust_crypto::X25519;
43use std::collections::HashMap;
44use std::str;
45use std::sync::Arc;
46use std::sync::atomic::AtomicBool;
47use tokio::net::TcpStream;
48use tokio::sync::Mutex;
49use tokio::sync::broadcast;
50use tokio::sync::mpsc;
51use typed_builder::TypedBuilder;
52
53use crate::esphomeapi::EspHomeApi;
54use crate::parser::ProtoMessage;
55use crate::proto::ListEntitiesDoneResponse;
56
57/// High-level ESPHome server implementation.
58///
59/// `EspHomeServer` provides an easier-to-use abstraction over the ESPHome native API
60/// by managing entity keys internally. It handles entity registration, message routing,
61/// and maintains state for all registered entities.
62///
63/// This struct uses the builder pattern via the [`TypedBuilder`] derive macro,
64/// allowing for flexible configuration.
65///
66/// # Examples
67///
68/// ```rust
69/// use esphome_native_api::esphomeserver::EspHomeServer;
70///
71/// let server = EspHomeServer::builder()
72///     .name("my-device".to_string())
73///     .api_version_major(1)
74///     .api_version_minor(10)
75///     .encryption_key("your-base64-key".to_string())
76///     .build();
77/// ```
78#[derive(TypedBuilder)]
79pub struct EspHomeServer {
80    // Private fields
81    #[builder(default=HashMap::new(), setter(skip))]
82    pub(crate) components_by_key: HashMap<u32, Entity>,
83    #[builder(default=HashMap::new(), setter(skip))]
84    pub(crate) components_key_id: HashMap<String, u32>,
85    #[builder(default = 0, setter(skip))]
86    pub(crate) current_key: u32,
87
88    #[builder(via_mutators, default=Arc::new(AtomicBool::new(false)))]
89    pub(crate) encrypted_api: Arc<AtomicBool>,
90
91    #[builder(via_mutators)]
92    pub(crate) noise_psk: Vec<u8>,
93
94    #[builder(default=Arc::new(Mutex::new(None)), setter(skip))]
95    pub(crate) handshake_state:
96        Arc<Mutex<Option<HandshakeState<X25519, ChaCha20Poly1305, Sha256>>>>,
97    #[builder(default=Arc::new(Mutex::new(None)), setter(skip))]
98    pub(crate) encrypt_cypher: Arc<Mutex<Option<CipherState<ChaCha20Poly1305>>>>,
99    #[builder(default=Arc::new(Mutex::new(None)), setter(skip))]
100    pub(crate) decrypt_cypher: Arc<Mutex<Option<CipherState<ChaCha20Poly1305>>>>,
101
102    name: String,
103
104    #[builder(default = None, setter(strip_option))]
105    #[deprecated(note = "https://esphome.io/components/api.html#configuration-variables")]
106    password: Option<String>,
107    #[builder(default = None, setter(strip_option))]
108    encryption_key: Option<String>,
109
110    #[builder(default = 1)]
111    api_version_major: u32,
112    #[builder(default = 10)]
113    api_version_minor: u32,
114    #[builder(default="Rust: esphome-native-api".to_string())]
115    server_info: String,
116
117    #[builder(default = None, setter(strip_option))]
118    friendly_name: Option<String>,
119
120    #[builder(default = None, setter(strip_option))]
121    mac: Option<String>,
122
123    #[builder(default = None, setter(strip_option))]
124    model: Option<String>,
125
126    #[builder(default = None, setter(strip_option))]
127    manufacturer: Option<String>,
128    #[builder(default = None, setter(strip_option))]
129    suggested_area: Option<String>,
130    #[builder(default = None, setter(strip_option))]
131    bluetooth_mac_address: Option<String>,
132}
133
134/// Easier version of the API abstraction.
135///
136/// Manages entity keys internally.
137impl EspHomeServer {
138    /// Starts the ESPHome server and begins communication over the provided TCP stream.
139    ///
140    /// This method initializes the underlying [`EspHomeApi`], establishes the connection,
141    /// and spawns a background task to handle message routing between the API and
142    /// registered entities.
143    ///
144    /// # Arguments
145    ///
146    /// * `tcp_stream` - An established TCP connection to an ESPHome device
147    ///
148    /// # Returns
149    ///
150    /// Returns a tuple containing:
151    /// - A sender for outgoing messages to the ESPHome device
152    /// - A receiver for incoming messages from the ESPHome device
153    ///
154    /// # Errors
155    ///
156    /// Returns an error if the connection cannot be established or if the initial
157    /// handshake fails.
158    ///
159    /// # Examples
160    ///
161    /// ```rust,no_run
162    /// # use esphome_native_api::esphomeserver::EspHomeServer;
163    /// # use tokio::net::TcpStream;
164    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
165    /// let stream = TcpStream::connect("192.168.1.100:6053").await?;
166    /// let mut server = EspHomeServer::builder().name("client".to_string()).build();
167    /// let (tx, mut rx) = server.start(stream).await?;
168    /// # Ok(())
169    /// # }
170    /// ```
171    pub async fn start(
172        &mut self,
173        tcp_stream: TcpStream,
174    ) -> Result<
175        (
176            mpsc::Sender<ProtoMessage>,
177            broadcast::Receiver<ProtoMessage>,
178        ),
179        Box<dyn std::error::Error>,
180    > {
181        let mut server = EspHomeApi::builder()
182            .api_version_major(self.api_version_major)
183            .api_version_minor(self.api_version_minor)
184            // .password(self.password.or_else())
185            .server_info(self.server_info.clone())
186            .name(self.name.clone())
187            // .friendly_name(self.friendly_name)
188            // .bluetooth_mac_address(self.bluetooth_mac_address)
189            // .mac(self.mac)
190            // .manufacturer(self.manufacturer)
191            // .model(self.model)
192            // .suggested_area(self.suggested_area)
193            .build();
194        let (messages_tx, mut messages_rx) = server.start(tcp_stream).await?;
195        let (outgoing_messages_tx, outgoing_messages_rx) = broadcast::channel::<ProtoMessage>(16);
196        let api_components_clone = self.components_by_key.clone();
197        // let messages_tx_clone = messages_tx.clone();
198
199        tokio::spawn(async move {
200            loop {
201                messages_rx.recv().await.map_or_else(
202                    |e| {
203                        error!("Error receiving message: {:?}", e);
204                        // Handle the error, maybe log it or break the loop
205                    },
206                    |message| {
207                        // Process the received message
208                        debug!("Received message: {:?}", message);
209
210                        match message {
211                            ProtoMessage::ListEntitiesRequest(list_entities_request) => {
212                                debug!("ListEntitiesRequest: {:?}", list_entities_request);
213
214                                for _sensor in api_components_clone.values() {
215                                    // TODO: Handle the different entity types
216                                    // outgoing_messages_tx.send(sensor.clone()).unwrap();
217                                }
218                                outgoing_messages_tx
219                                    .send(ProtoMessage::ListEntitiesDoneResponse(
220                                        ListEntitiesDoneResponse {},
221                                    ))
222                                    .unwrap();
223                            }
224                            other_message => {
225                                // Forward the message to the outgoing channel
226                                if let Err(e) = outgoing_messages_tx.send(other_message) {
227                                    error!("Error sending message to outgoing channel: {:?}", e);
228                                }
229                            }
230                        }
231                    },
232                );
233            }
234        });
235
236        Ok((messages_tx.clone(), outgoing_messages_rx))
237    }
238
239    /// Adds an entity to the server's internal registry.
240    ///
241    /// Each entity is assigned a unique key that is managed internally. The entity
242    /// can be referenced by its string identifier in subsequent operations.
243    ///
244    /// # Arguments
245    ///
246    /// * `entity_id` - A unique string identifier for the entity
247    /// * `entity` - The entity to register
248    ///
249    /// # Examples
250    ///
251    /// ```rust
252    /// # use esphome_native_api::esphomeserver::{EspHomeServer, Entity, BinarySensor};
253    /// let mut server = EspHomeServer::builder().name("server".to_string()).build();
254    /// let sensor = Entity::BinarySensor(BinarySensor {
255    ///     object_id: "motion_sensor".to_string(),
256    /// });
257    /// server.add_entity("motion", sensor);
258    /// ```
259    pub fn add_entity(&mut self, entity_id: &str, entity: Entity) {
260        self.components_key_id
261            .insert(entity_id.to_string(), self.current_key);
262        self.components_by_key.insert(self.current_key, entity);
263
264        self.current_key += 1;
265    }
266}
267
268/// Represents different types of entities supported by ESPHome.
269///
270/// This enum contains all entity types that can be registered with the server.
271/// Currently, only binary sensors are implemented, but this will expand to include
272/// other entity types like switches, lights, sensors, etc.
273#[derive(Clone, Debug)]
274pub enum Entity {
275    /// A binary sensor entity (on/off state)
276    BinarySensor(BinarySensor),
277}
278
279/// Represents a binary sensor entity.
280///
281/// Binary sensors report a simple on/off or true/false state, such as
282/// door/window sensors, motion detectors, or binary switches.
283#[derive(Clone, Debug)]
284pub struct BinarySensor {
285    /// The unique object identifier for this binary sensor
286    pub object_id: String,
287}