Add RFBridge component (#896)

* Add RFBridge component

* Fix format issues

* Rename methods

* More formatting

* Fix line length

* Apply suggestions from code review

Co-Authored-By: Otto Winter <otto@otto-winter.com>

* Check uart settings on dump

* Make receiving local to the loop

* FIx code order and schema

* Add rf_bridge to test file

* Apply suggestions from code review

Co-Authored-By: Otto Winter <otto@otto-winter.com>
This commit is contained in:
Jesse Hills 2019-12-10 23:09:35 +13:00 committed by Otto Winter
parent ae784dc74c
commit f5b7cc81d8
4 changed files with 285 additions and 2 deletions

View file

@ -0,0 +1,75 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_CODE, CONF_LOW, CONF_SYNC, CONF_HIGH
from esphome.components import uart
DEPENDENCIES = ['uart']
rf_bridge_ns = cg.esphome_ns.namespace('rf_bridge')
RFBridgeComponent = rf_bridge_ns.class_('RFBridgeComponent', cg.Component, uart.UARTDevice)
RFBridgeData = rf_bridge_ns.struct('RFBridgeData')
RFBridgeReceivedCodeTrigger = rf_bridge_ns.class_('RFBridgeReceivedCodeTrigger',
automation.Trigger.template(RFBridgeData))
RFBridgeSendCodeAction = rf_bridge_ns.class_('RFBridgeSendCodeAction', automation.Action)
RFBridgeLearnAction = rf_bridge_ns.class_('RFBridgeLearnAction', automation.Action)
CONF_ON_CODE_RECEIVED = 'on_code_received'
CONFIG_SCHEMA = cv.All(cv.Schema({
cv.GenerateID(): cv.declare_id(RFBridgeComponent),
cv.Optional(CONF_ON_CODE_RECEIVED): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RFBridgeReceivedCodeTrigger),
}),
}).extend(uart.UART_DEVICE_SCHEMA).extend(cv.COMPONENT_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_CODE_RECEIVED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
yield automation.build_automation(trigger, [(RFBridgeData, 'data')], conf)
RFBRIDGE_SEND_CODE_SCHEMA = cv.Schema({
cv.GenerateID(): cv.use_id(RFBridgeComponent),
cv.Required(CONF_SYNC): cv.templatable(cv.hex_uint16_t),
cv.Required(CONF_LOW): cv.templatable(cv.hex_uint16_t),
cv.Required(CONF_HIGH): cv.templatable(cv.hex_uint16_t),
cv.Required(CONF_CODE): cv.templatable(cv.hex_uint32_t)
})
@automation.register_action('rf_bridge.send_code', RFBridgeSendCodeAction,
RFBRIDGE_SEND_CODE_SCHEMA)
def rf_bridge_send_code_to_code(config, action_id, template_args, args):
paren = yield cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_args, paren)
template_ = yield cg.templatable(config[CONF_SYNC], args, cg.uint16)
cg.add(var.set_sync(template_))
template_ = yield cg.templatable(config[CONF_LOW], args, cg.uint16)
cg.add(var.set_low(template_))
template_ = yield cg.templatable(config[CONF_HIGH], args, cg.uint16)
cg.add(var.set_high(template_))
template_ = yield cg.templatable(config[CONF_CODE], args, cg.uint32)
cg.add(var.set_code(template_))
yield var
RFBRIDGE_LEARN_SCHEMA = cv.Schema({
cv.GenerateID(): cv.use_id(RFBridgeComponent)
})
@automation.register_action('rf_bridge.learn', RFBridgeLearnAction, RFBRIDGE_LEARN_SCHEMA)
def rf_bridge_learnx_to_code(config, action_id, template_args, args):
paren = yield cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_args, paren)
yield var

View file

@ -0,0 +1,100 @@
#include "rf_bridge.h"
#include "esphome/core/log.h"
#include <cstring>
namespace esphome {
namespace rf_bridge {
static const char *TAG = "rf_bridge";
void RFBridgeComponent::ack_() {
ESP_LOGV(TAG, "Sending ACK");
this->write(RF_CODE_START);
this->write(RF_CODE_ACK);
this->write(RF_CODE_STOP);
this->flush();
}
void RFBridgeComponent::decode_() {
uint8_t action = uartbuf_[0];
RFBridgeData data{};
switch (action) {
case RF_CODE_ACK:
ESP_LOGD(TAG, "Action OK");
break;
case RF_CODE_LEARN_KO:
this->ack_();
ESP_LOGD(TAG, "Learn timeout");
break;
case RF_CODE_LEARN_OK:
ESP_LOGD(TAG, "Learn started");
case RF_CODE_RFIN:
this->ack_();
data.sync = (uartbuf_[1] << 8) | uartbuf_[2];
data.low = (uartbuf_[3] << 8) | uartbuf_[4];
data.high = (uartbuf_[5] << 8) | uartbuf_[6];
data.code = (uartbuf_[7] << 16) | (uartbuf_[8] << 8) | uartbuf_[9];
ESP_LOGD(TAG, "Received RFBridge Code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low,
data.high, data.code);
this->callback_.call(data);
break;
default:
ESP_LOGD(TAG, "Unknown action: 0x%02X", action);
break;
}
this->last_ = millis();
}
void RFBridgeComponent::loop() {
bool receiving = false;
if (this->last_ != 0 && millis() - this->last_ > RF_DEBOUNCE) {
this->last_ = 0;
}
while (this->available()) {
uint8_t c = this->read();
if (receiving) {
if (c == RF_CODE_STOP && (this->uartpos_ == 1 || this->uartpos_ == RF_MESSAGE_SIZE + 1)) {
this->decode_();
receiving = false;
} else if (this->uartpos_ <= RF_MESSAGE_SIZE) {
this->uartbuf_[uartpos_++] = c;
} else {
receiving = false;
}
} else if (c == RF_CODE_START) {
this->uartpos_ = 0;
receiving = true;
}
}
}
void RFBridgeComponent::send_code(RFBridgeData data) {
ESP_LOGD(TAG, "Sending code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low, data.high,
data.code);
this->write(RF_CODE_START);
this->write(RF_CODE_RFOUT);
this->write(data.sync);
this->write(data.low);
this->write(data.high);
this->write(data.code);
this->write(RF_CODE_STOP);
}
void RFBridgeComponent::learn() {
ESP_LOGD(TAG, "Learning mode");
this->write(RF_CODE_START);
this->write(RF_CODE_LEARN);
this->write(RF_CODE_STOP);
}
void RFBridgeComponent::dump_config() {
ESP_LOGCONFIG(TAG, "RF_Bridge:");
this->check_uart_settings(19200);
}
} // namespace rf_bridge
} // namespace esphome

View file

@ -0,0 +1,95 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/automation.h"
namespace esphome {
namespace rf_bridge {
static const uint8_t RF_MESSAGE_SIZE = 9;
static const uint8_t RF_CODE_START = 0xAA;
static const uint8_t RF_CODE_ACK = 0xA0;
static const uint8_t RF_CODE_LEARN = 0xA1;
static const uint8_t RF_CODE_LEARN_KO = 0xA2;
static const uint8_t RF_CODE_LEARN_OK = 0xA3;
static const uint8_t RF_CODE_RFIN = 0xA4;
static const uint8_t RF_CODE_RFOUT = 0xA5;
static const uint8_t RF_CODE_SNIFFING_ON = 0xA6;
static const uint8_t RF_CODE_SNIFFING_OFF = 0xA7;
static const uint8_t RF_CODE_RFOUT_NEW = 0xA8;
static const uint8_t RF_CODE_LEARN_NEW = 0xA9;
static const uint8_t RF_CODE_LEARN_KO_NEW = 0xAA;
static const uint8_t RF_CODE_LEARN_OK_NEW = 0xAB;
static const uint8_t RF_CODE_RFOUT_BUCKET = 0xB0;
static const uint8_t RF_CODE_STOP = 0x55;
static const uint8_t RF_DEBOUNCE = 200;
struct RFBridgeData {
uint16_t sync;
uint16_t low;
uint16_t high;
uint32_t code;
};
class RFBridgeComponent : public uart::UARTDevice, public Component {
public:
void loop() override;
void dump_config() override;
void add_on_code_received_callback(std::function<void(RFBridgeData)> callback) {
this->callback_.add(std::move(callback));
}
void send_code(RFBridgeData data);
void learn();
protected:
void ack_();
void decode_();
unsigned long last_ = 0;
unsigned char uartbuf_[RF_MESSAGE_SIZE + 3] = {0};
unsigned char uartpos_ = 0;
CallbackManager<void(RFBridgeData)> callback_;
};
class RFBridgeReceivedCodeTrigger : public Trigger<RFBridgeData> {
public:
explicit RFBridgeReceivedCodeTrigger(RFBridgeComponent *parent) {
parent->add_on_code_received_callback([this](RFBridgeData data) { this->trigger(data); });
}
};
template<typename... Ts> class RFBridgeSendCodeAction : public Action<Ts...> {
public:
RFBridgeSendCodeAction(RFBridgeComponent *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(uint16_t, sync)
TEMPLATABLE_VALUE(uint16_t, low)
TEMPLATABLE_VALUE(uint16_t, high)
TEMPLATABLE_VALUE(uint32_t, code)
void play(Ts... x) {
RFBridgeData data{};
data.sync = this->sync_.value(x...);
data.low = this->low_.value(x...);
data.high = this->high_.value(x...);
data.code = this->code_.value(x...);
this->parent_->send_code(data);
}
protected:
RFBridgeComponent *parent_;
};
template<typename... Ts> class RFBridgeLearnAction : public Action<Ts...> {
public:
RFBridgeLearnAction(RFBridgeComponent *parent) : parent_(parent) {}
void play(Ts... x) { this->parent_->learn(); }
protected:
RFBridgeComponent *parent_;
};
} // namespace rf_bridge
} // namespace esphome

View file

@ -666,8 +666,21 @@ dfplayer:
dfplayer.is_playing
then:
logger.log: 'Playback finished event'
tm1651:
id: tm1651_battery
clk_pin: D6
dio_pin: D5
rf_bridge:
on_code_received:
- lambda: |-
uint32_t test;
test = data.sync;
test = data.low;
test = data.high;
test = data.code;
- rf_bridge.send_code:
sync: 0x1234
low: 0x1234
high: 0x1234
code: 0x123456
- rf_bridge.learn