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:
Guillermo Ruffino 2019-06-17 15:13:52 -03:00 committed by Otto Winter
parent 904a0b26ea
commit fc465d6d93
5 changed files with 420 additions and 1 deletions

View 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

View 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

View 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

View file

@ -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};

View file

@ -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'