mirror of
https://github.com/esphome/esphome.git
synced 2024-11-10 10:00:36 +01:00
Add boolean sensor
This commit is contained in:
parent
6fe6776d38
commit
dfb0defb60
@ -6,12 +6,14 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_TX_PIN,
|
||||
CONF_RX_PIN,
|
||||
CONF_ADDRESS,
|
||||
CONF_COMMAND,
|
||||
CONF_PAYLOAD,
|
||||
CONF_POSITION,
|
||||
)
|
||||
|
||||
|
||||
# TODO: make ebus primary address optional for only listening on the bus
|
||||
# TODO: send identification response ewhen requested
|
||||
# TODO: add binary_sensor
|
||||
# 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
|
||||
|
||||
@ -29,6 +31,21 @@ 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}
|
||||
@ -44,6 +61,29 @@ def validate_primary_address(value):
|
||||
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(
|
||||
{
|
||||
@ -80,3 +120,16 @@ async def to_code(config):
|
||||
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 sensor_base_config(sensor_base, config):
|
||||
cg.add(sensor_base.set_send_poll(config[CONF_TELEGRAM][CONF_SEND_POLL]))
|
||||
if CONF_ADDRESS in config[CONF_TELEGRAM]:
|
||||
cg.add(sensor_base.set_address(config[CONF_TELEGRAM][CONF_ADDRESS]))
|
||||
cg.add(sensor_base.set_command(config[CONF_TELEGRAM][CONF_COMMAND]))
|
||||
cg.add(sensor_base.set_payload(config[CONF_TELEGRAM][CONF_PAYLOAD]))
|
||||
cg.add(
|
||||
sensor_base.set_response_read_position(
|
||||
config[CONF_TELEGRAM][CONF_DECODE][CONF_POSITION]
|
||||
)
|
||||
)
|
||||
|
@ -26,8 +26,13 @@ class EbusReceiver {
|
||||
class EbusSender {
|
||||
public:
|
||||
EbusSender() {}
|
||||
virtual void set_primary_address(uint8_t) = 0;
|
||||
void set_primary_address(uint8_t primary_address) { this->primary_address_ = primary_address; }
|
||||
void set_address(uint8_t address) { this->address_ = Elf::to_secondary(address); }
|
||||
virtual optional<SendCommand> prepare_command() = 0;
|
||||
|
||||
protected:
|
||||
uint8_t primary_address_;
|
||||
uint8_t address_ = SYN;
|
||||
};
|
||||
|
||||
class EbusComponent : public PollingComponent {
|
||||
@ -52,7 +57,7 @@ class EbusComponent : public PollingComponent {
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
uint8_t primary_address_ = SYN;
|
||||
uint8_t primary_address_;
|
||||
uint8_t max_tries_;
|
||||
uint8_t max_lock_counter_;
|
||||
uint8_t history_queue_size_;
|
||||
|
@ -1,100 +1,47 @@
|
||||
import voluptuous as vol
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
|
||||
from esphome.const import (
|
||||
CONF_SENSORS,
|
||||
CONF_SOURCE,
|
||||
CONF_COMMAND,
|
||||
CONF_PAYLOAD,
|
||||
CONF_POSITION,
|
||||
CONF_BYTES,
|
||||
)
|
||||
|
||||
from .. import EbusComponent, CONF_EBUS_ID, ebus_ns
|
||||
from .. import (
|
||||
CONF_EBUS_ID,
|
||||
CONF_TELEGRAM,
|
||||
CONF_DECODE,
|
||||
ebus_ns,
|
||||
create_telegram_schema,
|
||||
sensor_base_config,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["ebus"]
|
||||
|
||||
EbusSensor = ebus_ns.class_("EbusSensor", sensor.Sensor, cg.Component)
|
||||
|
||||
CONF_TELEGRAM = "telegram"
|
||||
CONF_SEND_POLL = "send_poll"
|
||||
CONF_ADDRESS = "address"
|
||||
CONF_DECODE = "decode"
|
||||
CONF_DIVIDER = "divider"
|
||||
|
||||
|
||||
SYN = 0xAA
|
||||
ESC = 0xA9
|
||||
|
||||
|
||||
def validate_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)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EbusSensor),
|
||||
cv.GenerateID(CONF_EBUS_ID): cv.use_id(EbusComponent),
|
||||
cv.Required(CONF_SENSORS): cv.ensure_list(
|
||||
sensor.sensor_schema().extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EbusSensor),
|
||||
cv.Required(CONF_TELEGRAM): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_SEND_POLL, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ADDRESS): validate_address,
|
||||
cv.Required(CONF_COMMAND): cv.hex_uint16_t,
|
||||
cv.Required(CONF_PAYLOAD): cv.Schema([cv.hex_uint8_t]),
|
||||
cv.Optional(CONF_DECODE): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_POSITION, default=0): cv.int_range(
|
||||
0, 15
|
||||
),
|
||||
cv.Required(CONF_BYTES): cv.int_range(1, 4),
|
||||
cv.Required(CONF_DIVIDER): cv.float_,
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
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)
|
||||
|
||||
for i, conf in enumerate(config[CONF_SENSORS]):
|
||||
print(f"Sensor: {i}, {conf}")
|
||||
sens = await sensor.new_sensor(conf)
|
||||
if CONF_SOURCE in conf[CONF_TELEGRAM]:
|
||||
cg.add(sens.set_source(conf[CONF_TELEGRAM][CONF_SOURCE]))
|
||||
sensor_base_config(sens, config)
|
||||
|
||||
if CONF_ADDRESS in conf[CONF_TELEGRAM]:
|
||||
cg.add(sens.set_address(conf[CONF_TELEGRAM][CONF_ADDRESS]))
|
||||
cg.add(sens.set_command(conf[CONF_TELEGRAM][CONF_COMMAND]))
|
||||
cg.add(sens.set_payload(conf[CONF_TELEGRAM][CONF_PAYLOAD]))
|
||||
cg.add(
|
||||
sens.set_response_read_position(
|
||||
conf[CONF_TELEGRAM][CONF_DECODE][CONF_POSITION]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
sens.set_response_read_bytes(conf[CONF_TELEGRAM][CONF_DECODE][CONF_BYTES])
|
||||
)
|
||||
cg.add(
|
||||
sens.set_response_read_divider(
|
||||
conf[CONF_TELEGRAM][CONF_DECODE][CONF_DIVIDER]
|
||||
)
|
||||
)
|
||||
|
||||
cg.add(ebus.add_receiver(sens))
|
||||
cg.add(ebus.add_sender(sens))
|
||||
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])
|
||||
)
|
||||
cg.add(ebus.add_receiver(sens))
|
||||
cg.add(ebus.add_sender(sens))
|
||||
|
@ -1,43 +1,9 @@
|
||||
|
||||
#include "ebus_sensor.h"
|
||||
|
||||
// TODO: remove
|
||||
#define GET_BYTE(CMD, I) ((uint8_t) (((CMD) >> 8 * (I)) & 0XFF))
|
||||
|
||||
namespace esphome {
|
||||
namespace ebus {
|
||||
|
||||
void EbusSensor::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_);
|
||||
};
|
||||
|
||||
void EbusSensor::set_primary_address(uint8_t primary_address) { this->primary_address_ = primary_address; }
|
||||
void EbusSensor::set_send_poll(bool send_poll) { this->send_poll_ = send_poll; }
|
||||
void EbusSensor::set_address(uint8_t address) { this->address_ = Elf::to_secondary(address); }
|
||||
void EbusSensor::set_command(uint16_t command) { this->command_ = command; }
|
||||
void EbusSensor::set_payload(const std::vector<uint8_t> &payload) { this->payload_ = payload; }
|
||||
void EbusSensor::set_response_read_position(uint8_t response_position) { this->response_position_ = response_position; }
|
||||
void EbusSensor::set_response_read_bytes(uint8_t response_bytes) { this->response_bytes_ = response_bytes; }
|
||||
void EbusSensor::set_response_read_divider(float response_divider) { this->response_divider_ = response_divider; }
|
||||
|
||||
optional<SendCommand> EbusSensor::prepare_command() {
|
||||
optional<SendCommand> command;
|
||||
if (this->send_poll_) {
|
||||
command = SendCommand( //
|
||||
this->primary_address_, this->address_, GET_BYTE(this->command_, 1), GET_BYTE(this->command_, 0),
|
||||
this->payload_.size(), &this->payload_[0]);
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
void EbusSensor::process_received(Telegram telegram) {
|
||||
if (!is_mine(telegram)) {
|
||||
return;
|
||||
@ -45,32 +11,9 @@ void EbusSensor::process_received(Telegram telegram) {
|
||||
this->publish_state(to_float(telegram, this->response_position_, this->response_bytes_, this->response_divider_));
|
||||
}
|
||||
|
||||
uint32_t EbusSensor::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;
|
||||
}
|
||||
|
||||
float EbusSensor::to_float(Telegram &telegram, uint8_t start, uint8_t length, float divider) {
|
||||
return get_response_bytes(telegram, start, length) / divider;
|
||||
}
|
||||
|
||||
bool EbusSensor::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
|
||||
|
@ -1,42 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ebus_component.h"
|
||||
#include "../ebus_sensor_base.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ebus {
|
||||
|
||||
class EbusSensor : public EbusReceiver, public EbusSender, public sensor::Sensor, public Component {
|
||||
class EbusSensor : public EbusSensorBase, public sensor::Sensor {
|
||||
public:
|
||||
EbusSensor() {}
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void set_primary_address(uint8_t /*primary_address*/) override;
|
||||
void set_send_poll(bool /*send_poll*/);
|
||||
void set_address(uint8_t /*address*/);
|
||||
void set_command(uint16_t /*command*/);
|
||||
void set_payload(const std::vector<uint8_t> & /*payload*/);
|
||||
|
||||
void set_response_read_position(uint8_t /*response_position*/);
|
||||
void set_response_read_bytes(uint8_t /*response_bytes*/);
|
||||
void set_response_read_divider(float /*response_divider*/);
|
||||
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;
|
||||
optional<SendCommand> prepare_command() override;
|
||||
|
||||
// TODO: refactor these
|
||||
uint32_t get_response_bytes(Telegram &telegram, uint8_t start, uint8_t length);
|
||||
float to_float(Telegram &telegram, uint8_t start, uint8_t length, float divider);
|
||||
bool is_mine(Telegram &telegram);
|
||||
|
||||
protected:
|
||||
uint8_t primary_address_;
|
||||
bool send_poll_;
|
||||
uint8_t address_ = SYN;
|
||||
uint16_t command_;
|
||||
std::vector<uint8_t> payload_{};
|
||||
uint8_t response_position_;
|
||||
uint8_t response_bytes_;
|
||||
float response_divider_;
|
||||
};
|
||||
|
@ -104,19 +104,18 @@ bool Telegram::is_request_valid() {
|
||||
|
||||
SendCommand::SendCommand() { this->state_ = TelegramState::endCompleted; }
|
||||
|
||||
SendCommand::SendCommand(uint8_t qq, uint8_t zz, uint8_t pb, uint8_t sb, uint8_t nn, uint8_t *data) {
|
||||
SendCommand::SendCommand(uint8_t qq, uint8_t zz, uint16_t command, uint8_t nn, uint8_t *data) {
|
||||
this->state_ = TelegramState::waitForSend;
|
||||
this->push_req_data(qq);
|
||||
this->push_req_data(zz);
|
||||
this->push_req_data(pb);
|
||||
this->push_req_data(sb);
|
||||
this->push_req_data(command >> 8);
|
||||
this->push_req_data(command & 0xFF);
|
||||
this->push_req_data(nn);
|
||||
for (int i = 0; i < nn; i++) {
|
||||
this->push_req_data(data[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_; }
|
||||
|
@ -132,7 +132,7 @@ class Telegram : public TelegramBase {
|
||||
class SendCommand : public TelegramBase {
|
||||
public:
|
||||
SendCommand();
|
||||
SendCommand(uint8_t qq, uint8_t zz, uint8_t pb, uint8_t sb, uint8_t nn, uint8_t *data);
|
||||
SendCommand(uint8_t qq, uint8_t zz, uint16_t command, uint8_t nn, uint8_t *data);
|
||||
bool can_retry(int8_t max_tries);
|
||||
uint8_t get_crc();
|
||||
|
||||
|
@ -2,7 +2,7 @@ ebus:
|
||||
primary_address: 0x00
|
||||
history_queue_size: 32
|
||||
command_queue_size: 16
|
||||
poll_interval: "15s"
|
||||
poll_interval: "60s"
|
||||
uart:
|
||||
num: 1
|
||||
tx_pin: 33
|
||||
@ -10,28 +10,41 @@ ebus:
|
||||
|
||||
sensor:
|
||||
- platform: ebus
|
||||
sensors:
|
||||
- name: "Water Pressure"
|
||||
id: zebus_water_pressure
|
||||
telegram:
|
||||
destination: 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
|
||||
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
Block a user