esphome_native_api/
lib.rs1pub mod parser;
2mod proto;
3
4use log::debug;
5use parser::ProtoMessage;
6use std::{future::Future, pin::Pin, str};
7use tokio::io::{AsyncReadExt, AsyncWriteExt};
8use tokio::net::TcpListener;
9use tokio::sync::broadcast::Receiver;
10use tokio::sync::broadcast::Sender;
11use proto::{SensorStateClass, SensorLastResetType, EntityCategory};
12
13pub async fn start_server() -> Result<(), Box<dyn std::error::Error>> {
14 let addr = "0.0.0.0:6053".to_string();
15
16 let listener = TcpListener::bind(&addr).await?;
20 debug!("Listening on: {}", addr);
21
22 loop {
23 let (mut socket, _) = listener.accept().await?;
25
26 tokio::spawn(async move {
27 let mut buf = vec![0; 1024];
28
29 loop {
30 let n = socket
31 .read(&mut buf)
32 .await
33 .expect("failed to read data from socket");
34
35 if n == 0 {
36 return;
37 }
38
39 debug!("TCP: {:02X?}", &buf[0..n]);
40
41 let mut cursor = 0;
42
43 while cursor < n {
44 let len = buf[cursor + 1] as usize;
48 let message_type = buf[cursor + 2];
49 let packet_content = &buf[cursor + 3..cursor + 3 + len];
50
51 debug!("Message type: {}", message_type);
52 debug!("Message: {:?}", packet_content);
53
54 let message =
60 parser::parse_proto_message(message_type, packet_content).unwrap();
61
62 let mut answer_buf: Vec<u8> = vec![];
63 let mut disconnect: bool = false;
64 match message {
65 ProtoMessage::HelloRequest(hello_request) => {
66 println!(
67 "APIVersion: {}.{} from {}",
68 hello_request.api_version_major,
69 hello_request.api_version_minor,
70 hello_request.client_info
71 );
72 println!("HelloRequest: {:?}", hello_request);
73 let response_message = proto::HelloResponse {
74 api_version_major: 1,
75 api_version_minor: 10,
76 server_info: "Hello from Rust gRPC server".to_string(),
77 name: "Coool".to_string(),
78 };
79
80 answer_buf = [
81 answer_buf,
82 to_packet(ProtoMessage::HelloResponse(response_message)).unwrap(),
83 ]
84 .concat();
85 }
86 ProtoMessage::DeviceInfoRequest(device_info_request) => {
87 println!("DeviceInfoRequest: {:?}", device_info_request);
88 let response_message = proto::DeviceInfoResponse {
89 uses_password: false,
90 name: "Hello".to_owned(),
91 mac_address: "aa:bb:cc:dd:ee:ff".to_owned(),
92 esphome_version: "Hello".to_owned(),
93 compilation_time: "Hello".to_owned(),
94 model: "Hello".to_owned(),
95 has_deep_sleep: false,
96 project_name: "Hello".to_owned(),
97 project_version: "Hello".to_owned(),
98 webserver_port: 8080,
99 legacy_bluetooth_proxy_version: 1,
100 bluetooth_proxy_feature_flags: 0,
101 manufacturer: "Hello".to_owned(),
102 friendly_name: "Hello".to_owned(),
103 legacy_voice_assistant_version: 0,
104 voice_assistant_feature_flags: 0,
105 suggested_area: "Hello".to_owned(),
106 bluetooth_mac_address: "Hello".to_owned(),
107 };
108 answer_buf = [
109 answer_buf,
110 to_packet(ProtoMessage::DeviceInfoResponse(response_message))
111 .unwrap(),
112 ]
113 .concat();
114 }
115 ProtoMessage::ConnectRequest(connect_request) => {
116 println!("ConnectRequest: {:?}", connect_request);
117 let response_message = proto::ConnectResponse {
118 invalid_password: false,
119 };
120 answer_buf = [
121 answer_buf,
122 to_packet(ProtoMessage::ConnectResponse(response_message)).unwrap(),
123 ]
124 .concat();
125 }
126
127 ProtoMessage::DisconnectRequest(disconnect_request) => {
128 println!("DisconnectRequest: {:?}", disconnect_request);
129 let response_message = proto::DisconnectResponse {};
130 answer_buf = [
131 answer_buf,
132 to_packet(ProtoMessage::DisconnectResponse(response_message))
133 .unwrap(),
134 ]
135 .concat();
136 disconnect = true;
137 }
138 ProtoMessage::ListEntitiesRequest(list_entities_request) => {
139 println!("ListEntitiesRequest: {:?}", list_entities_request);
140
141 let sensor = proto::ListEntitiesSensorResponse {
142 object_id: "sensor_1".to_string(),
143 key: 1,
144 name: "Example Sensor".to_string(),
145 unique_id: "unique_sensor_1".to_string(),
146 icon: "mdi:thermometer".to_string(),
147 unit_of_measurement: "°C".to_string(),
148 accuracy_decimals: 2,
149 force_update: false,
150 device_class: "temperature".to_string(),
151 state_class: SensorStateClass::StateClassMeasurement as i32,
152 last_reset_type: SensorLastResetType::LastResetNone as i32,
153 disabled_by_default: false,
154 entity_category: EntityCategory::Config as i32,
155 };
156
157 let response_message = proto::ListEntitiesDoneResponse {};
158 answer_buf = [
159 answer_buf,
160 to_packet(ProtoMessage::ListEntitiesSensorResponse(sensor))
161 .unwrap(),
162 to_packet(ProtoMessage::ListEntitiesDoneResponse(response_message))
163 .unwrap(),
164 ]
165 .concat();
166 }
167 ProtoMessage::PingRequest(ping_request) => {
168 println!("PingRequest: {:?}", ping_request);
169 let response_message = proto::PingResponse {};
170 answer_buf = [
171 answer_buf,
172 to_packet(ProtoMessage::PingResponse(response_message)).unwrap(),
173 ]
174 .concat();
175 }
176 _ => {
177 println!("Ignore message type: {:?}", message);
178 return;
179 }
180 }
181
182 socket
183 .write_all(&answer_buf)
184 .await
185 .expect("failed to write data to socket");
186
187 if disconnect {
188 debug!("Disconnecting");
189 socket.shutdown().await.expect("failed to shutdown socket");
190 break;
191 }
192 cursor += 3 + len;
195 }
196 }
197 });
198 }
199}
200
201pub fn to_packet(obj: ProtoMessage) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
202 let response_content = parser::proto_to_vec(&obj)?;
203 let message_type = parser::message_to_num(&obj)?;
204 let zero: Vec<u8> = vec![0];
205 let length: Vec<u8> = vec![response_content.len().try_into().unwrap()];
206 let message_bit: Vec<u8> = vec![message_type];
207
208 let answer_buf: Vec<u8> = [zero, length, message_bit, response_content].concat();
209 Ok(answer_buf)
210}