mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 22:48:10 +01: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:
parent
904a0b26ea
commit
fc465d6d93
5 changed files with 420 additions and 1 deletions
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};
|
||||
|
|
|
@ -465,3 +465,13 @@ ttp229_lsf:
|
|||
ttp229_bsf:
|
||||
sdo_pin: D0
|
||||
scl_pin: D1
|
||||
|
||||
sim800l:
|
||||
on_sms_received:
|
||||
- lambda: |-
|
||||
std::string str;
|
||||
str = sender;
|
||||
str = message;
|
||||
- sim800l.send_sms:
|
||||
message: 'hello you'
|
||||
recipient: '+1234'
|
||||
|
|
Loading…
Reference in a new issue