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}