esphome_native_api/
lib.rs

1pub 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    // Next up we create a TCP listener which will listen for incoming
17    // connections. This TCP listener is bound to the address we determined
18    // above and must be associated with an event loop.
19    let listener = TcpListener::bind(&addr).await?;
20    debug!("Listening on: {}", addr);
21
22    loop {
23        // Asynchronously wait for an inbound socket.
24        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                    // Ignore first byte
45                    // Get Length of packet
46
47                    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                    // TODO: Parse Frames
55
56                    // How to decode [00, 1D, 01, 0A, 17, 48, 6F, 6D, 65, 20, 41, 73, 73, 69, 73, 74, 61, 6E, 74, 20, 32, 30, 32, 35, 2E, 33, 2E, 32, 10, 01, 18, 0A, 00, 00, 03]
57                    // let request_content = &buf[3..n];
58
59                    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                    // Close the socket
193
194                    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}