ESPHome 2026.2.3
Loading...
Searching...
No Matches
sim800l.cpp
Go to the documentation of this file.
1#include "sim800l.h"
3#include "esphome/core/log.h"
4#include <cstring>
5
6namespace esphome {
7namespace sim800l {
8
9static const char *const TAG = "sim800l";
10
11const char ASCII_CR = 0x0D;
12const char ASCII_LF = 0x0A;
13
15 if (this->watch_dog_++ == 2) {
16 this->state_ = STATE_INIT;
17 this->write(26);
18 }
19
20 if (this->expect_ack_)
21 return;
22
23 if (state_ == STATE_INIT) {
24 if (this->registered_ && this->send_pending_) {
25 this->send_cmd_("AT+CSCS=\"GSM\"");
27 } else if (this->registered_ && this->dial_pending_) {
28 this->send_cmd_("AT+CSCS=\"GSM\"");
29 this->state_ = STATE_DIALING1;
30 } else if (this->registered_ && this->connect_pending_) {
31 this->connect_pending_ = false;
32 ESP_LOGI(TAG, "Connecting");
33 this->send_cmd_("ATA");
34 this->state_ = STATE_ATA_SENT;
35 } else if (this->registered_ && this->send_ussd_pending_) {
36 this->send_cmd_("AT+CSCS=\"GSM\"");
38 } else if (this->registered_ && this->disconnect_pending_) {
39 this->disconnect_pending_ = false;
40 ESP_LOGI(TAG, "Disconnecting");
41 this->send_cmd_("ATH");
42 } else if (this->registered_ && this->call_state_ != 6) {
43 send_cmd_("AT+CLCC");
45 return;
46 } else {
47 this->send_cmd_("AT");
49 }
50 this->expect_ack_ = true;
51 } else if (state_ == STATE_RECEIVED_SMS) {
52 // Serial Buffer should have flushed.
53 // Send cmd to delete received sms
54 char delete_cmd[20]; // "AT+CMGD=" (8) + uint8_t (max 3) + null = 12 <= 20
55 buf_append_printf(delete_cmd, sizeof(delete_cmd), 0, "AT+CMGD=%d", this->parse_index_);
56 this->send_cmd_(delete_cmd);
57 this->state_ = STATE_CHECK_SMS;
58 this->expect_ack_ = true;
59 }
60}
61
62void Sim800LComponent::send_cmd_(const std::string &message) {
63 ESP_LOGV(TAG, "S: %s - %d", message.c_str(), this->state_);
64 this->watch_dog_ = 0;
65 this->write_str(message.c_str());
66 this->write_byte(ASCII_CR);
67 this->write_byte(ASCII_LF);
68}
69
71 if (message.empty())
72 return;
73
74 ESP_LOGV(TAG, "R: %s - %d", message.c_str(), this->state_);
75
76 if (this->state_ != STATE_RECEIVE_SMS) {
77 if (message == "RING") {
78 // Incoming call...
80 this->expect_ack_ = false;
81 } else if (message == "NO CARRIER") {
82 if (this->call_state_ != 6) {
83 this->call_state_ = 6;
84 this->call_disconnected_callback_.call();
85 }
86 }
87 }
88
89 bool ok = message == "OK";
90 if (this->expect_ack_) {
91 this->expect_ack_ = false;
92 if (!ok) {
93 if (this->state_ == STATE_SETUP_CMGF && message == "AT") {
94 // Expected ack but AT echo received
96 this->expect_ack_ = true;
97 } else {
98 ESP_LOGW(TAG, "Not ack. %d %s", this->state_, message.c_str());
99 this->state_ = STATE_IDLE; // Let it timeout
100 return;
101 }
102 }
103 } else if (ok && (this->state_ != STATE_PARSE_SMS_RESPONSE && this->state_ != STATE_CHECK_CALL &&
104 this->state_ != STATE_RECEIVE_SMS && this->state_ != STATE_DIALING2)) {
105 ESP_LOGW(TAG, "Received unexpected OK. Ignoring");
106 return;
107 }
108
109 switch (this->state_) {
110 case STATE_INIT: {
111 // While we were waiting for update to check for messages, this notifies a message
112 // is available.
113 bool message_available = message.compare(0, 6, "+CMTI:") == 0;
114 if (!message_available) {
115 if (message == "RING") {
116 // Incoming call...
117 this->state_ = STATE_PARSE_CLIP;
118 } else if (message == "NO CARRIER") {
119 if (this->call_state_ != 6) {
120 this->call_state_ = 6;
121 this->call_disconnected_callback_.call();
122 }
123 } else if (message.compare(0, 6, "+CUSD:") == 0) {
124 // Incoming USSD MESSAGE
125 this->state_ = STATE_CHECK_USSD;
126 }
127 break;
128 }
129
130 // Else fall thru ...
131 }
132 case STATE_CHECK_SMS:
133 send_cmd_("AT+CMGL=\"ALL\"");
135 this->parse_index_ = 0;
136 break;
138 send_cmd_("ATE0");
139 this->state_ = STATE_SETUP_CMGF;
140 this->expect_ack_ = true;
141 break;
142 case STATE_SETUP_CMGF:
143 send_cmd_("AT+CMGF=1");
144 this->state_ = STATE_SETUP_CLIP;
145 this->expect_ack_ = true;
146 break;
147 case STATE_SETUP_CLIP:
148 send_cmd_("AT+CLIP=1");
149 this->state_ = STATE_CREG;
150 this->expect_ack_ = true;
151 break;
152 case STATE_SETUP_USSD:
153 send_cmd_("AT+CUSD=1");
154 this->state_ = STATE_CREG;
155 this->expect_ack_ = true;
156 break;
157 case STATE_SEND_USSD1:
158 this->send_cmd_("AT+CUSD=1, \"" + this->ussd_ + "\"");
159 this->state_ = STATE_SEND_USSD2;
160 this->expect_ack_ = true;
161 break;
162 case STATE_SEND_USSD2:
163 ESP_LOGD(TAG, "SendUssd2: '%s'", message.c_str());
164 if (message == "OK") {
165 // Dialing
166 ESP_LOGD(TAG, "Dialing ussd code: '%s' done.", this->ussd_.c_str());
167 this->state_ = STATE_CHECK_USSD;
168 this->send_ussd_pending_ = false;
169 } else {
170 this->set_registered_(false);
171 this->state_ = STATE_INIT;
172 this->send_cmd_("AT+CMEE=2");
173 this->write(26);
174 }
175 break;
176 case STATE_CHECK_USSD:
177 ESP_LOGD(TAG, "Check ussd code: '%s'", message.c_str());
178 if (message.compare(0, 6, "+CUSD:") == 0) {
180 this->ussd_ = "";
181 size_t start = 10;
182 size_t end = message.find_last_of(',');
183 if (end > start) {
184 this->ussd_ = message.substr(start + 1, end - start - 2);
185 this->ussd_received_callback_.call(this->ussd_);
186 }
187 }
188 // Otherwise we receive another OK, we do nothing just wait polling to continuously check for SMS
189 if (message == "OK")
190 this->state_ = STATE_INIT;
191 break;
192 case STATE_CREG:
193 send_cmd_("AT+CREG?");
194 this->state_ = STATE_CREG_WAIT;
195 break;
196 case STATE_CREG_WAIT: {
197 // Response: "+CREG: 0,1" -- the one there means registered ok
198 // "+CREG: -,-" means not registered ok
199 bool registered = message.compare(0, 6, "+CREG:") == 0 && (message[9] == '1' || message[9] == '5');
200 if (registered) {
201 if (!this->registered_) {
202 ESP_LOGD(TAG, "Registered OK");
203 }
204 this->state_ = STATE_CSQ;
205 this->expect_ack_ = true;
206 } else {
207 ESP_LOGW(TAG, "Registration Fail");
208 if (message[7] == '0') { // Network registration is disable, enable it
209 send_cmd_("AT+CREG=1");
210 this->expect_ack_ = true;
211 this->state_ = STATE_SETUP_CMGF;
212 } else {
213 // Keep waiting registration
214 this->state_ = STATE_INIT;
215 }
216 }
217 set_registered_(registered);
218 break;
219 }
220 case STATE_CSQ:
221 send_cmd_("AT+CSQ");
223 break;
225 if (message.compare(0, 5, "+CSQ:") == 0) {
226 size_t comma = message.find(',', 6);
227 if (comma != 6) {
228 int rssi = parse_number<int>(message.substr(6, comma - 6)).value_or(0);
229
230#ifdef USE_SENSOR
231 if (this->rssi_sensor_ != nullptr) {
232 this->rssi_sensor_->publish_state(rssi);
233 } else {
234 ESP_LOGD(TAG, "RSSI: %d", rssi);
235 }
236#else
237 ESP_LOGD(TAG, "RSSI: %d", rssi);
238#endif
239 }
240 }
241 this->expect_ack_ = true;
242 this->state_ = STATE_CHECK_SMS;
243 break;
245 if (message.compare(0, 6, "+CMGL:") == 0 && this->parse_index_ == 0) {
246 size_t start = 7;
247 size_t end = message.find(',', start);
248 uint8_t item = 0;
249 while (end != start) {
250 item++;
251 if (item == 1) { // Slot Index
252 this->parse_index_ = parse_number<uint8_t>(message.substr(start, end - start)).value_or(0);
253 }
254 // item 2 = STATUS, usually "REC UNREAD"
255 if (item == 3) { // recipient
256 // Add 1 and remove 2 from substring to get rid of "quotes"
257 this->sender_ = message.substr(start + 1, end - start - 2);
258 this->message_.clear();
259 break;
260 }
261 // item 4 = ""
262 // item 5 = Received timestamp
263 start = end + 1;
264 end = message.find(',', start);
265 }
266
267 if (item < 2) {
268 ESP_LOGD(TAG, "Invalid message %d %s", this->state_, message.c_str());
269 return;
270 }
272 }
273 // Otherwise we receive another OK
274 if (ok) {
275 send_cmd_("AT+CLCC");
276 this->state_ = STATE_CHECK_CALL;
277 }
278 break;
279 case STATE_CHECK_CALL:
280 if (message.compare(0, 6, "+CLCC:") == 0 && this->parse_index_ == 0) {
281 this->expect_ack_ = true;
282 size_t start = 7;
283 size_t end = message.find(',', start);
284 uint8_t item = 0;
285 while (end != start) {
286 item++;
287 // item 1 call index for +CHLD
288 // item 2 dir 0 Mobile originated; 1 Mobile terminated
289 if (item == 3) { // stat
290 uint8_t current_call_state = parse_number<uint8_t>(message.substr(start, end - start)).value_or(6);
291 if (current_call_state != this->call_state_) {
292 if (current_call_state == 4) {
293 ESP_LOGV(TAG, "Premature call state '4'. Ignoring, waiting for RING");
294 } else {
295 this->call_state_ = current_call_state;
296 ESP_LOGD(TAG, "Call state is now: %d", current_call_state);
297 if (current_call_state == 0)
298 this->call_connected_callback_.call();
299 }
300 }
301 break;
302 }
303 // item 4 = ""
304 // item 5 = Received timestamp
305 start = end + 1;
306 end = message.find(',', start);
307 }
308
309 if (item < 2) {
310 ESP_LOGD(TAG, "Invalid message %d %s", this->state_, message.c_str());
311 return;
312 }
313 } else if (ok) {
314 if (this->call_state_ != 6) {
315 // no call in progress
316 this->call_state_ = 6; // Disconnect
317 this->call_disconnected_callback_.call();
318 }
319 }
320 this->state_ = STATE_INIT;
321 break;
323 /* Our recipient is set and the message body is in message
324 kick ESPHome callback now
325 */
326 if (ok || message.compare(0, 6, "+CMGL:") == 0) {
327 ESP_LOGD(TAG,
328 "Received SMS from: %s\n"
329 "%s",
330 this->sender_.c_str(), this->message_.c_str());
331 this->sms_received_callback_.call(this->message_, this->sender_);
333 } else {
334 if (!this->message_.empty())
335 this->message_ += "\n";
336 this->message_ += message;
337 }
338 break;
341 // Let the buffer flush. Next poll will request to delete the parsed index message.
342 break;
344 this->send_cmd_("AT+CMGS=\"" + this->recipient_ + "\"");
346 break;
348 if (message == ">") {
349 // Send sms body
350 ESP_LOGI(TAG, "Sending to %s message: '%s'", this->recipient_.c_str(), this->outgoing_message_.c_str());
351 this->write_str(this->outgoing_message_.c_str());
352 this->write(26);
354 } else {
355 set_registered_(false);
356 this->state_ = STATE_INIT;
357 this->send_cmd_("AT+CMEE=2");
358 this->write(26);
359 }
360 break;
362 if (message.compare(0, 6, "+CMGS:") == 0) {
363 ESP_LOGD(TAG, "SMS Sent OK: %s", message.c_str());
364 this->send_pending_ = false;
365 this->state_ = STATE_CHECK_SMS;
366 this->expect_ack_ = true;
367 }
368 break;
369 case STATE_DIALING1:
370 this->send_cmd_("ATD" + this->recipient_ + ';');
371 this->state_ = STATE_DIALING2;
372 break;
373 case STATE_DIALING2:
374 if (ok) {
375 ESP_LOGI(TAG, "Dialing: '%s'", this->recipient_.c_str());
376 this->dial_pending_ = false;
377 } else {
378 this->set_registered_(false);
379 this->send_cmd_("AT+CMEE=2");
380 this->write(26);
381 }
382 this->state_ = STATE_INIT;
383 break;
384 case STATE_PARSE_CLIP:
385 if (message.compare(0, 6, "+CLIP:") == 0) {
386 std::string caller_id;
387 size_t start = 7;
388 size_t end = message.find(',', start);
389 uint8_t item = 0;
390 while (end != start) {
391 item++;
392 if (item == 1) { // Slot Index
393 // Add 1 and remove 2 from substring to get rid of "quotes"
394 caller_id = message.substr(start + 1, end - start - 2);
395 break;
396 }
397 // item 4 = ""
398 // item 5 = Received timestamp
399 start = end + 1;
400 end = message.find(',', start);
401 }
402 if (this->call_state_ != 4) {
403 this->call_state_ = 4;
404 ESP_LOGI(TAG, "Incoming call from %s", caller_id.c_str());
405 incoming_call_callback_.call(caller_id);
406 }
407 this->state_ = STATE_INIT;
408 }
409 break;
410 case STATE_ATA_SENT:
411 ESP_LOGI(TAG, "Call connected");
412 if (this->call_state_ != 0) {
413 this->call_state_ = 0;
414 this->call_connected_callback_.call();
415 }
416 this->state_ = STATE_INIT;
417 break;
418 default:
419 ESP_LOGW(TAG, "Unhandled: %s - %d", message.c_str(), this->state_);
420 break;
421 }
422} // namespace sim800l
423
425 // Read message
426 while (this->available()) {
427 uint8_t byte;
428 this->read_byte(&byte);
429
431 this->read_pos_ = 0;
432
433 ESP_LOGVV(TAG, "Buffer pos: %u %d", this->read_pos_, byte); // NOLINT
434
435 if (byte == ASCII_CR)
436 continue;
437 if (byte >= 0x7F)
438 byte = '?'; // need to be valid utf8 string for log functions.
439 this->read_buffer_[this->read_pos_] = byte;
440
441 if (this->state_ == STATE_SENDING_SMS_2 && this->read_pos_ == 0 && byte == '>')
442 this->read_buffer_[++this->read_pos_] = ASCII_LF;
443
444 if (this->read_buffer_[this->read_pos_] == ASCII_LF) {
445 this->read_buffer_[this->read_pos_] = 0;
446 this->read_pos_ = 0;
447 this->parse_cmd_(this->read_buffer_);
448 } else {
449 this->read_pos_++;
450 }
451 }
452 if (state_ == STATE_INIT && this->registered_ &&
453 (this->call_state_ != 6 // A call is in progress
454 || this->send_pending_ || this->dial_pending_ || this->connect_pending_ || this->disconnect_pending_)) {
455 this->update();
456 }
457}
458
459void Sim800LComponent::send_sms(const std::string &recipient, const std::string &message) {
460 this->recipient_ = recipient;
462 this->send_pending_ = true;
463}
464
465void Sim800LComponent::send_ussd(const std::string &ussd_code) {
466 ESP_LOGD(TAG, "Sending USSD code: %s", ussd_code.c_str());
467 this->ussd_ = ussd_code;
468 this->send_ussd_pending_ = true;
469 this->update();
470}
472 ESP_LOGCONFIG(TAG, "SIM800L:");
473#ifdef USE_BINARY_SENSOR
474 LOG_BINARY_SENSOR(" ", "Registered", this->registered_binary_sensor_);
475#endif
476#ifdef USE_SENSOR
477 LOG_SENSOR(" ", "Rssi", this->rssi_sensor_);
478#endif
479}
480void Sim800LComponent::dial(const std::string &recipient) {
481 this->recipient_ = recipient;
482 this->dial_pending_ = true;
483}
486
488 this->registered_ = registered;
489#ifdef USE_BINARY_SENSOR
490 if (this->registered_binary_sensor_ != nullptr)
491 this->registered_binary_sensor_->publish_state(registered);
492#endif
493}
494
495} // namespace sim800l
496} // namespace esphome
void publish_state(bool new_state)
Publish a new state to the front-end.
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
sensor::Sensor * rssi_sensor_
Definition sim800l.h:95
char read_buffer_[SIM800L_READ_BUFFER_LENGTH]
Definition sim800l.h:99
CallbackManager< void(std::string)> ussd_received_callback_
Definition sim800l.h:121
void send_cmd_(const std::string &message)
Definition sim800l.cpp:62
CallbackManager< void(std::string, std::string)> sms_received_callback_
Definition sim800l.h:117
CallbackManager< void(std::string)> incoming_call_callback_
Definition sim800l.h:118
binary_sensor::BinarySensor * registered_binary_sensor_
Definition sim800l.h:91
CallbackManager< void()> call_connected_callback_
Definition sim800l.h:119
void set_registered_(bool registered)
Definition sim800l.cpp:487
void send_sms(const std::string &recipient, const std::string &message)
Definition sim800l.cpp:459
void update() override
Retrieve the latest sensor values. This operation takes approximately 16ms.
Definition sim800l.cpp:14
void parse_cmd_(std::string message)
Definition sim800l.cpp:70
CallbackManager< void()> call_disconnected_callback_
Definition sim800l.h:120
void dial(const std::string &recipient)
Definition sim800l.cpp:480
void send_ussd(const std::string &ussd_code)
Definition sim800l.cpp:465
void write_str(const char *str)
Definition uart.h:32
bool read_byte(uint8_t *data)
Definition uart.h:34
void write_byte(uint8_t data)
Definition uart.h:18
size_t write(uint8_t data)
Definition uart.h:57
const char * message
Definition component.cpp:38
@ STATE_PARSE_SMS_RESPONSE
Definition sim800l.h:34
const uint16_t SIM800L_READ_BUFFER_LENGTH
Definition sim800l.h:19
const char ASCII_LF
Definition sim800l.cpp:12
const char ASCII_CR
Definition sim800l.cpp:11
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
optional< T > parse_number(const char *str)
Parse an unsigned decimal number from a null-terminated string.
Definition helpers.h:785
uint8_t end[39]
Definition sun_gtil2.cpp:17