mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 06:33:51 +00:00 
			
		
		
		
	SMS Sender / Receiver (#522)
* add sim800l * Increse SoftwareSerial Buffer Size * use auto id on action * lint * lint * add to test3.yaml * lint Co-authored-by: Guillermo Ruffino <guillermo.ruffino@pampatech.net>
This commit is contained in:
		
				
					committed by
					
						 Otto Winter
						Otto Winter
					
				
			
			
				
	
			
			
			
						parent
						
							904a0b26ea
						
					
				
				
					commit
					fc465d6d93
				
			
							
								
								
									
										59
									
								
								esphome/components/sim800l/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								esphome/components/sim800l/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| from esphome.const import CONF_ID, CONF_TRIGGER_ID | ||||
| from esphome.components import uart | ||||
|  | ||||
| DEPENDENCIES = ['uart'] | ||||
|  | ||||
| sim800l_ns = cg.esphome_ns.namespace('sim800l') | ||||
| Sim800LComponent = sim800l_ns.class_('Sim800LComponent', cg.Component) | ||||
|  | ||||
| Sim800LReceivedMessageTrigger = sim800l_ns.class_('Sim800LReceivedMessageTrigger', | ||||
|                                                   automation.Trigger.template(cg.std_string, | ||||
|                                                                               cg.std_string)) | ||||
|  | ||||
| # Actions | ||||
| Sim800LSendSmsAction = sim800l_ns.class_('Sim800LSendSmsAction', automation.Action) | ||||
|  | ||||
| MULTI_CONF = True | ||||
|  | ||||
| CONF_ON_SMS_RECEIVED = 'on_sms_received' | ||||
| CONF_RECIPIENT = 'recipient' | ||||
| CONF_MESSAGE = 'message' | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(Sim800LComponent), | ||||
|     cv.Optional(CONF_ON_SMS_RECEIVED): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Sim800LReceivedMessageTrigger), | ||||
|     }), | ||||
| }).extend(cv.polling_component_schema('5s')).extend(uart.UART_DEVICE_SCHEMA)) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield uart.register_uart_device(var, config) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_SMS_RECEIVED, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         yield automation.build_automation(trigger, [(cg.std_string, 'message'), | ||||
|                                                     (cg.std_string, 'sender')], conf) | ||||
|  | ||||
|  | ||||
| SIM800L_SEND_SMS_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.use_id(Sim800LComponent), | ||||
|     cv.Required(CONF_RECIPIENT): cv.templatable(cv.string_strict), | ||||
|     cv.Required(CONF_MESSAGE): cv.templatable(cv.string), | ||||
| }) | ||||
|  | ||||
|  | ||||
| @automation.register_action('sim800l.send_sms', Sim800LSendSmsAction, SIM800L_SEND_SMS_SCHEMA) | ||||
| def sim800l_send_sms_to_code(config, action_id, template_arg, args): | ||||
|     paren = yield cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, paren) | ||||
|     template_ = yield cg.templatable(config[CONF_RECIPIENT], args, cg.std_string) | ||||
|     cg.add(var.set_recipient(template_)) | ||||
|     template_ = yield cg.templatable(config[CONF_MESSAGE], args, cg.std_string) | ||||
|     cg.add(var.set_message(template_)) | ||||
|     yield var | ||||
							
								
								
									
										259
									
								
								esphome/components/sim800l/sim800l.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										259
									
								
								esphome/components/sim800l/sim800l.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,259 @@ | ||||
| #include "sim800l.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include <string.h> | ||||
|  | ||||
| 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 { | ||||
|       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: | ||||
|       if (message.compare(0, 6, "+CMTI:") == 0) { | ||||
|         // While we were waiting for update to check for messages, this notifies a message | ||||
|         // is available. Grab it quickly | ||||
|         this->state_ = STATE_CHECK_SMS; | ||||
|       } | ||||
|       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'; | ||||
|       if (registered) { | ||||
|         if (!this->registered_) | ||||
|           ESP_LOGD(TAG, "Registered OK"); | ||||
|         send_cmd_("AT+CSQ"); | ||||
|         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: | ||||
|       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->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; | ||||
|     default: | ||||
|       ESP_LOGD(TAG, "Unhandled: %s - %d", message.c_str(), this->state_); | ||||
|       break; | ||||
|   } | ||||
|   if (this->state_ == STATE_CHECK_SMS) { | ||||
|     send_cmd_("AT+CMGL=\"ALL\""); | ||||
|     this->state_ = STATE_PARSE_SMS; | ||||
|     this->parse_index_ = 0; | ||||
|     this->expect_ack_ = true; | ||||
|   } | ||||
| } | ||||
|  | ||||
| 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(); | ||||
| } | ||||
|  | ||||
| }  // namespace sim800l | ||||
| }  // namespace esphome | ||||
							
								
								
									
										91
									
								
								esphome/components/sim800l/sim800l.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								esphome/components/sim800l/sim800l.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/uart/uart.h" | ||||
| #include "esphome/core/automation.h" | ||||
|  | ||||
| #define SIM800L_READ_BUFFER_LENGTH 255 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace sim800l { | ||||
|  | ||||
| enum State { | ||||
|   STATE_IDLE = 0, | ||||
|   STATE_INIT, | ||||
|   STATE_CHECK_AT, | ||||
|   STATE_CREG, | ||||
|   STATE_CREGWAIT, | ||||
|   STATE_CSQ, | ||||
|   STATE_CSQ_RESPONSE, | ||||
|   STATE_IDLEWAIT, | ||||
|   STATE_SENDINGSMS1, | ||||
|   STATE_SENDINGSMS2, | ||||
|   STATE_SENDINGSMS3, | ||||
|   STATE_CHECK_SMS, | ||||
|   STATE_PARSE_SMS, | ||||
|   STATE_PARSE_SMS_RESPONSE, | ||||
|   STATE_RECEIVESMS, | ||||
|   STATE_READSMS, | ||||
|   STATE_RECEIVEDSMS, | ||||
|   STATE_DELETEDSMS, | ||||
|   STATE_DISABLE_ECHO, | ||||
|   STATE_PARSE_SMS_OK | ||||
| }; | ||||
|  | ||||
| class Sim800LComponent : public uart::UARTDevice, public PollingComponent { | ||||
|  public: | ||||
|   /// Retrieve the latest sensor values. This operation takes approximately 16ms. | ||||
|   void update() override; | ||||
|   void loop() override; | ||||
|   void add_on_sms_received_callback(std::function<void(std::string, std::string)> callback) { | ||||
|     this->callback_.add(std::move(callback)); | ||||
|   } | ||||
|   void send_sms(std::string recipient, std::string message); | ||||
|  | ||||
|  protected: | ||||
|   void send_cmd_(std::string); | ||||
|   void parse_cmd_(std::string); | ||||
|  | ||||
|   std::string sender_; | ||||
|   char read_buffer_[SIM800L_READ_BUFFER_LENGTH]; | ||||
|   size_t read_pos_{0}; | ||||
|   uint8_t parse_index_{0}; | ||||
|   uint8_t watch_dog_{0}; | ||||
|   bool expect_ack_{false}; | ||||
|   sim800l::State state_{STATE_IDLE}; | ||||
|   bool registered_{false}; | ||||
|   int rssi_{0}; | ||||
|  | ||||
|   std::string recipient_; | ||||
|   std::string outgoing_message_; | ||||
|   bool send_pending_; | ||||
|  | ||||
|   CallbackManager<void(std::string, std::string)> callback_; | ||||
| }; | ||||
|  | ||||
| class Sim800LReceivedMessageTrigger : public Trigger<std::string, std::string> { | ||||
|  public: | ||||
|   explicit Sim800LReceivedMessageTrigger(Sim800LComponent *parent) { | ||||
|     parent->add_on_sms_received_callback( | ||||
|         [this](std::string message, std::string sender) { this->trigger(message, sender); }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class Sim800LSendSmsAction : public Action<Ts...> { | ||||
|  public: | ||||
|   Sim800LSendSmsAction(Sim800LComponent *parent) : parent_(parent) {} | ||||
|   TEMPLATABLE_VALUE(std::string, recipient) | ||||
|   TEMPLATABLE_VALUE(std::string, message) | ||||
|  | ||||
|   void play(Ts... x) { | ||||
|     auto recipient = this->recipient_.value(x...); | ||||
|     auto message = this->message_.value(x...); | ||||
|     this->parent_->send_sms(recipient, message); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   Sim800LComponent *parent_; | ||||
| }; | ||||
|  | ||||
| }  // namespace sim800l | ||||
| }  // namespace esphome | ||||
| @@ -30,7 +30,7 @@ class ESP8266SoftwareSerial { | ||||
|  | ||||
|   uint32_t bit_time_{0}; | ||||
|   uint8_t *rx_buffer_{nullptr}; | ||||
|   size_t rx_buffer_size_{64}; | ||||
|   size_t rx_buffer_size_{512}; | ||||
|   volatile size_t rx_in_pos_{0}; | ||||
|   size_t rx_out_pos_{0}; | ||||
|   ISRInternalGPIOPin *tx_pin_{nullptr}; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user