10static constexpr uint8_t HEADER_START = 0xAA;
11static constexpr uint8_t HEADER_END = 0x55;
12static constexpr uint8_t STUFFING = 0x01;
13static constexpr uint32_t FRAME_GAP_MS = 50;
15static constexpr uint32_t DISCOVERY_START_ID = 0;
16static constexpr uint32_t DISCOVERY_STEP_ID = 1;
17static constexpr uint32_t DISCOVERY_START_MS = 16000;
18static constexpr uint32_t DISCOVERY_STEP_MS = 1600;
19static constexpr uint8_t DISCOVERY_MAX_ATTEMPTS = 3;
45#ifdef DUCO_DEVICE_COUNT
46 for (
auto &pending : this->
pending_) {
47 if (pending.device == device) {
49 pending.missed =
false;
60 ESP_LOGV(TAG,
"Message longer than %u bytes dropped",
static_cast<unsigned>(
MAX_FRAME_SIZE));
63 const uint8_t prev = at > 0 ? this->
rx_buffer_[at - 1] : 0;
65 if (at == 1 && prev == HEADER_START &&
byte == HEADER_END)
71 if (prev == HEADER_START && !escaped) {
72 if (
byte == STUFFING) {
76 if (at >= 3 &&
byte == HEADER_END) {
92 ESP_LOGV(TAG,
"Malformed message dropped: %u bytes, length byte %u", this->
rx_size_, raw[0]);
99 if (computed_crc != remote_crc) {
101 ESP_LOGV(TAG,
"CRC check failed: %04X != %04X", computed_crc, remote_crc);
104 ESP_LOGV(TAG,
"CRC check failed, ignored: %04X != %04X", computed_crc, remote_crc);
107#ifdef DUCO_DEVICE_COUNT
110 for (
auto &pending : this->
pending_) {
111 if (pending.function != 0 && pending.id ==
message.id && pending.function + 2 ==
raw[1]) {
112 pending.function = 0;
113 pending.missed =
false;
115 pending.device->receive_response(
message);
120 if ((
raw[1] & 1) == 0) {
121 ESP_LOGV(TAG,
"Answer %02X with id %u matches no open request",
raw[1],
message.id);
126#ifdef DUCO_DEVICE_COUNT
128 for (
const auto &pending : this->
pending_) {
129 if (pending.missed) {
154 ESP_LOGE(TAG,
"Request of %u bytes is too long",
static_cast<unsigned>(data.size()));
160 const size_t size = data.size();
161 const uint8_t
id = this->
next_id_();
162 uint8_t frame[5 +
MAX_REQUEST_DATA + 2] = {HEADER_START, HEADER_END,
static_cast<uint8_t
>(
size + 2), function,
id};
163 std::copy_n(data.begin(),
size, frame + 5);
164 const uint16_t crc =
crc16(frame + 2,
size + 3);
165 frame[
size + 5] = crc & 0xFF;
166 frame[
size + 6] = crc >> 8;
170#ifdef DUCO_DEVICE_COUNT
171 for (
auto &pending : this->
pending_) {
172 if (pending.device == device) {
173 pending.missed = pending.function != 0;
174 if (pending.missed) {
175 ESP_LOGV(TAG,
"No answer to request %02X with id %u", pending.function, pending.id);
179 pending.function = function;
185 ESP_LOGV_HEX(TAG, frame + 2,
size + 5,
sizeof(frame),
"Sent %s");
188static const LogString *node_type_name(uint8_t
type) {
191 return LOG_STR(
"UCBAT");
193 return LOG_STR(
"UC");
195 return LOG_STR(
"UCRH");
197 return LOG_STR(
"UCCO2");
199 return LOG_STR(
"VLV");
201 return LOG_STR(
"BOX");
203 return LOG_STR(
"SWITCH");
205 return LOG_STR(
"UNKNOWN");
211 this->
set_timeout(DISCOVERY_START_ID, DISCOVERY_START_MS, [
this]() {
212 this->
set_interval(DISCOVERY_STEP_ID, DISCOVERY_STEP_MS, [
this]() { this->
update(); });
217 if (this->
attempts_ == DISCOVERY_MAX_ATTEMPTS) {
218 ESP_LOGV(TAG,
"No answer for node %u", this->
next_node_);
224 this->
parent_->close_request(
this);
229 this->
parent_->close_request(
this);
230 ESP_LOGV(TAG,
"Discovering node %u", this->
next_node_);
239 ESP_LOGV(TAG,
"Found node %u: type %u", this->
next_node_, message.
data[0]);
247 ESP_LOGCONFIG(TAG,
"Discovered nodes:");
248 for (uint8_t node = 0; node <
NODE_COUNT; node++) {
251 ESP_LOGCONFIG(TAG,
" Node %u: type %u (%s)", node,
type, LOG_STR_ARG(node_type_name(
type)));
255 ESP_LOGCONFIG(TAG,
" Still discovering, at node %u", this->
next_node_);
263 ESP_LOGV(TAG,
"Time is not valid yet");
267 this->
parent_->send(
nullptr, FUNCTION_PARAMETER,
268 {0x05,
static_cast<uint8_t
>(ts),
static_cast<uint8_t
>(ts >> 8),
static_cast<uint8_t
>(ts >> 16),
269 static_cast<uint8_t
>(ts >> 24), 0x00, 0x00, 0x00, 0x00});
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void status_set_warning()
bool cancel_interval(const char *name)
Cancel an interval function.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
void set_interval(const char *name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a const char* name.
void status_clear_warning()
Every entity polls the box; deriving from PollingComponent shares its vtable.
void receive_response(const DucoMessage &message) override
std::array< uint8_t, NODE_COUNT > node_types_
static constexpr uint8_t NODE_COUNT
void dump_config() override
std::array< uint8_t, MAX_FRAME_SIZE > rx_buffer_
static constexpr size_t MAX_REQUEST_DATA
static constexpr size_t MAX_FRAME_SIZE
void dump_config() override
void close_request(DucoDevice *device)
Forget device's open request, for a device that will not send again.
void send(DucoDevice *device, uint8_t function, std::initializer_list< uint8_t > data)
Send a request; its answer goes to device, or nowhere if nullptr.
StaticVector< DucoPendingRequest, DUCO_DEVICE_COUNT > pending_
bool parse_byte_(uint8_t byte)
time::RealTimeClock * clock_
ESPTime now()
Get the time in the currently defined timezone.
bool read_available(F &&on_byte)
Same as read_available_chunks(), calling on_byte(uint8_t) once per byte.
void write_array(const uint8_t *data, size_t len)
const LogString * message
ESPHOME_LOG_TAG(TAG, "duco")
uint16_t crc16(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t reverse_poly, bool refin, bool refout)
Calculate a CRC-16 checksum of data with size len.
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Application App
Global storage of Application pointer - only one Application can exist.
A more user-friendly version of struct tm from time.h.
time_t timestamp
unix epoch time (seconds since UTC Midnight January 1, 1970)
bool is_valid(bool check_day_of_week=true, bool check_day_of_year=true) const
Check if this ESPTime is valid (year >= 2019 and the requested fields are in range).
A received message; data points into the receive buffer and is valid only during the callback.