#include "sim800l.h" #include "esphome/core/log.h" #include namespace esphome { namespace sim800l { static const char *TAG = "sim800l"; const char ASCII_CR = 0x0D; const char ASCII_LF = 0x0A; void Sim800LComponent::update() { if (this->watch_dog_++ == 2) { this->state_ = STATE_INIT; this->write(26); } if (state_ == STATE_INIT) { if (this->registered_ && this->send_pending_) { this->send_cmd_("AT+CSCS=\"GSM\""); this->state_ = STATE_SENDINGSMS1; } else if (this->registered_ && this->dial_pending_) { this->send_cmd_("AT+CSCS=\"GSM\""); this->state_ = STATE_DIALING1; } else { this->send_cmd_("AT"); this->state_ = STATE_CHECK_AT; } this->expect_ack_ = true; } if (state_ == STATE_RECEIVEDSMS) { // Serial Buffer should have flushed. // Send cmd to delete received sms char delete_cmd[20]; sprintf(delete_cmd, "AT+CMGD=%d", this->parse_index_); this->send_cmd_(delete_cmd); this->state_ = STATE_CHECK_SMS; this->expect_ack_ = true; } } void Sim800LComponent::send_cmd_(std::string message) { ESP_LOGV(TAG, "S: %s - %d", message.c_str(), this->state_); this->watch_dog_ = 0; this->write_str(message.c_str()); this->write_byte(ASCII_LF); } void Sim800LComponent::parse_cmd_(std::string message) { ESP_LOGV(TAG, "R: %s - %d", message.c_str(), this->state_); if (message.empty()) return; if (this->expect_ack_) { bool ok = message == "OK"; this->expect_ack_ = false; if (!ok) { if (this->state_ == STATE_CHECK_AT && message == "AT") { // Expected ack but AT echo received this->state_ = STATE_DISABLE_ECHO; this->expect_ack_ = true; } else { ESP_LOGW(TAG, "Not ack. %d %s", this->state_, message.c_str()); this->state_ = STATE_IDLE; // Let it timeout return; } } } switch (this->state_) { case STATE_INIT: { // While we were waiting for update to check for messages, this notifies a message // is available. bool message_available = message.compare(0, 6, "+CMTI:") == 0; if (!message_available) break; // Else fall thru ... } case STATE_CHECK_SMS: send_cmd_("AT+CMGL=\"ALL\""); this->state_ = STATE_PARSE_SMS; this->parse_index_ = 0; break; case STATE_DISABLE_ECHO: send_cmd_("ATE0"); this->state_ = STATE_CHECK_AT; this->expect_ack_ = true; break; case STATE_CHECK_AT: send_cmd_("AT+CMGF=1"); this->state_ = STATE_CREG; this->expect_ack_ = true; break; case STATE_CREG: send_cmd_("AT+CREG?"); this->state_ = STATE_CREGWAIT; break; case STATE_CREGWAIT: { // Response: "+CREG: 0,1" -- the one there means registered ok // "+CREG: -,-" means not registered ok bool registered = message.compare(0, 6, "+CREG:") == 0 && (message[9] == '1' || message[9] == '5'); if (registered) { if (!this->registered_) ESP_LOGD(TAG, "Registered OK"); this->state_ = STATE_CSQ; this->expect_ack_ = true; } else { ESP_LOGW(TAG, "Registration Fail"); if (message[7] == '0') { // Network registration is disable, enable it send_cmd_("AT+CREG=1"); this->expect_ack_ = true; this->state_ = STATE_CHECK_AT; } else { // Keep waiting registration this->state_ = STATE_CREG; } } this->registered_ = registered; break; } case STATE_CSQ: send_cmd_("AT+CSQ"); this->state_ = STATE_CSQ_RESPONSE; break; case STATE_CSQ_RESPONSE: if (message.compare(0, 5, "+CSQ:") == 0) { size_t comma = message.find(',', 6); if (comma != 6) { this->rssi_ = strtol(message.substr(6, comma - 6).c_str(), nullptr, 10); ESP_LOGD(TAG, "RSSI: %d", this->rssi_); } } this->expect_ack_ = true; this->state_ = STATE_CHECK_SMS; break; case STATE_PARSE_SMS: this->state_ = STATE_PARSE_SMS_RESPONSE; break; case STATE_PARSE_SMS_RESPONSE: if (message.compare(0, 6, "+CMGL:") == 0 && this->parse_index_ == 0) { size_t start = 7; size_t end = message.find(',', start); uint8_t item = 0; while (end != start) { item++; if (item == 1) { // Slot Index this->parse_index_ = strtol(message.substr(start, end - start).c_str(), nullptr, 10); } // item 2 = STATUS, usually "REC UNERAD" if (item == 3) { // recipient // Add 1 and remove 2 from substring to get rid of "quotes" this->sender_ = message.substr(start + 1, end - start - 2); break; } // item 4 = "" // item 5 = Received timestamp start = end + 1; end = message.find(',', start); } if (item < 2) { ESP_LOGD(TAG, "Invalid message %d %s", this->state_, message.c_str()); return; } this->state_ = STATE_RECEIVESMS; } // Otherwise we receive another OK, we do nothing just wait polling to continuously check for SMS if (message == "OK") this->state_ = STATE_INIT; break; case STATE_RECEIVESMS: /* Our recipient is set and the message body is in message kick ESPHome callback now */ ESP_LOGD(TAG, "Received SMS from: %s", this->sender_.c_str()); ESP_LOGD(TAG, "%s", message.c_str()); this->callback_.call(message, this->sender_); /* If the message is multiline, next lines will contain message data. If there were other messages in the list, next line will be +CMGL: ... At the end of the list the new line and the OK should be received. To keep this simple just first line of message if considered, then the next state will swallow all received data and in next poll event this message index is marked for deletion. */ this->state_ = STATE_RECEIVEDSMS; break; case STATE_RECEIVEDSMS: // Let the buffer flush. Next poll will request to delete the parsed index message. break; case STATE_SENDINGSMS1: this->send_cmd_("AT+CMGS=\"" + this->recipient_ + "\""); this->state_ = STATE_SENDINGSMS2; break; case STATE_SENDINGSMS2: if (message == ">") { // Send sms body ESP_LOGD(TAG, "Sending message: '%s'", this->outgoing_message_.c_str()); this->write_str(this->outgoing_message_.c_str()); this->write(26); this->state_ = STATE_SENDINGSMS3; } else { this->registered_ = false; this->state_ = STATE_INIT; this->send_cmd_("AT+CMEE=2"); this->write(26); } break; case STATE_SENDINGSMS3: if (message.compare(0, 6, "+CMGS:") == 0) { ESP_LOGD(TAG, "SMS Sent OK: %s", message.c_str()); this->send_pending_ = false; this->state_ = STATE_CHECK_SMS; this->expect_ack_ = true; } break; case STATE_DIALING1: this->send_cmd_("ATD" + this->recipient_ + ';'); this->state_ = STATE_DIALING2; break; case STATE_DIALING2: if (message == "OK") { // Dialing ESP_LOGD(TAG, "Dialing: '%s'", this->recipient_.c_str()); this->state_ = STATE_INIT; this->dial_pending_ = false; } else { this->registered_ = false; this->state_ = STATE_INIT; this->send_cmd_("AT+CMEE=2"); this->write(26); } break; default: ESP_LOGD(TAG, "Unhandled: %s - %d", message.c_str(), this->state_); break; } } void Sim800LComponent::loop() { // Read message while (this->available()) { uint8_t byte; this->read_byte(&byte); if (this->read_pos_ == SIM800L_READ_BUFFER_LENGTH) this->read_pos_ = 0; ESP_LOGVV(TAG, "Buffer pos: %u %d", this->read_pos_, byte); // NOLINT if (byte == ASCII_CR) continue; if (byte >= 0x7F) byte = '?'; // need to be valid utf8 string for log functions. this->read_buffer_[this->read_pos_] = byte; if (this->state_ == STATE_SENDINGSMS2 && this->read_pos_ == 0 && byte == '>') this->read_buffer_[++this->read_pos_] = ASCII_LF; if (this->read_buffer_[this->read_pos_] == ASCII_LF) { this->read_buffer_[this->read_pos_] = 0; this->read_pos_ = 0; this->parse_cmd_(this->read_buffer_); } else { this->read_pos_++; } } } void Sim800LComponent::send_sms(std::string recipient, std::string message) { ESP_LOGD(TAG, "Sending to %s: %s", recipient.c_str(), message.c_str()); this->recipient_ = recipient; this->outgoing_message_ = message; this->send_pending_ = true; this->update(); } void Sim800LComponent::dump_config() { ESP_LOGCONFIG(TAG, "SIM800L:"); ESP_LOGCONFIG(TAG, " RSSI: %d dB", this->rssi_); } void Sim800LComponent::dial(std::string recipient) { ESP_LOGD(TAG, "Dialing %s", recipient.c_str()); this->recipient_ = recipient; this->dial_pending_ = true; this->update(); } } // namespace sim800l } // namespace esphome