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