mirror of https://github.com/esphome/esphome.git
Merge c4031bef74
into c7c0d97a5e
This commit is contained in:
commit
abf55447e6
|
@ -105,6 +105,7 @@ esphome/components/dps310/* @kbx81
|
|||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/duty_time/* @dudanov
|
||||
esphome/components/ebus/* @guidoschreuder
|
||||
esphome/components/ee895/* @Stock-M
|
||||
esphome/components/ektf2232/touchscreen/* @jesserockz
|
||||
esphome/components/emc2101/* @ellull
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
import voluptuous as vol
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_TX_PIN,
|
||||
CONF_RX_PIN,
|
||||
CONF_ADDRESS,
|
||||
CONF_COMMAND,
|
||||
CONF_PAYLOAD,
|
||||
CONF_POSITION,
|
||||
)
|
||||
|
||||
|
||||
# TODO: send identification response when requested
|
||||
# TODO: add debug mode that logs all messages on the bus
|
||||
# TODO: investigate using UART component, but that does not seem to expose the UART NUM
|
||||
# TODO: telegrams are always send to secondary right now, primary->primary communication
|
||||
# is possible according to the spec, but haven't found any need for it yet
|
||||
|
||||
|
||||
CODEOWNERS = ["@guidoschreuder"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
ebus_ns = cg.esphome_ns.namespace("ebus")
|
||||
|
||||
CONF_EBUS_ID = "ebus_id"
|
||||
CONF_PRIMARY_ADDRESS = "primary_address"
|
||||
CONF_MAX_TRIES = "max_tries"
|
||||
CONF_MAX_LOCK_COUNTER = "max_lock_counter"
|
||||
CONF_HISTORY_QUEUE_SIZE = "history_queue_size"
|
||||
CONF_COMMAND_QUEUE_SIZE = "command_queue_size"
|
||||
CONF_POLL_INTERVAL = "poll_interval"
|
||||
CONF_UART = "uart"
|
||||
CONF_NUM = "num"
|
||||
|
||||
CONF_TELEGRAM = "telegram"
|
||||
CONF_SEND_POLL = "send_poll"
|
||||
CONF_DECODE = "decode"
|
||||
|
||||
SYN = 0xAA
|
||||
ESC = 0xA9
|
||||
|
||||
|
||||
def validate_ebus_address(address):
|
||||
if address == SYN:
|
||||
raise vol.Invalid("SYN symbol (0xAA) is not a valid address")
|
||||
if address == ESC:
|
||||
raise vol.Invalid("ESC symbol (0xA9) is not a valid address")
|
||||
return cv.hex_uint8_t(address)
|
||||
|
||||
|
||||
def is_primary_nibble(value):
|
||||
return (value & 0x0F) in {0x00, 0x01, 0x03, 0x07, 0x0F}
|
||||
|
||||
|
||||
def validate_primary_address(value):
|
||||
"""Validate that the config option is a valid ebus primary address."""
|
||||
if is_primary_nibble(value) and is_primary_nibble(value >> 4):
|
||||
return value & 0xFF
|
||||
raise vol.Invalid(f"'0x{value:02x}' is an invalid ebus primary address")
|
||||
|
||||
|
||||
EbusComponent = ebus_ns.class_("EbusComponent", cg.Component)
|
||||
|
||||
|
||||
def create_decode_schema(options):
|
||||
return cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_POSITION, default=0): cv.int_range(0, 15),
|
||||
}
|
||||
).extend(options)
|
||||
|
||||
|
||||
def create_telegram_schema(decode_options):
|
||||
return {
|
||||
cv.GenerateID(CONF_EBUS_ID): cv.use_id(EbusComponent),
|
||||
cv.Required(CONF_TELEGRAM): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_SEND_POLL, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ADDRESS): validate_ebus_address,
|
||||
cv.Required(CONF_COMMAND): cv.hex_uint16_t,
|
||||
cv.Required(CONF_PAYLOAD): cv.Schema([cv.hex_uint8_t]),
|
||||
cv.Optional(CONF_DECODE): create_decode_schema(decode_options),
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EbusComponent),
|
||||
cv.Optional(CONF_PRIMARY_ADDRESS): validate_primary_address,
|
||||
cv.Optional(CONF_MAX_TRIES, default=2): cv.hex_uint8_t,
|
||||
cv.Optional(CONF_MAX_LOCK_COUNTER, default=4): cv.hex_uint8_t,
|
||||
cv.Optional(CONF_HISTORY_QUEUE_SIZE, default=20): cv.uint8_t,
|
||||
cv.Optional(CONF_COMMAND_QUEUE_SIZE, default=10): cv.uint8_t,
|
||||
cv.Optional(CONF_POLL_INTERVAL, default="30s"): cv.time_period,
|
||||
cv.Required(CONF_UART): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NUM): cv.uint8_t,
|
||||
cv.Required(CONF_TX_PIN): cv.uint8_t,
|
||||
cv.Required(CONF_RX_PIN): cv.uint8_t,
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if CONF_PRIMARY_ADDRESS in config:
|
||||
cg.add(var.set_primary_address(config[CONF_PRIMARY_ADDRESS]))
|
||||
cg.add(var.set_max_tries(config[CONF_MAX_TRIES]))
|
||||
cg.add(var.set_max_lock_counter(config[CONF_MAX_LOCK_COUNTER]))
|
||||
cg.add(var.set_uart_num(config[CONF_UART][CONF_NUM]))
|
||||
cg.add(var.set_uart_tx_pin(config[CONF_UART][CONF_TX_PIN]))
|
||||
cg.add(var.set_uart_rx_pin(config[CONF_UART][CONF_RX_PIN]))
|
||||
cg.add(var.set_history_queue_size(config[CONF_HISTORY_QUEUE_SIZE]))
|
||||
cg.add(var.set_command_queue_size(config[CONF_COMMAND_QUEUE_SIZE]))
|
||||
cg.add(var.set_update_interval(config[CONF_POLL_INTERVAL].total_milliseconds))
|
||||
|
||||
|
||||
def item_config(ebus, item, config):
|
||||
cg.add(item.set_parent(ebus))
|
||||
cg.add(ebus.add_item(item))
|
||||
cg.add(item.set_send_poll(config[CONF_TELEGRAM][CONF_SEND_POLL]))
|
||||
if CONF_ADDRESS in config[CONF_TELEGRAM]:
|
||||
cg.add(item.set_address(config[CONF_TELEGRAM][CONF_ADDRESS]))
|
||||
cg.add(item.set_command(config[CONF_TELEGRAM][CONF_COMMAND]))
|
||||
cg.add(item.set_payload(config[CONF_TELEGRAM][CONF_PAYLOAD]))
|
||||
cg.add(
|
||||
item.set_response_read_position(
|
||||
config[CONF_TELEGRAM][CONF_DECODE][CONF_POSITION]
|
||||
)
|
||||
)
|
|
@ -0,0 +1,36 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
|
||||
from .. import (
|
||||
CONF_EBUS_ID,
|
||||
CONF_TELEGRAM,
|
||||
CONF_DECODE,
|
||||
ebus_ns,
|
||||
create_telegram_schema,
|
||||
item_config,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["ebus"]
|
||||
|
||||
EbusBinarySensor = ebus_ns.class_(
|
||||
"EbusBinarySensor", binary_sensor.BinarySensor, cg.Component
|
||||
)
|
||||
|
||||
CONF_MASK = "mask"
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
binary_sensor.binary_sensor_schema(EbusBinarySensor).extend(
|
||||
create_telegram_schema(
|
||||
{cv.Optional(CONF_MASK, default=0xFF): cv.int_range(1, 255)}
|
||||
)
|
||||
)
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
ebus = await cg.get_variable(config[CONF_EBUS_ID])
|
||||
sens = await binary_sensor.new_binary_sensor(config)
|
||||
|
||||
item_config(ebus, sens, config)
|
||||
cg.add(sens.set_response_read_mask(config[CONF_TELEGRAM][CONF_DECODE][CONF_MASK]))
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
#include "ebus_binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ebus {
|
||||
|
||||
void EbusBinarySensor::process_received(Telegram telegram) {
|
||||
if (!is_mine(telegram)) {
|
||||
return;
|
||||
}
|
||||
uint8_t result = (uint8_t) this->get_response_bytes(telegram, this->response_position_, 1);
|
||||
this->publish_state(result & this->mask_);
|
||||
}
|
||||
|
||||
} // namespace ebus
|
||||
} // namespace esphome
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "../ebus_component.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ebus {
|
||||
|
||||
class EbusBinarySensor : public EbusItem, public binary_sensor::BinarySensor {
|
||||
public:
|
||||
EbusBinarySensor() {}
|
||||
|
||||
void set_response_read_mask(uint8_t mask) { this->mask_ = mask; };
|
||||
|
||||
void process_received(Telegram /*telegram*/) override;
|
||||
|
||||
protected:
|
||||
int8_t mask_;
|
||||
};
|
||||
|
||||
} // namespace ebus
|
||||
} // namespace esphome
|
|
@ -0,0 +1,319 @@
|
|||
#include "ebus.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace esphome {
|
||||
namespace ebus {
|
||||
|
||||
uint8_t Ebus::uart_send_char_(uint8_t cr, bool esc, bool run_crc, uint8_t crc_init) {
|
||||
char buffer[2];
|
||||
uint8_t crc = 0;
|
||||
uint8_t len = 1;
|
||||
if (esc && cr == ESC) {
|
||||
buffer[0] = ESC;
|
||||
buffer[1] = 0x00;
|
||||
len = 2;
|
||||
} else if (esc && cr == SYN) {
|
||||
buffer[0] = ESC;
|
||||
buffer[1] = 0x01;
|
||||
len = 2;
|
||||
} else {
|
||||
buffer[0] = cr;
|
||||
}
|
||||
uart_send_(buffer, len);
|
||||
if (!run_crc) {
|
||||
return 0;
|
||||
}
|
||||
crc = Elf::crc8_calc(buffer[0], crc_init);
|
||||
if (len == 1) {
|
||||
return crc;
|
||||
}
|
||||
return Elf::crc8_calc(buffer[1], crc);
|
||||
}
|
||||
|
||||
void Ebus::uart_send_char_(uint8_t cr, bool esc) { this->uart_send_char_(cr, esc, false, 0); }
|
||||
|
||||
void Ebus::uart_send_remaining_request_part_(SendCommand &command) {
|
||||
this->uart_send_char_(command.get_zz());
|
||||
this->uart_send_char_(command.get_pb());
|
||||
this->uart_send_char_(command.get_sb());
|
||||
this->uart_send_char_(command.get_nn());
|
||||
for (int i = 0; i < command.get_nn(); i++) {
|
||||
this->uart_send_char_((uint8_t) command.get_request_byte(i));
|
||||
}
|
||||
this->uart_send_char_(command.get_crc());
|
||||
}
|
||||
|
||||
void Ebus::process_received_char(uint8_t received_byte) {
|
||||
// keep track of number of character between last 2 SYN chars
|
||||
// this is needed in case of arbitration
|
||||
if (received_byte == SYN) {
|
||||
this->state_ = this->char_count_since_last_syn_ == 1 ? EbusState::ARBITRATION : EbusState::NORMAL;
|
||||
this->char_count_since_last_syn_ = 0;
|
||||
|
||||
if (this->lock_counter_ > 0 && this->state_ == EbusState::NORMAL) {
|
||||
this->lock_counter_--;
|
||||
}
|
||||
|
||||
} else {
|
||||
this->char_count_since_last_syn_++;
|
||||
}
|
||||
|
||||
if (this->receiving_telegram_.is_finished()) {
|
||||
if (this->queue_received_telegram_) {
|
||||
this->queue_received_telegram_(this->receiving_telegram_);
|
||||
}
|
||||
this->receiving_telegram_ = Telegram();
|
||||
}
|
||||
|
||||
if (this->active_command_.is_finished() && this->dequeue_command_) {
|
||||
SendCommand dequeued;
|
||||
if (this->dequeue_command_(&dequeued)) {
|
||||
this->active_command_ = dequeued;
|
||||
}
|
||||
}
|
||||
|
||||
switch (this->receiving_telegram_.get_state()) {
|
||||
case TelegramState::waitForSyn:
|
||||
if (received_byte == SYN) {
|
||||
this->receiving_telegram_.set_state(TelegramState::waitForArbitration);
|
||||
}
|
||||
break;
|
||||
case TelegramState::waitForArbitration:
|
||||
if (received_byte != SYN) {
|
||||
this->receiving_telegram_.push_req_data(received_byte);
|
||||
this->receiving_telegram_.set_state(TelegramState::waitForRequestData);
|
||||
}
|
||||
break;
|
||||
case TelegramState::waitForRequestData:
|
||||
if (received_byte == SYN) {
|
||||
if (this->receiving_telegram_.get_zz() == ESC) {
|
||||
this->receiving_telegram_.set_state(TelegramState::endArbitration);
|
||||
} else {
|
||||
this->receiving_telegram_.set_state(TelegramState::endErrorUnexpectedSyn);
|
||||
}
|
||||
} else {
|
||||
this->receiving_telegram_.push_req_data(received_byte);
|
||||
if (this->receiving_telegram_.is_request_complete()) {
|
||||
this->receiving_telegram_.set_state(this->receiving_telegram_.is_ack_expected()
|
||||
? TelegramState::waitForRequestAck
|
||||
: TelegramState::endCompleted);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TelegramState::waitForRequestAck:
|
||||
switch (received_byte) {
|
||||
case ACK:
|
||||
this->receiving_telegram_.set_state(this->receiving_telegram_.is_response_expected()
|
||||
? TelegramState::waitForResponseData
|
||||
: TelegramState::endCompleted);
|
||||
break;
|
||||
case NACK:
|
||||
this->receiving_telegram_.set_state(TelegramState::endErrorRequestNackReceived);
|
||||
break;
|
||||
default:
|
||||
this->receiving_telegram_.set_state(TelegramState::endErrorRequestNoAck);
|
||||
}
|
||||
break;
|
||||
case TelegramState::waitForResponseData:
|
||||
if (received_byte == SYN) {
|
||||
this->receiving_telegram_.set_state(TelegramState::endErrorUnexpectedSyn);
|
||||
} else {
|
||||
this->receiving_telegram_.push_response_data(received_byte);
|
||||
if (this->receiving_telegram_.is_response_complete()) {
|
||||
this->receiving_telegram_.set_state(TelegramState::waitForResponseAck);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TelegramState::waitForResponseAck:
|
||||
switch (received_byte) {
|
||||
case ACK:
|
||||
this->receiving_telegram_.set_state(TelegramState::endCompleted);
|
||||
break;
|
||||
case NACK:
|
||||
this->receiving_telegram_.set_state(TelegramState::endErrorResponseNackReceived);
|
||||
break;
|
||||
default:
|
||||
this->receiving_telegram_.set_state(TelegramState::endErrorResponseNoAck);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (this->active_command_.get_state()) {
|
||||
case TelegramState::waitForSend:
|
||||
if (received_byte == SYN && state_ == EbusState::NORMAL && this->lock_counter_ == 0) {
|
||||
this->active_command_.set_state(TelegramState::waitForArbitration);
|
||||
this->uart_send_char_(this->active_command_.get_qq());
|
||||
}
|
||||
break;
|
||||
case TelegramState::waitForArbitration:
|
||||
if (received_byte == this->active_command_.get_qq()) {
|
||||
// we won arbitration
|
||||
this->uart_send_remaining_request_part_(this->active_command_);
|
||||
if (this->active_command_.is_ack_expected()) {
|
||||
this->active_command_.set_state(TelegramState::waitForCommandAck);
|
||||
} else {
|
||||
this->active_command_.set_state(TelegramState::endCompleted);
|
||||
this->lock_counter_ = this->max_lock_counter_;
|
||||
}
|
||||
} else if (Elf::get_priority_class(received_byte) == Elf::get_priority_class(this->active_command_.get_qq())) {
|
||||
// eligible for round 2
|
||||
this->active_command_.set_state(TelegramState::waitForArbitration2nd);
|
||||
} else {
|
||||
// lost arbitration, try again later if retries left
|
||||
this->active_command_.set_state(this->active_command_.can_retry(this->max_tries_)
|
||||
? TelegramState::waitForSend
|
||||
: TelegramState::endSendFailed);
|
||||
}
|
||||
break;
|
||||
case TelegramState::waitForArbitration2nd:
|
||||
if (received_byte == SYN) {
|
||||
this->uart_send_char_(this->active_command_.get_qq());
|
||||
} else if (received_byte == this->active_command_.get_qq()) {
|
||||
// won round 2
|
||||
this->uart_send_remaining_request_part_(this->active_command_);
|
||||
if (this->active_command_.is_ack_expected()) {
|
||||
this->active_command_.set_state(TelegramState::waitForCommandAck);
|
||||
} else {
|
||||
this->active_command_.set_state(TelegramState::endCompleted);
|
||||
this->lock_counter_ = this->max_lock_counter_;
|
||||
}
|
||||
} else {
|
||||
// try again later if retries left
|
||||
this->active_command_.set_state(this->active_command_.can_retry(this->max_tries_)
|
||||
? TelegramState::waitForSend
|
||||
: TelegramState::endSendFailed);
|
||||
}
|
||||
break;
|
||||
case TelegramState::waitForCommandAck:
|
||||
if (received_byte == ACK) {
|
||||
this->active_command_.set_state(TelegramState::endCompleted);
|
||||
this->lock_counter_ = this->max_lock_counter_;
|
||||
} else if (received_byte == SYN) { // timeout waiting for ACK signaled by AUTO-SYN
|
||||
this->active_command_.set_state(this->active_command_.can_retry(this->max_tries_)
|
||||
? TelegramState::waitForSend
|
||||
: TelegramState::endSendFailed);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// responses to our commands are stored in receiving_telegram_
|
||||
// when response is completed send ACK or NACK when we were the primary
|
||||
if (this->receiving_telegram_.get_state() == TelegramState::waitForResponseAck &&
|
||||
this->receiving_telegram_.get_qq() == this->primary_address_) {
|
||||
if (this->receiving_telegram_.is_response_valid()) {
|
||||
this->uart_send_char_(ACK);
|
||||
this->uart_send_char_(SYN, false);
|
||||
} else {
|
||||
this->uart_send_char_(NACK);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle our responses
|
||||
this->handle_response_(this->receiving_telegram_);
|
||||
}
|
||||
|
||||
void Ebus::add_send_response_handler(std::function<std::vector<uint8_t>(Telegram &)> send_response_handler) {
|
||||
send_response_handlers_.push_back(send_response_handler);
|
||||
}
|
||||
|
||||
void Ebus::handle_response_(Telegram &telegram) {
|
||||
if (telegram.get_state() != TelegramState::waitForRequestAck ||
|
||||
telegram.get_zz() != Elf::to_secondary(this->primary_address_)) {
|
||||
return;
|
||||
}
|
||||
if (!telegram.is_request_valid()) {
|
||||
uart_send_char_(NACK);
|
||||
return;
|
||||
}
|
||||
|
||||
// response buffer
|
||||
std::vector<uint8_t> reply;
|
||||
|
||||
// find response
|
||||
for (auto const &handler : send_response_handlers_) {
|
||||
reply = handler(telegram);
|
||||
if (reply.size() != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// we found no reponse to send
|
||||
if (reply.size() == 0 || reply.size() > RESPONSE_BUFFER_SIZE) {
|
||||
uart_send_char_(NACK);
|
||||
return;
|
||||
}
|
||||
|
||||
uart_send_char_(ACK);
|
||||
uint8_t crc = Elf::crc8_calc(reply.size(), 0);
|
||||
uart_send_char_(reply.size());
|
||||
for (int i = 0; i < reply.size(); i++) {
|
||||
crc = uart_send_char_(reply[i], true, true, crc);
|
||||
}
|
||||
uart_send_char_(crc);
|
||||
}
|
||||
|
||||
uint8_t Elf::crc8_calc(uint8_t data, uint8_t crc_init) {
|
||||
uint8_t crc;
|
||||
uint8_t polynom;
|
||||
|
||||
crc = crc_init;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (crc & 0x80) {
|
||||
polynom = 0x9B;
|
||||
} else {
|
||||
polynom = 0;
|
||||
}
|
||||
crc = ((crc & ~0x80) << 1);
|
||||
if (data & 0x80) {
|
||||
crc = (crc | 1);
|
||||
}
|
||||
crc = (crc ^ polynom);
|
||||
data = (data << 1);
|
||||
}
|
||||
return (crc);
|
||||
}
|
||||
|
||||
uint8_t Elf::crc8_array(uint8_t data[], uint8_t length) {
|
||||
uint8_t uc_crc = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
uc_crc = crc8_calc(data[i], uc_crc);
|
||||
}
|
||||
return (uc_crc);
|
||||
}
|
||||
|
||||
bool Elf::is_primary(uint8_t address) {
|
||||
return is_primary_nibble(get_priority_class(address)) && //
|
||||
is_primary_nibble(get_sub_address(address));
|
||||
}
|
||||
|
||||
int Elf::is_primary_nibble(uint8_t nibble) {
|
||||
switch (nibble) {
|
||||
case 0b0000:
|
||||
case 0b0001:
|
||||
case 0b0011:
|
||||
case 0b0111:
|
||||
case 0b1111:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Elf::get_priority_class(uint8_t address) { return (address & 0x0F); }
|
||||
|
||||
uint8_t Elf::get_sub_address(uint8_t address) { return (address >> 4); }
|
||||
|
||||
uint8_t Elf::to_secondary(uint8_t address) {
|
||||
if (is_primary(address)) {
|
||||
return (address + 5) % 0xFF;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
} // namespace ebus
|
||||
} // namespace esphome
|
|
@ -0,0 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include "telegram.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ebus {
|
||||
|
||||
class Elf {
|
||||
public:
|
||||
static uint8_t crc8_calc(uint8_t data, uint8_t crc_init);
|
||||
static uint8_t crc8_array(uint8_t data[], uint8_t length);
|
||||
static bool is_primary(uint8_t address);
|
||||
static int is_primary_nibble(uint8_t nibble);
|
||||
static uint8_t get_priority_class(uint8_t address);
|
||||
static uint8_t get_sub_address(uint8_t address);
|
||||
static uint8_t to_secondary(uint8_t address);
|
||||
};
|
||||
|
||||
class Ebus {
|
||||
public:
|
||||
void set_primary_address(uint8_t primary_address) { this->primary_address_ = primary_address; }
|
||||
void set_max_tries(uint8_t max_tries) { this->max_tries_ = max_tries; }
|
||||
void set_max_lock_counter(uint8_t max_lock_counter) { this->max_lock_counter_ = max_lock_counter; }
|
||||
void set_uart_send_function(std::function<void(const char *, int16_t)> uart_send) {
|
||||
this->uart_send_ = std::move(uart_send);
|
||||
}
|
||||
void set_queue_received_telegram_function(std::function<void(Telegram &telegram)> queue_received_telegram) {
|
||||
this->queue_received_telegram_ = std::move(queue_received_telegram);
|
||||
}
|
||||
void set_dequeue_command_function(const std::function<bool(void *const)> &dequeue_command) {
|
||||
this->dequeue_command_ = dequeue_command;
|
||||
}
|
||||
|
||||
void process_received_char(uint8_t received_byte);
|
||||
void add_send_response_handler(std::function<std::vector<uint8_t>(Telegram &)> send_response_handler);
|
||||
|
||||
protected:
|
||||
uint8_t primary_address_;
|
||||
uint8_t max_tries_;
|
||||
uint8_t max_lock_counter_;
|
||||
uint8_t lock_counter_ = 0;
|
||||
uint8_t char_count_since_last_syn_ = 0;
|
||||
EbusState state_ = EbusState::ARBITRATION;
|
||||
Telegram receiving_telegram_;
|
||||
SendCommand active_command_;
|
||||
std::list<std::function<std::vector<uint8_t>(Telegram &)>> send_response_handlers_;
|
||||
|
||||
std::function<void(const char *, int16_t)> uart_send_;
|
||||
std::function<void(Telegram &)> queue_received_telegram_;
|
||||
std::function<bool(void *const &)> dequeue_command_;
|
||||
uint8_t uart_send_char_(uint8_t cr, bool esc, bool run_crc, uint8_t crc_init);
|
||||
void uart_send_char_(uint8_t cr, bool esc = true);
|
||||
void uart_send_remaining_request_part_(SendCommand &command);
|
||||
void handle_response_(Telegram &telegram);
|
||||
};
|
||||
|
||||
} // namespace ebus
|
||||
} // namespace esphome
|
|
@ -0,0 +1,223 @@
|
|||
#ifndef USE_ESP8266
|
||||
|
||||
#include "ebus_component.h"
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ebus {
|
||||
|
||||
void EbusComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "EbusComponent");
|
||||
if (this->primary_address_ == SYN) {
|
||||
ESP_LOGCONFIG(TAG, " primary_addres: N/A");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " primary_addres: 0x%02x", this->primary_address_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " max_tries: %d", this->max_tries_);
|
||||
ESP_LOGCONFIG(TAG, " max_lock_counter: %d", this->max_lock_counter_);
|
||||
ESP_LOGCONFIG(TAG, " history_queue_size: %d", this->history_queue_size_);
|
||||
ESP_LOGCONFIG(TAG, " command_queue_size: %d", this->command_queue_size_);
|
||||
ESP_LOGCONFIG(TAG, " poll_interval (ms): %d", this->update_interval_);
|
||||
ESP_LOGCONFIG(TAG, " uart:");
|
||||
ESP_LOGCONFIG(TAG, " num: %d", this->uart_num_);
|
||||
ESP_LOGCONFIG(TAG, " tx_pin: %d", this->uart_tx_pin_);
|
||||
ESP_LOGCONFIG(TAG, " rx_pin: %d", this->uart_rx_pin_);
|
||||
}
|
||||
|
||||
void EbusComponent::setup() {
|
||||
this->setup_queues_();
|
||||
this->setup_ebus_();
|
||||
this->setup_uart_();
|
||||
this->setup_tasks_();
|
||||
}
|
||||
|
||||
void EbusComponent::set_primary_address(uint8_t primary_address) { this->primary_address_ = primary_address; }
|
||||
void EbusComponent::set_max_tries(uint8_t max_tries) { this->max_tries_ = max_tries; }
|
||||
void EbusComponent::set_max_lock_counter(uint8_t max_lock_counter) { this->max_lock_counter_ = max_lock_counter; }
|
||||
void EbusComponent::set_uart_num(uint8_t uart_num) { this->uart_num_ = uart_num; }
|
||||
void EbusComponent::set_uart_tx_pin(uint8_t uart_tx_pin) { this->uart_tx_pin_ = uart_tx_pin; }
|
||||
void EbusComponent::set_uart_rx_pin(uint8_t uart_rx_pin) { this->uart_rx_pin_ = uart_rx_pin; }
|
||||
void EbusComponent::set_history_queue_size(uint8_t history_queue_size) {
|
||||
this->history_queue_size_ = history_queue_size;
|
||||
}
|
||||
void EbusComponent::set_command_queue_size(uint8_t command_queue_size) {
|
||||
this->command_queue_size_ = command_queue_size;
|
||||
}
|
||||
|
||||
void EbusComponent::setup_queues_() {
|
||||
this->history_queue_ = xQueueCreate(this->history_queue_size_, sizeof(Telegram));
|
||||
this->command_queue_ = xQueueCreate(this->command_queue_size_, sizeof(Telegram));
|
||||
}
|
||||
|
||||
void EbusComponent::setup_ebus_() {
|
||||
this->ebus_ = make_unique<Ebus>();
|
||||
this->ebus_->set_primary_address(this->primary_address_);
|
||||
this->ebus_->set_max_tries(this->max_tries_);
|
||||
this->ebus_->set_max_lock_counter(this->max_lock_counter_);
|
||||
|
||||
this->ebus_->set_uart_send_function(
|
||||
[&](const char *buffer, int16_t length) { return uart_write_bytes(this->uart_num_, buffer, length); });
|
||||
|
||||
this->ebus_->set_queue_received_telegram_function([&](Telegram &telegram) {
|
||||
BaseType_t x_higher_priority_task_woken;
|
||||
x_higher_priority_task_woken = pdFALSE;
|
||||
xQueueSendToBackFromISR(this->history_queue_, &telegram, &x_higher_priority_task_woken);
|
||||
if (x_higher_priority_task_woken) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
});
|
||||
|
||||
this->ebus_->add_send_response_handler([&](Telegram &telegram) {
|
||||
std::vector<uint8_t> reply = {};
|
||||
for (auto const &item : this->items_) {
|
||||
reply = item->reply(telegram);
|
||||
if (reply.size() != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return reply;
|
||||
});
|
||||
|
||||
this->ebus_->set_dequeue_command_function([&](void *const command) {
|
||||
BaseType_t x_higher_priority_task_woken = pdFALSE;
|
||||
if (xQueueReceiveFromISR(this->command_queue_, command, &x_higher_priority_task_woken)) {
|
||||
if (x_higher_priority_task_woken) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void EbusComponent::setup_uart_() {
|
||||
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
portENTER_CRITICAL(&mux);
|
||||
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = 2400,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.rx_flow_ctrl_thresh = 2,
|
||||
.source_clk = UART_SCLK_REF_TICK,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(uart_param_config(this->uart_num_, &uart_config));
|
||||
|
||||
ESP_ERROR_CHECK(
|
||||
uart_set_pin(this->uart_num_, this->uart_tx_pin_, this->uart_rx_pin_, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
||||
|
||||
ESP_ERROR_CHECK(uart_driver_install(this->uart_num_, 256, 0, 0, nullptr, 0));
|
||||
|
||||
portEXIT_CRITICAL(&mux);
|
||||
}
|
||||
|
||||
void EbusComponent::setup_tasks_() {
|
||||
xTaskCreate(&process_received_bytes, "ebus_process_received_bytes", 2048, (void *) this, 10, nullptr);
|
||||
xTaskCreate(&process_received_messages, "ebus_process_received_messages", 2560, (void *) this, 5, nullptr);
|
||||
}
|
||||
|
||||
void EbusComponent::process_received_bytes(void *pv_parameter) {
|
||||
EbusComponent *instance = static_cast<EbusComponent *>(pv_parameter);
|
||||
|
||||
while (true) {
|
||||
uint8_t received_byte;
|
||||
int len = uart_read_bytes(instance->uart_num_, &received_byte, 1, 20 / portTICK_PERIOD_MS);
|
||||
if (len) {
|
||||
instance->ebus_->process_received_char(received_byte);
|
||||
taskYIELD();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EbusComponent::process_received_messages(void *pv_parameter) {
|
||||
EbusComponent *instance = static_cast<EbusComponent *>(pv_parameter);
|
||||
|
||||
Telegram telegram;
|
||||
while (true) {
|
||||
if (xQueueReceive(instance->history_queue_, &telegram, pdMS_TO_TICKS(1000))) {
|
||||
instance->handle_message_(telegram);
|
||||
// TODO: this comment is kept as reference on how to debug stack overflows. Could be generalized.
|
||||
// ESP_LOGD(TAG, "Task: %s, Stack Highwater Mark: %d", pcTaskGetTaskName(NULL),
|
||||
// uxTaskGetStackHighWaterMark(NULL));
|
||||
taskYIELD();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EbusComponent::handle_message_(Telegram &telegram) {
|
||||
if (telegram.get_state() != TelegramState::endCompleted) {
|
||||
ESP_LOGD(TAG, "Message received with invalid state: %s, QQ:%02X, ZZ:%02X, Command:%02X%02X",
|
||||
telegram.get_state_string(), telegram.get_qq(), telegram.get_zz(), telegram.get_pb(), telegram.get_sb());
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto const &item : this->items_) {
|
||||
item->process_received(telegram);
|
||||
}
|
||||
}
|
||||
|
||||
void EbusComponent::update() {
|
||||
if (this->primary_address_ == SYN) {
|
||||
return;
|
||||
}
|
||||
for (auto const &item : this->items_) {
|
||||
optional<SendCommand> command = item->prepare_command();
|
||||
if (command.has_value()) {
|
||||
xQueueSendToBack(this->command_queue_, &command.value(), portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EbusItem::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "EbusSensor");
|
||||
ESP_LOGCONFIG(TAG, " message:");
|
||||
ESP_LOGCONFIG(TAG, " send_poll: %s", this->send_poll_ ? "true" : "false");
|
||||
if (this->address_ == SYN) {
|
||||
ESP_LOGCONFIG(TAG, " address: N/A");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " address: 0x%02x", this->address_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " command: 0x%04x", this->command_);
|
||||
};
|
||||
|
||||
optional<SendCommand> EbusItem::prepare_command() {
|
||||
optional<SendCommand> command;
|
||||
|
||||
if (this->send_poll_) {
|
||||
command = SendCommand( //
|
||||
this->parent_->get_primary_address(), this->address_, this->command_, this->payload_);
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
uint32_t EbusItem::get_response_bytes(Telegram &telegram, uint8_t start, uint8_t length) {
|
||||
uint32_t result = 0;
|
||||
for (uint8_t i = 0; i < 4 && i < length; i++) {
|
||||
result = result | (telegram.get_response_byte(start + i) << (i * 8));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool EbusItem::is_mine(Telegram &telegram) {
|
||||
if (this->address_ != SYN && this->address_ != telegram.get_zz()) {
|
||||
return false;
|
||||
}
|
||||
if (telegram.get_command() != this->command_) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < this->payload_.size(); i++) {
|
||||
if (this->payload_[i] != telegram.get_request_byte(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ebus
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP8266
|
|
@ -0,0 +1,105 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include "ebus.h"
|
||||
|
||||
#ifndef USE_ESP8266
|
||||
#include <driver/uart.h>
|
||||
#include <soc/uart_reg.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace ebus {
|
||||
|
||||
static const char *const TAG = "ebus";
|
||||
|
||||
class EbusComponent;
|
||||
|
||||
class EbusItem : public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
|
||||
void set_parent(EbusComponent *parent) { this->parent_ = parent; }
|
||||
void set_address(uint8_t address) { this->address_ = Elf::to_secondary(address); }
|
||||
void set_send_poll(bool send_poll) { this->send_poll_ = send_poll; }
|
||||
void set_command(uint16_t command) { this->command_ = command; }
|
||||
void set_payload(const std::vector<uint8_t> &payload) { this->payload_ = payload; }
|
||||
void set_response_read_position(uint8_t response_position) { this->response_position_ = response_position; }
|
||||
|
||||
virtual void process_received(Telegram) {}
|
||||
virtual std::vector<uint8_t> reply(Telegram telegram) {
|
||||
std::vector<uint8_t> reply = {0xe3, 'E', 'S', 'P', 'H', 'M', 0x12, 0x34, 0x56, 0x78};
|
||||
return reply;
|
||||
};
|
||||
virtual optional<SendCommand> prepare_command();
|
||||
|
||||
// TODO: refactor these
|
||||
uint32_t get_response_bytes(Telegram &telegram, uint8_t start, uint8_t length);
|
||||
bool is_mine(Telegram &telegram);
|
||||
|
||||
protected:
|
||||
EbusComponent *parent_;
|
||||
uint8_t address_ = SYN;
|
||||
bool send_poll_;
|
||||
uint16_t command_;
|
||||
std::vector<uint8_t> payload_{};
|
||||
uint8_t response_position_;
|
||||
};
|
||||
|
||||
class EbusComponent : public PollingComponent {
|
||||
public:
|
||||
EbusComponent() {}
|
||||
|
||||
void dump_config() override;
|
||||
void setup() override;
|
||||
|
||||
void set_primary_address(uint8_t /*primary_address*/);
|
||||
uint8_t get_primary_address() { return this->primary_address_; }
|
||||
void set_max_tries(uint8_t /*max_tries*/);
|
||||
void set_max_lock_counter(uint8_t /*max_lock_counter*/);
|
||||
void set_uart_num(uint8_t /*uart_num*/);
|
||||
void set_uart_tx_pin(uint8_t /*uart_tx_pin*/);
|
||||
void set_uart_rx_pin(uint8_t /*uart_rx_pin*/);
|
||||
void set_history_queue_size(uint8_t /*history_queue_size*/);
|
||||
void set_command_queue_size(uint8_t /*command_queue_size*/);
|
||||
|
||||
void add_item(EbusItem *item) { this->items_.push_back(item); };
|
||||
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
uint8_t primary_address_;
|
||||
uint8_t max_tries_;
|
||||
uint8_t max_lock_counter_;
|
||||
uint8_t history_queue_size_;
|
||||
uint8_t command_queue_size_;
|
||||
uint8_t uart_num_;
|
||||
uint8_t uart_tx_pin_;
|
||||
uint8_t uart_rx_pin_;
|
||||
|
||||
#ifndef USE_ESP8266
|
||||
QueueHandle_t history_queue_;
|
||||
QueueHandle_t command_queue_;
|
||||
#endif
|
||||
|
||||
std::list<EbusItem *> items_;
|
||||
|
||||
std::unique_ptr<Ebus> ebus_;
|
||||
|
||||
void setup_queues_();
|
||||
void setup_ebus_();
|
||||
void setup_uart_();
|
||||
void setup_tasks_();
|
||||
|
||||
static void process_received_bytes(void * /*pvParameter*/);
|
||||
static void process_received_messages(void * /*pvParameter*/);
|
||||
void handle_message_(Telegram & /*telegram*/);
|
||||
};
|
||||
|
||||
} // namespace ebus
|
||||
} // namespace esphome
|
|
@ -0,0 +1,45 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
|
||||
from esphome.const import (
|
||||
CONF_BYTES,
|
||||
)
|
||||
|
||||
from .. import (
|
||||
CONF_EBUS_ID,
|
||||
CONF_TELEGRAM,
|
||||
CONF_DECODE,
|
||||
ebus_ns,
|
||||
create_telegram_schema,
|
||||
item_config,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["ebus"]
|
||||
|
||||
EbusSensor = ebus_ns.class_("EbusSensor", sensor.Sensor, cg.Component)
|
||||
|
||||
CONF_DIVIDER = "divider"
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(EbusSensor).extend(
|
||||
create_telegram_schema(
|
||||
{
|
||||
cv.Required(CONF_BYTES): cv.int_range(1, 4),
|
||||
cv.Required(CONF_DIVIDER): cv.float_,
|
||||
}
|
||||
)
|
||||
)
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
ebus = await cg.get_variable(config[CONF_EBUS_ID])
|
||||
sens = await sensor.new_sensor(config)
|
||||
|
||||
item_config(ebus, sens, config)
|
||||
|
||||
cg.add(sens.set_response_read_bytes(config[CONF_TELEGRAM][CONF_DECODE][CONF_BYTES]))
|
||||
cg.add(
|
||||
sens.set_response_read_divider(config[CONF_TELEGRAM][CONF_DECODE][CONF_DIVIDER])
|
||||
)
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
#include "ebus_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ebus {
|
||||
|
||||
void EbusSensor::process_received(Telegram telegram) {
|
||||
if (!is_mine(telegram)) {
|
||||
return;
|
||||
}
|
||||
this->publish_state(to_float(telegram, this->response_position_, this->response_bytes_, this->response_divider_));
|
||||
}
|
||||
|
||||
float EbusSensor::to_float(Telegram &telegram, uint8_t start, uint8_t length, float divider) {
|
||||
return get_response_bytes(telegram, start, length) / divider;
|
||||
}
|
||||
|
||||
} // namespace ebus
|
||||
} // namespace esphome
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include "../ebus_component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ebus {
|
||||
|
||||
class EbusSensor : public EbusItem, public sensor::Sensor {
|
||||
public:
|
||||
EbusSensor() {}
|
||||
|
||||
void set_response_read_bytes(uint8_t response_bytes) { this->response_bytes_ = response_bytes; }
|
||||
void set_response_read_divider(float response_divider) { this->response_divider_ = response_divider; }
|
||||
|
||||
void process_received(Telegram /*telegram*/) override;
|
||||
|
||||
float to_float(Telegram &telegram, uint8_t start, uint8_t length, float divider);
|
||||
|
||||
protected:
|
||||
uint8_t response_bytes_;
|
||||
float response_divider_;
|
||||
};
|
||||
|
||||
} // namespace ebus
|
||||
} // namespace esphome
|
|
@ -0,0 +1,124 @@
|
|||
#include "ebus.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ebus {
|
||||
|
||||
TelegramBase::TelegramBase() {}
|
||||
|
||||
void TelegramBase::set_state(TelegramState new_state) { this->state_ = new_state; }
|
||||
|
||||
TelegramState TelegramBase::get_state() { return this->state_; }
|
||||
|
||||
#define X(name, int) \
|
||||
case int: \
|
||||
return "" #name "";
|
||||
const char *TelegramBase::get_state_string() {
|
||||
switch ((int8_t) this->state_) {
|
||||
TELEGRAM_STATE_TABLE
|
||||
default:
|
||||
return "[INVALID STATE]";
|
||||
}
|
||||
}
|
||||
#undef X
|
||||
|
||||
void TelegramBase::push_buffer_(uint8_t cr, uint8_t *buffer, uint8_t *pos, uint8_t *crc, int max_pos) {
|
||||
if (*pos < max_pos) {
|
||||
*crc = Elf::crc8_calc(cr, *crc);
|
||||
}
|
||||
if (this->wait_for_escaped_char_) {
|
||||
buffer[(*pos)] = (cr == 0x0 ? ESC : SYN);
|
||||
this->wait_for_escaped_char_ = false;
|
||||
} else {
|
||||
buffer[(*pos)++] = cr;
|
||||
this->wait_for_escaped_char_ = (cr == ESC);
|
||||
}
|
||||
}
|
||||
|
||||
TelegramType TelegramBase::get_type() {
|
||||
if (this->get_zz() == ESC) {
|
||||
return TelegramType::UNKNOWN;
|
||||
}
|
||||
if (this->get_zz() == BROADCAST_ADDRESS) {
|
||||
return TelegramType::BROADCAST;
|
||||
}
|
||||
if (Elf::is_primary(this->get_zz())) {
|
||||
return TelegramType::PRIMARY_PRIMARY;
|
||||
}
|
||||
return TelegramType::PRIMARY_SECONDARY;
|
||||
}
|
||||
|
||||
int16_t TelegramBase::get_request_byte(uint8_t pos) {
|
||||
if (pos > this->get_nn() || pos >= MAX_DATA_LENGTH) {
|
||||
return -1;
|
||||
}
|
||||
return this->request_buffer_[OFFSET_DATA + pos];
|
||||
}
|
||||
|
||||
uint8_t TelegramBase::get_request_crc() { return this->request_buffer_[OFFSET_DATA + this->get_nn()]; }
|
||||
|
||||
void TelegramBase::push_req_data(uint8_t cr) {
|
||||
this->push_buffer_(cr, request_buffer_, &request_buffer_pos_, &request_rolling_crc_, OFFSET_DATA + get_nn());
|
||||
}
|
||||
|
||||
bool TelegramBase::is_ack_expected() { return (this->get_type() != TelegramType::BROADCAST); }
|
||||
|
||||
bool TelegramBase::is_response_expected() { return (this->get_type() == TelegramType::PRIMARY_SECONDARY); }
|
||||
|
||||
bool TelegramBase::is_finished() { return this->state_ < TelegramState::unknown; }
|
||||
|
||||
Telegram::Telegram() { this->state_ = TelegramState::waitForSyn; }
|
||||
|
||||
int16_t Telegram::get_response_byte(uint8_t pos) {
|
||||
if (pos > this->get_response_nn() || pos >= MAX_DATA_LENGTH) {
|
||||
return INVALID_RESPONSE_BYTE;
|
||||
}
|
||||
return this->response_buffer_[RESPONSE_OFFSET + pos];
|
||||
}
|
||||
|
||||
uint8_t Telegram::get_response_crc() { return this->response_buffer_[RESPONSE_OFFSET + this->get_response_nn()]; }
|
||||
|
||||
void Telegram::push_response_data(uint8_t cr) {
|
||||
this->push_buffer_(cr, response_buffer_, &response_buffer_pos_, &response_rolling_crc_,
|
||||
RESPONSE_OFFSET + get_response_nn());
|
||||
}
|
||||
|
||||
bool Telegram::is_response_complete() {
|
||||
return (this->state_ > TelegramState::waitForSyn || this->state_ == TelegramState::endCompleted) &&
|
||||
(this->response_buffer_pos_ > RESPONSE_OFFSET) &&
|
||||
(this->response_buffer_pos_ == (RESPONSE_OFFSET + this->get_response_nn() + 1)) &&
|
||||
!this->wait_for_escaped_char_;
|
||||
}
|
||||
|
||||
bool Telegram::is_response_valid() {
|
||||
return this->is_response_complete() && this->get_response_crc() == this->response_rolling_crc_;
|
||||
}
|
||||
|
||||
bool Telegram::is_request_complete() {
|
||||
return (this->state_ > TelegramState::waitForSyn || this->state_ == TelegramState::endCompleted) &&
|
||||
(this->request_buffer_pos_ > OFFSET_DATA) &&
|
||||
(this->request_buffer_pos_ == (OFFSET_DATA + this->get_nn() + 1)) && !this->wait_for_escaped_char_;
|
||||
}
|
||||
bool Telegram::is_request_valid() {
|
||||
return this->is_request_complete() && this->get_request_crc() == this->request_rolling_crc_;
|
||||
}
|
||||
|
||||
SendCommand::SendCommand() { this->state_ = TelegramState::endCompleted; }
|
||||
|
||||
SendCommand::SendCommand(uint8_t qq, uint8_t zz, uint16_t command, std::vector<uint8_t> &data) {
|
||||
this->state_ = TelegramState::waitForSend;
|
||||
this->push_req_data(qq);
|
||||
this->push_req_data(zz);
|
||||
this->push_req_data(command >> 8);
|
||||
this->push_req_data(command & 0xFF);
|
||||
this->push_req_data(data.size());
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
this->push_req_data(data.at(i));
|
||||
}
|
||||
this->push_req_data(this->request_rolling_crc_);
|
||||
}
|
||||
bool SendCommand::can_retry(int8_t max_tries) { return this->tries_count_++ < max_tries; }
|
||||
|
||||
uint8_t SendCommand::get_crc() { return this->request_rolling_crc_; }
|
||||
|
||||
} // namespace ebus
|
||||
} // namespace esphome
|
|
@ -0,0 +1,144 @@
|
|||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
namespace ebus {
|
||||
|
||||
enum EbusState : int8_t {
|
||||
NORMAL,
|
||||
ARBITRATION,
|
||||
};
|
||||
|
||||
enum TelegramType : int8_t {
|
||||
UNKNOWN = -1,
|
||||
BROADCAST = 0,
|
||||
PRIMARY_PRIMARY = 1,
|
||||
PRIMARY_SECONDARY = 2,
|
||||
};
|
||||
|
||||
#define TELEGRAM_STATE_TABLE \
|
||||
X(waitForSyn, 1) \
|
||||
X(waitForSend, 2) \
|
||||
X(waitForRequestData, 3) \
|
||||
X(waitForRequestAck, 4) \
|
||||
X(waitForResponseData, 5) \
|
||||
X(waitForResponseAck, 6) \
|
||||
X(waitForArbitration, 7) \
|
||||
X(waitForArbitration2nd, 8) \
|
||||
X(waitForCommandAck, 9) \
|
||||
X(unknown, 0) \
|
||||
X(endErrorUnexpectedSyn, -1) \
|
||||
X(endErrorRequestNackReceived, -2) \
|
||||
X(endErrorResponseNackReceived, -3) \
|
||||
X(endErrorResponseNoAck, -4) \
|
||||
X(endErrorRequestNoAck, -5) \
|
||||
X(endArbitration, -6) \
|
||||
X(endCompleted, -16) \
|
||||
X(endSendFailed, -17)
|
||||
|
||||
#define X(name, int) name = int,
|
||||
enum TelegramState : int8_t { TELEGRAM_STATE_TABLE };
|
||||
#undef X
|
||||
|
||||
const uint8_t SYN = 0xAA;
|
||||
const uint8_t ESC = 0xA9;
|
||||
const uint8_t ACK = 0x00;
|
||||
const uint8_t NACK = 0xFF;
|
||||
|
||||
const uint8_t BROADCAST_ADDRESS = 0xFE;
|
||||
|
||||
/* Specification says:
|
||||
1. In primary and secondary telegram part, standardised commands must be limited to 10 used data bytes.
|
||||
2. In primary and secondary telegram part, the sum of mfr.-specific telegram used data bytes must not exceed 14.
|
||||
We use 16 to be on the safe side for now.
|
||||
*/
|
||||
const uint8_t MAX_DATA_LENGTH = 16;
|
||||
const uint8_t OFFSET_QQ = 0;
|
||||
const uint8_t OFFSET_ZZ = 1;
|
||||
const uint8_t OFFSET_PB = 2;
|
||||
const uint8_t OFFSET_SB = 3;
|
||||
const uint8_t OFFSET_NN = 4;
|
||||
const uint8_t OFFSET_DATA = 5;
|
||||
const uint8_t REQUEST_BUFFER_SIZE = (OFFSET_DATA + MAX_DATA_LENGTH + 1);
|
||||
const uint8_t RESPONSE_BUFFER_SIZE = (MAX_DATA_LENGTH + 2);
|
||||
const uint8_t RESPONSE_OFFSET = 1;
|
||||
const uint8_t INVALID_RESPONSE_BYTE = -1;
|
||||
|
||||
class TelegramBase {
|
||||
public:
|
||||
TelegramBase();
|
||||
|
||||
uint8_t get_qq() { return this->request_buffer_[OFFSET_QQ]; }
|
||||
uint8_t get_zz() { return this->request_buffer_[OFFSET_ZZ]; }
|
||||
uint8_t get_pb() { return this->request_buffer_[OFFSET_PB]; }
|
||||
uint8_t get_sb() { return this->request_buffer_[OFFSET_SB]; }
|
||||
uint16_t get_command() { return ((uint16_t) get_pb()) << 8 | get_sb(); }
|
||||
uint8_t get_nn() {
|
||||
uint8_t nn = this->request_buffer_[OFFSET_NN];
|
||||
if (nn >= MAX_DATA_LENGTH) {
|
||||
return 0;
|
||||
}
|
||||
return nn;
|
||||
}
|
||||
|
||||
void set_state(TelegramState new_state);
|
||||
TelegramState get_state();
|
||||
const char *get_state_string();
|
||||
|
||||
TelegramType get_type();
|
||||
int16_t get_request_byte(uint8_t pos);
|
||||
uint8_t get_request_crc();
|
||||
void push_req_data(uint8_t cr);
|
||||
bool is_ack_expected();
|
||||
bool is_response_expected();
|
||||
bool is_finished();
|
||||
|
||||
protected:
|
||||
TelegramState state_;
|
||||
uint8_t request_buffer_[REQUEST_BUFFER_SIZE] = {
|
||||
ESC, ESC}; // initialize QQ and ZZ with ESC char to distinguish from valid primary 0
|
||||
uint8_t request_buffer_pos_ = 0;
|
||||
uint8_t request_rolling_crc_ = 0;
|
||||
bool wait_for_escaped_char_ = false;
|
||||
void push_buffer_(uint8_t cr, uint8_t *buffer, uint8_t *pos, uint8_t *crc, int max_pos);
|
||||
};
|
||||
|
||||
class Telegram : public TelegramBase {
|
||||
public:
|
||||
Telegram();
|
||||
|
||||
uint8_t get_response_nn() {
|
||||
uint8_t nn = response_buffer_[0];
|
||||
if (nn >= MAX_DATA_LENGTH) {
|
||||
return 0;
|
||||
}
|
||||
return nn;
|
||||
}
|
||||
|
||||
int16_t get_response_byte(uint8_t pos);
|
||||
uint8_t get_response_crc();
|
||||
|
||||
void push_response_data(uint8_t cr);
|
||||
bool is_response_complete();
|
||||
bool is_response_valid();
|
||||
bool is_request_complete();
|
||||
bool is_request_valid();
|
||||
|
||||
protected:
|
||||
uint8_t response_buffer_[RESPONSE_BUFFER_SIZE] = {0};
|
||||
uint8_t response_buffer_pos_ = 0;
|
||||
uint8_t response_rolling_crc_ = 0;
|
||||
};
|
||||
|
||||
class SendCommand : public TelegramBase {
|
||||
public:
|
||||
SendCommand();
|
||||
SendCommand(uint8_t qq, uint8_t zz, uint16_t command, std::vector<uint8_t> &data);
|
||||
bool can_retry(int8_t max_tries);
|
||||
uint8_t get_crc();
|
||||
|
||||
protected:
|
||||
uint8_t tries_count_ = 0;
|
||||
};
|
||||
|
||||
} // namespace ebus
|
||||
} // namespace esphome
|
|
@ -0,0 +1,50 @@
|
|||
ebus:
|
||||
primary_address: 0x00
|
||||
history_queue_size: 32
|
||||
command_queue_size: 16
|
||||
poll_interval: "60s"
|
||||
uart:
|
||||
num: 1
|
||||
tx_pin: 33
|
||||
rx_pin: 32
|
||||
|
||||
sensor:
|
||||
- platform: ebus
|
||||
name: "Water Pressure"
|
||||
id: zebus_water_pressure
|
||||
telegram:
|
||||
send_poll: true
|
||||
address: 0x03
|
||||
command: 0xb509
|
||||
payload: [0x0d, 0x02, 0x00]
|
||||
decode:
|
||||
position: 0
|
||||
bytes: 2
|
||||
divider: 1000.0
|
||||
device_class: "pressure"
|
||||
unit_of_measurement: bar
|
||||
state_class: "measurement"
|
||||
accuracy_decimals: 3
|
||||
internal: false
|
||||
filters:
|
||||
- clamp:
|
||||
min_value: 0
|
||||
max_value: 4
|
||||
ignore_out_of_range: true
|
||||
- median:
|
||||
window_size: 5
|
||||
send_every: 3
|
||||
send_first_at: 3
|
||||
|
||||
binary_sensor:
|
||||
- platform: ebus
|
||||
name: "Thermostat in Quick Veto"
|
||||
id: zebus_in_quick_veto
|
||||
telegram:
|
||||
send_poll: true
|
||||
address: 0x10
|
||||
command: 0xb509
|
||||
payload: [0x0d, 0x16, 0x00]
|
||||
decode:
|
||||
position: 0
|
||||
mask: 0x01
|
Loading…
Reference in New Issue