ESPHome 2026.11.0-dev
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::sim800l {
7
8ESPHOME_LOG_TAG(TAG, "sim800l");
9
10const char ASCII_CR = 0x0D;
11const char ASCII_LF = 0x0A;
12
14 if (this->watch_dog_++ == 2) {
15 this->state_ = STATE_INIT;
16 this->write(26);
17 // The module may have restarted, enable DTMF detection again
18 this->dtmf_ready_ = false;
19 }
20
21 if (this->expect_ack_)
22 return;
23
24 if (state_ == STATE_INIT) {
25 if (this->registered_ && this->send_pending_) {
26 this->send_cmd_("AT+CSCS=\"GSM\"");
28 } else if (this->registered_ && this->dial_pending_) {
29 this->send_cmd_("AT+CSCS=\"GSM\"");
30 this->state_ = STATE_DIALING1;
31 } else if (this->registered_ && this->connect_pending_) {
32 this->connect_pending_ = false;
33 ESP_LOGI(TAG, "Connecting");
34 this->send_cmd_("ATA");
35 this->state_ = STATE_ATA_SENT;
36 } else if (this->registered_ && this->send_ussd_pending_) {
37 this->send_cmd_("AT+CSCS=\"GSM\"");
39 } else if (this->registered_ && this->disconnect_pending_) {
40 this->disconnect_pending_ = false;
41 ESP_LOGI(TAG, "Disconnecting");
42 this->send_cmd_("ATH");
43 } else if (this->registered_ && this->call_state_ != 6) {
44 send_cmd_("AT+CLCC");
46 return;
47 } else {
48 this->send_cmd_("AT");
50 }
51 this->expect_ack_ = true;
52 } else if (state_ == STATE_RECEIVED_SMS) {
53 // Serial Buffer should have flushed.
54 // Send cmd to delete received sms
55 char delete_cmd[20]; // "AT+CMGD=" (8) + uint8_t (max 3) + null = 12 <= 20
56 buf_append_printf(delete_cmd, sizeof(delete_cmd), 0, "AT+CMGD=%d", this->parse_index_);
57 this->send_cmd_(delete_cmd);
58 this->state_ = STATE_CHECK_SMS;
59 this->expect_ack_ = true;
60 }
61}
62
63void Sim800LComponent::send_cmd_(const std::string &message) {
64 ESP_LOGV(TAG, "S: %s - %d", message.c_str(), this->state_);
65 this->watch_dog_ = 0;
66 this->write_str(message.c_str());
67 this->write_byte(ASCII_CR);
68 this->write_byte(ASCII_LF);
69}
70
72 if (message.empty())
73 return;
74
75 ESP_LOGV(TAG, "R: %s - %d", message.c_str(), this->state_);
76
77 if (this->state_ != STATE_RECEIVE_SMS) {
78 if (message == "RING") {
79 // Incoming call...
81 this->expect_ack_ = false;
82 } else if (message == "NO CARRIER") {
83 if (this->call_state_ != 6) {
84 this->call_state_ = 6;
85 this->call_disconnected_callback_.call();
86 }
87 } else if (message.starts_with("+DTMF:")) {
88 // Unsolicited DTMF key report: "+DTMF: <key>[,<duration>]"
89 size_t start = message.find_first_not_of(' ', 6);
90 if (start != std::string::npos) {
91 std::string key = message.substr(start, message.find(',', start) - start);
92 ESP_LOGD(TAG, "DTMF received: %s", key.c_str());
93 this->dtmf_received_callback_.call(key);
94 }
95 return;
96 }
97 }
98
99 bool ok = message == "OK";
100 if (this->expect_ack_) {
101 this->expect_ack_ = false;
102 if (!ok) {
103 if (this->state_ == STATE_SETUP_CMGF && message == "AT") {
104 // Expected ack but AT echo received
106 this->expect_ack_ = true;
107 } else if (this->state_ == STATE_DDET_SENT) {
108 if (message != "ERROR" && !message.starts_with("+CME ERROR:")) {
109 // Unrelated unsolicited line, keep waiting for the DDET response
110 this->expect_ack_ = true;
111 return;
112 }
113 // Module firmware without DTMF support, carry on with the setup sequence
114 ESP_LOGW(TAG, "DTMF detection not supported by the module: %s", message.c_str());
115 } else {
116 ESP_LOGW(TAG, "Not ack. %d %s", this->state_, message.c_str());
117 this->state_ = STATE_IDLE; // Let it timeout
118 return;
119 }
120 }
121 } else if (ok && (this->state_ != STATE_PARSE_SMS_RESPONSE && this->state_ != STATE_CHECK_CALL &&
122 this->state_ != STATE_RECEIVE_SMS && this->state_ != STATE_DIALING2)) {
123 ESP_LOGW(TAG, "Received unexpected OK. Ignoring");
124 return;
125 }
126
127 switch (this->state_) {
128 case STATE_INIT: {
129 // While we were waiting for update to check for messages, this notifies a message
130 // is available.
131 bool message_available = message.starts_with("+CMTI:");
132 if (!message_available) {
133 if (message == "RING") {
134 // Incoming call...
135 this->state_ = STATE_PARSE_CLIP;
136 } else if (message == "NO CARRIER") {
137 if (this->call_state_ != 6) {
138 this->call_state_ = 6;
139 this->call_disconnected_callback_.call();
140 }
141 } else if (message.starts_with("+CUSD:")) {
142 // Incoming USSD MESSAGE
143 this->state_ = STATE_CHECK_USSD;
144 }
145 break;
146 }
147
148 [[fallthrough]];
149 }
150 case STATE_CHECK_SMS:
151 send_cmd_("AT+CMGL=\"ALL\"");
153 this->parse_index_ = 0;
154 break;
156 send_cmd_("ATE0");
157 this->state_ = STATE_SETUP_CMGF;
158 this->expect_ack_ = true;
159 break;
160 case STATE_SETUP_CMGF:
161 send_cmd_("AT+CMGF=1");
162 this->state_ = STATE_SETUP_CLIP;
163 this->expect_ack_ = true;
164 break;
165 case STATE_SETUP_CLIP:
166 send_cmd_("AT+CLIP=1");
167 this->state_ = this->dtmf_enabled_ && !this->dtmf_ready_ ? STATE_SETUP_DDET : STATE_CREG;
168 this->expect_ack_ = true;
169 break;
170 case STATE_SETUP_DDET:
171 send_cmd_("AT+DDET=1");
172 this->state_ = STATE_DDET_SENT;
173 this->expect_ack_ = true;
174 break;
175 case STATE_SETUP_USSD:
176 send_cmd_("AT+CUSD=1");
177 this->state_ = STATE_CREG;
178 this->expect_ack_ = true;
179 break;
180 case STATE_SEND_USSD1:
181 this->send_cmd_("AT+CUSD=1, \"" + this->ussd_ + "\"");
182 this->state_ = STATE_SEND_USSD2;
183 this->expect_ack_ = true;
184 break;
185 case STATE_SEND_USSD2:
186 ESP_LOGD(TAG, "SendUssd2: '%s'", message.c_str());
187 if (message == "OK") {
188 // Dialing
189 ESP_LOGD(TAG, "Dialing ussd code: '%s' done.", this->ussd_.c_str());
190 this->state_ = STATE_CHECK_USSD;
191 this->send_ussd_pending_ = false;
192 } else {
193 this->set_registered_(false);
194 this->state_ = STATE_INIT;
195 this->send_cmd_("AT+CMEE=2");
196 this->write(26);
197 }
198 break;
199 case STATE_CHECK_USSD:
200 ESP_LOGD(TAG, "Check ussd code: '%s'", message.c_str());
201 if (message.starts_with("+CUSD:")) {
203 this->ussd_ = "";
204 size_t start = 10;
205 size_t end = message.find_last_of(',');
206 if (end > start) {
207 this->ussd_ = message.substr(start + 1, end - start - 2);
208 this->ussd_received_callback_.call(this->ussd_);
209 }
210 }
211 // Otherwise we receive another OK, we do nothing just wait polling to continuously check for SMS
212 if (message == "OK")
213 this->state_ = STATE_INIT;
214 break;
215 case STATE_DDET_SENT:
216 // Tried once per module reset; firmware without DDET only logs a warning, and a passing error is retried
217 // after the next reset
218 this->dtmf_ready_ = true;
219 [[fallthrough]];
220 case STATE_CREG:
221 send_cmd_("AT+CREG?");
222 this->state_ = STATE_CREG_WAIT;
223 break;
224 case STATE_CREG_WAIT: {
225 // Response: "+CREG: 0,1" -- the one there means registered ok
226 // "+CREG: -,-" means not registered ok
227 bool registered = message.size() > 9 && message.starts_with("+CREG:") && (message[9] == '1' || message[9] == '5');
228 if (registered) {
229 if (!this->registered_) {
230 ESP_LOGD(TAG, "Registered OK");
231 }
232 this->state_ = STATE_CSQ;
233 this->expect_ack_ = true;
234 } else {
235 ESP_LOGW(TAG, "Registration Fail");
236 if (message.size() > 7 && message[7] == '0') { // Network registration is disabled, enable it
237 send_cmd_("AT+CREG=1");
238 this->expect_ack_ = true;
239 this->state_ = STATE_SETUP_CMGF;
240 } else {
241 // Keep waiting registration
242 this->state_ = STATE_INIT;
243 }
244 }
245 set_registered_(registered);
246 break;
247 }
248 case STATE_CSQ:
249 send_cmd_("AT+CSQ");
251 break;
253 if (message.starts_with("+CSQ:")) {
254 size_t comma = message.find(',', 6);
255 if (comma != 6) {
256 int rssi = parse_number<int>(message.substr(6, comma - 6)).value_or(0);
257
258#ifdef USE_SENSOR
259 if (this->rssi_sensor_ != nullptr) {
260 this->rssi_sensor_->publish_state(rssi);
261 } else {
262 ESP_LOGD(TAG, "RSSI: %d", rssi);
263 }
264#else
265 ESP_LOGD(TAG, "RSSI: %d", rssi);
266#endif
267 }
268 }
269 this->expect_ack_ = true;
270 this->state_ = STATE_CHECK_SMS;
271 break;
273 if (message.starts_with("+CMGL:") && this->parse_index_ == 0) {
274 size_t start = 7;
275 size_t end = message.find(',', start);
276 uint8_t item = 0;
277 while (end != start) {
278 item++;
279 if (item == 1) { // Slot Index
280 this->parse_index_ = parse_number<uint8_t>(message.substr(start, end - start)).value_or(0);
281 }
282 // item 2 = STATUS, usually "REC UNREAD"
283 if (item == 3) { // recipient
284 // Add 1 and remove 2 from substring to get rid of "quotes"
285 this->sender_ = message.substr(start + 1, end - start - 2);
286 this->message_.clear();
287 break;
288 }
289 // item 4 = ""
290 // item 5 = Received timestamp
291 start = end + 1;
292 end = message.find(',', start);
293 }
294
295 if (item < 2) {
296 ESP_LOGD(TAG, "Invalid message %d %s", this->state_, message.c_str());
297 return;
298 }
300 }
301 // Otherwise we receive another OK
302 if (ok) {
303 send_cmd_("AT+CLCC");
304 this->state_ = STATE_CHECK_CALL;
305 }
306 break;
307 case STATE_CHECK_CALL:
308 if (message.starts_with("+CLCC:") && this->parse_index_ == 0) {
309 this->expect_ack_ = true;
310 size_t start = 7;
311 size_t end = message.find(',', start);
312 uint8_t item = 0;
313 while (end != start) {
314 item++;
315 // item 1 call index for +CHLD
316 // item 2 dir 0 Mobile originated; 1 Mobile terminated
317 if (item == 3) { // stat
318 uint8_t current_call_state = parse_number<uint8_t>(message.substr(start, end - start)).value_or(6);
319 if (current_call_state != this->call_state_) {
320 if (current_call_state == 4) {
321 ESP_LOGV(TAG, "Premature call state '4'. Ignoring, waiting for RING");
322 } else {
323 this->call_state_ = current_call_state;
324 ESP_LOGD(TAG, "Call state is now: %d", current_call_state);
325 if (current_call_state == 0)
326 this->call_connected_callback_.call();
327 }
328 }
329 break;
330 }
331 // item 4 = ""
332 // item 5 = Received timestamp
333 start = end + 1;
334 end = message.find(',', start);
335 }
336
337 if (item < 2) {
338 ESP_LOGD(TAG, "Invalid message %d %s", this->state_, message.c_str());
339 return;
340 }
341 } else if (ok) {
342 if (this->call_state_ != 6) {
343 // no call in progress
344 this->call_state_ = 6; // Disconnect
345 this->call_disconnected_callback_.call();
346 }
347 }
348 this->state_ = STATE_INIT;
349 break;
351 /* Our recipient is set and the message body is in message
352 kick ESPHome callback now
353 */
354 if (ok || message.starts_with("+CMGL:")) {
355 ESP_LOGD(TAG,
356 "Received SMS from: %s\n"
357 " %s",
358 this->sender_.c_str(), this->message_.c_str());
359 this->sms_received_callback_.call(this->message_, this->sender_);
361 } else {
362 if (!this->message_.empty())
363 this->message_ += '\n';
364 this->message_ += message;
365 }
366 break;
369 // Let the buffer flush. Next poll will request to delete the parsed index message.
370 break;
372 this->send_cmd_("AT+CMGS=\"" + this->recipient_ + "\"");
374 break;
376 if (message == ">") {
377 // Send sms body
378 ESP_LOGI(TAG, "Sending to %s message: '%s'", this->recipient_.c_str(), this->outgoing_message_.c_str());
379 this->write_str(this->outgoing_message_.c_str());
380 this->write(26);
382 } else {
383 set_registered_(false);
384 this->state_ = STATE_INIT;
385 this->send_cmd_("AT+CMEE=2");
386 this->write(26);
387 }
388 break;
390 if (message.starts_with("+CMGS:")) {
391 ESP_LOGD(TAG, "SMS Sent OK: %s", message.c_str());
392 this->send_pending_ = false;
393 this->state_ = STATE_CHECK_SMS;
394 this->expect_ack_ = true;
395 }
396 break;
397 case STATE_DIALING1:
398 this->send_cmd_("ATD" + this->recipient_ + ';');
399 this->state_ = STATE_DIALING2;
400 break;
401 case STATE_DIALING2:
402 if (ok) {
403 ESP_LOGI(TAG, "Dialing: '%s'", this->recipient_.c_str());
404 this->dial_pending_ = false;
405 } else {
406 this->set_registered_(false);
407 this->send_cmd_("AT+CMEE=2");
408 this->write(26);
409 }
410 this->state_ = STATE_INIT;
411 break;
412 case STATE_PARSE_CLIP:
413 if (message.starts_with("+CLIP:")) {
414 std::string caller_id;
415 size_t start = 7;
416 size_t end = message.find(',', start);
417 uint8_t item = 0;
418 while (end != start) {
419 item++;
420 if (item == 1) { // Slot Index
421 // Add 1 and remove 2 from substring to get rid of "quotes"
422 caller_id = message.substr(start + 1, end - start - 2);
423 break;
424 }
425 // item 4 = ""
426 // item 5 = Received timestamp
427 start = end + 1;
428 end = message.find(',', start);
429 }
430 if (this->call_state_ != 4) {
431 this->call_state_ = 4;
432 ESP_LOGI(TAG, "Incoming call from %s", caller_id.c_str());
433 incoming_call_callback_.call(caller_id);
434 }
435 this->state_ = STATE_INIT;
436 }
437 break;
438 case STATE_ATA_SENT:
439 ESP_LOGI(TAG, "Call connected");
440 if (this->call_state_ != 0) {
441 this->call_state_ = 0;
442 this->call_connected_callback_.call();
443 }
444 this->state_ = STATE_INIT;
445 break;
446 default:
447 ESP_LOGW(TAG, "Unhandled: %s - %d", message.c_str(), this->state_);
448 break;
449 }
450} // namespace sim800l
451
453 // Read message
454 this->read_available([this](uint8_t byte) {
455 ESP_LOGVV(TAG, "Buffer pos: %u %d", this->line_reader_.length(), byte); // NOLINT
456
457 if (byte >= 0x7F)
458 byte = '?'; // need to be valid utf8 string for log functions.
459 const char *line = this->line_reader_.feed(byte);
460 if (line == nullptr && this->state_ == STATE_SENDING_SMS_2 && byte == '>' && this->line_reader_.length() == 1) {
461 // The send prompt has no line end
462 line = this->line_reader_.partial();
463 this->line_reader_.reset();
464 }
465 if (line != nullptr) {
466 this->parse_cmd_(line);
467 } else {
469 }
470 });
471 if (state_ == STATE_INIT && this->registered_ &&
472 (this->call_state_ != 6 // A call is in progress
473 || this->send_pending_ || this->dial_pending_ || this->connect_pending_ || this->disconnect_pending_)) {
474 this->update();
475 }
476}
477
478void Sim800LComponent::send_sms(const std::string &recipient, const std::string &message) {
479 this->recipient_ = recipient;
481 this->send_pending_ = true;
482}
483
484void Sim800LComponent::send_ussd(const std::string &ussd_code) {
485 ESP_LOGD(TAG, "Sending USSD code: %s", ussd_code.c_str());
486 this->ussd_ = ussd_code;
487 this->send_ussd_pending_ = true;
488 this->update();
489}
491 ESP_LOGCONFIG(TAG, "SIM800L:");
492#ifdef USE_BINARY_SENSOR
493 LOG_BINARY_SENSOR(" ", "Registered", this->registered_binary_sensor_);
494#endif
495#ifdef USE_SENSOR
496 LOG_SENSOR(" ", "Rssi", this->rssi_sensor_);
497#endif
498}
499void Sim800LComponent::dial(const std::string &recipient) {
500 this->recipient_ = recipient;
501 this->dial_pending_ = true;
502}
505
507 this->registered_ = registered;
508#ifdef USE_BINARY_SENSOR
509 if (this->registered_binary_sensor_ != nullptr)
510 this->registered_binary_sensor_->publish_state(registered);
511#endif
512}
513
514} // namespace esphome::sim800l
const char * partial()
The partial line, NUL terminated, for prompts with no line end; the first N - 1 characters on the byt...
Definition line_reader.h:60
size_t length() const
Length of the partial line; 0 after a line is returned and while an overlong line is dropped.
Definition line_reader.h:56
char * feed(uint8_t c)
Feed one byte; returns the completed line (empty lines included), valid until the next call,...
Definition line_reader.h:23
void reset()
Forget the partial line and start a new one with the next byte.
Definition line_reader.h:76
void log_if_overflowed(const char *tag) const
Log the shared "line dropped" warning when the last feed() overflowed.
Definition line_reader.h:69
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
CallbackManager< void(std::string)> ussd_received_callback_
Definition sim800l.h:129
void send_cmd_(const std::string &message)
Definition sim800l.cpp:63
CallbackManager< void(std::string, std::string)> sms_received_callback_
Definition sim800l.h:125
CallbackManager< void(std::string)> incoming_call_callback_
Definition sim800l.h:126
binary_sensor::BinarySensor * registered_binary_sensor_
Definition sim800l.h:98
CallbackManager< void()> call_connected_callback_
Definition sim800l.h:127
void set_registered_(bool registered)
Definition sim800l.cpp:506
void send_sms(const std::string &recipient, const std::string &message)
Definition sim800l.cpp:478
void update() override
Retrieve the latest sensor values. This operation takes approximately 16ms.
Definition sim800l.cpp:13
CallbackManager< void(std::string)> dtmf_received_callback_
Definition sim800l.h:130
void parse_cmd_(std::string message)
Definition sim800l.cpp:71
CallbackManager< void()> call_disconnected_callback_
Definition sim800l.h:128
void dial(const std::string &recipient)
Definition sim800l.cpp:499
LineReader< SIM800L_READ_BUFFER_LENGTH > line_reader_
Definition sim800l.h:106
void send_ussd(const std::string &ussd_code)
Definition sim800l.cpp:484
void write_str(const char *str)
Definition uart.h:37
void write_byte(uint8_t data)
Definition uart.h:22
bool read_available(F &&on_byte)
Same as read_available_chunks(), calling on_byte(uint8_t) once per byte.
Definition uart.h:77
size_t write(uint8_t data)
Definition uart.h:105
const LogString * message
Definition component.cpp:35
const char * key
@ STATE_PARSE_SMS_RESPONSE
Definition sim800l.h:34
ESPHOME_LOG_TAG(TAG, "sim800l")
const char ASCII_LF
Definition sim800l.cpp:11
const char ASCII_CR
Definition sim800l.cpp:10
optional< T > parse_number(const char *str)
Parse an unsigned decimal number from a null-terminated string.
Definition helpers.h:1352
uint8_t end[39]
Definition sun_gtil2.cpp:17