ESPHome  2024.2.0
sim800l.cpp
Go to the documentation of this file.
1 #include "sim800l.h"
2 #include "esphome/core/log.h"
3 #include <cstring>
4 
5 namespace esphome {
6 namespace sim800l {
7 
8 static const char *const TAG = "sim800l";
9 
10 const char ASCII_CR = 0x0D;
11 const 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\"");
36  this->state_ = STATE_SEND_USSD1;
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");
43  this->state_ = STATE_CHECK_CALL;
44  return;
45  } else {
46  this->send_cmd_("AT");
47  this->state_ = STATE_SETUP_CMGF;
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 
61 void 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 
69 void 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...
78  this->state_ = STATE_PARSE_CLIP;
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
94  this->state_ = STATE_DISABLE_ECHO;
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;
136  case STATE_DISABLE_ECHO:
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) {
178  this->state_ = STATE_RECEIVED_USSD;
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");
221  this->state_ = STATE_CSQ_RESPONSE;
222  break;
223  case STATE_CSQ_RESPONSE:
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  }
270  this->state_ = STATE_RECEIVE_SMS;
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;
317  case STATE_RECEIVE_SMS:
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_);
325  this->state_ = STATE_RECEIVED_SMS;
326  } else {
327  if (this->message_.length() > 0)
328  this->message_ += "\n";
329  this->message_ += message;
330  }
331  break;
332  case STATE_RECEIVED_SMS:
333  case STATE_RECEIVED_USSD:
334  // Let the buffer flush. Next poll will request to delete the parsed index message.
335  break;
336  case STATE_SENDING_SMS_1:
337  this->send_cmd_("AT+CMGS=\"" + this->recipient_ + "\"");
338  this->state_ = STATE_SENDING_SMS_2;
339  break;
340  case STATE_SENDING_SMS_2:
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);
346  this->state_ = STATE_SENDING_SMS_3;
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;
354  case STATE_SENDING_SMS_3:
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 
452 void 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 
458 void 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 }
473 void Sim800LComponent::dial(const std::string &recipient) {
474  this->recipient_ = recipient;
475  this->dial_pending_ = true;
476 }
479 
480 void Sim800LComponent::set_registered_(bool registered) {
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 write_str(const char *str)
Definition: uart.h:27
CallbackManager< void(std::string)> ussd_received_callback_
Definition: sim800l.h:121
CallbackManager< void(std::string, std::string)> sms_received_callback_
Definition: sim800l.h:117
void write_byte(uint8_t data)
Definition: uart.h:19
void send_cmd_(const std::string &message)
Definition: sim800l.cpp:61
void send_sms(const std::string &recipient, const std::string &message)
Definition: sim800l.cpp:452
CallbackManager< void()> call_disconnected_callback_
Definition: sim800l.h:120
const char ASCII_CR
Definition: sim800l.cpp:10
void update() override
Retrieve the latest sensor values. This operation takes approximately 16ms.
Definition: sim800l.cpp:13
bool read_byte(uint8_t *data)
Definition: uart.h:29
void parse_cmd_(std::string message)
Definition: sim800l.cpp:69
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
char read_buffer_[SIM800L_READ_BUFFER_LENGTH]
Definition: sim800l.h:99
void publish_state(bool state)
Publish a new state to the front-end.
binary_sensor::BinarySensor * registered_binary_sensor_
Definition: sim800l.h:91
void send_ussd(const std::string &ussd_code)
Definition: sim800l.cpp:458
sensor::Sensor * rssi_sensor_
Definition: sim800l.h:95
CallbackManager< void(std::string)> incoming_call_callback_
Definition: sim800l.h:118
void dial(const std::string &recipient)
Definition: sim800l.cpp:473
CallbackManager< void()> call_connected_callback_
Definition: sim800l.h:119
void set_registered_(bool registered)
Definition: sim800l.cpp:480
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
const uint16_t SIM800L_READ_BUFFER_LENGTH
Definition: sim800l.h:19
size_t write(uint8_t data)
Definition: uart.h:52
const char ASCII_LF
Definition: sim800l.cpp:11