ESP IDF support.

This commit is contained in:
Your Name 2023-02-13 21:12:44 +01:00
parent aafaf474d3
commit f2097c76af
16 changed files with 151 additions and 96 deletions

View File

@ -172,7 +172,6 @@ The following [ESP Home actions](https://esphome.io/guides/automations.html#acti
## TODO
- [ ] This file
- [ ] ESP32 IDF support
- [ ] RP2040 support
- [ ] Testing of Combi 4E / Combi 6E and Alde devices (I only have access to an Combi 4)
- [ ] More Testing

View File

@ -21,11 +21,11 @@ void LinBusListener::dump_config() {
void LinBusListener::setup() {
ESP_LOGCONFIG(TAG, "Setting up LIN BUS...");
this->time_per_baud_ = (1000.0f * 1000.0f / this->parent_->get_baud_rate());
this->time_per_lin_break_ = this->time_per_baud_ * this->lin_break_length * 1.1;
this->time_per_pid_ = this->time_per_baud_ * this->frame_length_ * 1.1;
this->time_per_first_byte_ = this->time_per_baud_ * this->frame_length_ * 3.0;
this->time_per_byte_ = this->time_per_baud_ * this->frame_length_ * 1.1;
// this->time_per_baud_ = (1000.0f * 1000.0f / this->parent_->get_baud_rate());
// this->time_per_lin_break_ = this->time_per_baud_ * this->lin_break_length * 1.1;
// this->time_per_pid_ = this->time_per_baud_ * this->frame_length_ * 1.1;
// this->time_per_first_byte_ = this->time_per_baud_ * this->frame_length_ * 3.0;
// this->time_per_byte_ = this->time_per_baud_ * this->frame_length_ * 1.1;
// call device specific function
this->setup_framework();
@ -55,6 +55,12 @@ void LinBusListener::write_lin_answer_(const u_int8_t *data, size_t len) {
data_CRC = data_checksum(data, len, this->current_PID_with_parity_);
}
// I am answering too quick ~50-60us after stop bit. Normal communication has a ~100us pause (second stop bits).
// The heater is answering after ~500-600us.
// If there is any issue I might have to add a delay here.
// Check when last byte was read from buffer and wait at least one baud time.
// It is working when I answer quicker.
if (!this->observer_mode_) {
this->write_array(data, len);
this->write(data_CRC);
@ -63,6 +69,30 @@ void LinBusListener::write_lin_answer_(const u_int8_t *data, size_t len) {
ESP_LOGV(TAG, "RESPONSE %02x %s %02x", this->current_PID_, format_hex_pretty(data, len).c_str(), data_CRC);
}
void LinBusListener::onReceive_() {
// Check if Lin Bus is faulty.
if (this->fault_pin_ != nullptr) {
if (!this->fault_pin_->digital_read()) {
if (!this->fault_on_lin_bus_reported_) {
this->fault_on_lin_bus_reported_ = true;
ESP_LOGE(TAG, "Fault on LIN BUS detected.");
}
// Ignore any data present in buffer
this->clear_uart_buffer_();
} else if (this->fault_on_lin_bus_reported_) {
this->fault_on_lin_bus_reported_ = false;
ESP_LOGI(TAG, "Fault on LIN BUS fixed.");
}
}
if (!this->fault_on_lin_bus_reported_) {
while (this->available()) {
// this->last_data_recieved_ = micros();
this->read_lin_frame_();
}
}
}
void LinBusListener::read_lin_frame_() {
u_int8_t buf;
switch (this->current_state_) {
@ -128,6 +158,8 @@ void LinBusListener::read_lin_frame_() {
this->current_state_ = READ_STATE_ACT;
}
break;
default:
break;
}
if (this->current_state_ == READ_STATE_ACT && this->current_data_count_ > 1) {

View File

@ -4,6 +4,11 @@
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#ifdef USE_ESP32_FRAMEWORK_ESP_IDF
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#endif // USE_ESP32_FRAMEWORK_ESP_IDF
namespace esphome {
namespace truma_inetbox {
@ -34,19 +39,19 @@ class LinBusListener : public PollingComponent, public uart::UARTDevice {
virtual void lin_message_recieved_(const u_int8_t pid, const u_int8_t *message, u_int8_t length) = 0;
private:
// Microseconds per UART Baud
u_int32_t time_per_baud_;
// 9.. 15
u_int8_t lin_break_length = 13;
// Microseconds per LIN Break
u_int32_t time_per_lin_break_;
u_int8_t frame_length_ = (8 /* bits */ + 1 /* Start bit */ + 2 /* Stop bits */);
// Microseconds per UART Byte (UART Frame)
u_int32_t time_per_pid_;
// Microseconds per UART Byte (UART Frame)
u_int32_t time_per_first_byte_;
// Microseconds per UART Byte (UART Frame)
u_int32_t time_per_byte_;
// // Microseconds per UART Baud
// u_int32_t time_per_baud_;
// // 9.. 15
// u_int8_t lin_break_length = 13;
// // Microseconds per LIN Break
// u_int32_t time_per_lin_break_;
// u_int8_t frame_length_ = (8 /* bits */ + 1 /* Start bit */ + 2 /* Stop bits */);
// // Microseconds per UART Byte (UART Frame)
// u_int32_t time_per_pid_;
// // Microseconds per UART Byte (UART Frame)
// u_int32_t time_per_first_byte_;
// // Microseconds per UART Byte (UART Frame)
// u_int32_t time_per_byte_;
bool fault_on_lin_bus_reported_ = false;
bool can_write_lin_answer_ = false;
@ -66,11 +71,17 @@ class LinBusListener : public PollingComponent, public uart::UARTDevice {
// up to 8 byte data frame + CRC
u_int8_t current_data_[9] = {};
// // Time when the last LIN data was available.
// int64_t last_data_recieved_;
// uint32_t last_data_recieved_;
void onReceive_();
void read_lin_frame_();
void clear_uart_buffer_();
void setup_framework();
#ifdef USE_ESP32_FRAMEWORK_ESP_IDF
TaskHandle_t _eventTask;
static void _uartEventTask(void *args);
#endif // USE_ESP32_FRAMEWORK_ESP_IDF
};
} // namespace truma_inetbox

View File

@ -16,7 +16,7 @@ void LinBusListener::setup_framework() {
auto uartComp = static_cast<esphome::uart::truma_ESP32ArduinoUARTComponent *>(this->parent_);
auto uart_num = uartComp->get_hw_serial_number();
auto hwSerial = uartComp->get_hw_serial();
auto hw_serial = uartComp->get_hw_serial();
// Extract from `uartSetFastReading` - Can't call it because I don't have access to `uart_t` object.
@ -34,32 +34,10 @@ void LinBusListener::setup_framework() {
uart_intr.txfifo_empty_intr_thresh = 10; // UART_EMPTY_THRESH_DEFAULT
uart_intr_config(uart_num, &uart_intr);
hwSerial->onReceive(
[this]() {
// Check if Lin Bus is faulty.
if (this->fault_pin_ != nullptr) {
if (!this->fault_pin_->digital_read()) {
if (!this->fault_on_lin_bus_reported_) {
this->fault_on_lin_bus_reported_ = true;
ESP_LOGE(TAG, "Fault on LIN BUS detected.");
}
// Ignore any data present in buffer
this->clear_uart_buffer_();
} else if (this->fault_on_lin_bus_reported_) {
this->fault_on_lin_bus_reported_ = false;
ESP_LOGI(TAG, "Fault on LIN BUS fixed.");
}
}
if (!this->fault_on_lin_bus_reported_) {
while (this->available()) {
// this->last_data_recieved_ = esp_timer_get_time();
this->read_lin_frame_();
}
}
},
false);
hwSerial->onReceiveError([this](hardwareSerial_error_t val) {
hw_serial->onReceive([this]() { this->onReceive_(); }, false);
hw_serial->onReceiveError([this](hardwareSerial_error_t val) {
// Ignore any data present in buffer
this->clear_uart_buffer_();
if (val == UART_BREAK_ERROR) {
// If the break is valid the `onReceive` is called first and the break is handeld. Therfore the expectation is
// that the state should be in waiting for `SYNC`.
@ -67,14 +45,6 @@ void LinBusListener::setup_framework() {
this->current_state_ = READ_STATE_BREAK;
}
return;
} else if (val == UART_BUFFER_FULL_ERROR) {
ESP_LOGW(TAG, "UART_BUFFER_FULL_ERROR");
} else if (val == UART_FIFO_OVF_ERROR) {
ESP_LOGW(TAG, "UART_FIFO_OVF_ERROR");
} else if (val == UART_FRAME_ERROR) {
ESP_LOGW(TAG, "UART_FRAME_ERROR");
} else if (val == UART_PARITY_ERROR) {
ESP_LOGW(TAG, "UART_PARITY_ERROR");
}
});
}

View File

@ -1,6 +1,7 @@
#ifdef USE_ESP32_FRAMEWORK_ESP_IDF
#include "LinBusListener.h"
#include "esphome/core/log.h"
#include "soc/uart_reg.h"
#include "esphome/components/uart/truma_uart_component_esp_idf.h"
#include "esphome/components/uart/uart_component_esp_idf.h"
@ -11,20 +12,59 @@ static const char *const TAG = "truma_inetbox.LinBusListener";
void LinBusListener::setup_framework() {
// uartSetFastReading
auto uartComp = ((*uart::truma_IDFUARTComponent) this->parent_);
auto uartComp = static_cast<uart::truma_IDFUARTComponent *>(this->parent_);
auto uart_num = uartComp->get_hw_serial_number();
// Tweak the fifo settings so data is available as soon as the first byte is recieved.
// If not it will wait either until fifo is filled or a certain time has passed.
uart_intr_config_t uart_intr;
uart_intr.intr_enable_mask = 0; // UART_RXFIFO_FULL_INT_ENA_M | UART_RXFIFO_TOUT_INT_ENA_M | UART_FRM_ERR_INT_ENA_M |
// UART_RXFIFO_OVF_INT_ENA_M | UART_BRK_DET_INT_ENA_M | UART_PARITY_ERR_INT_ENA_M;
uart_intr.intr_enable_mask =
UART_RXFIFO_FULL_INT_ENA_M | UART_RXFIFO_TOUT_INT_ENA_M; // only these IRQs - no BREAK, PARITY or OVERFLOW
uart_intr.rxfifo_full_thresh =
1; // UART_FULL_THRESH_DEFAULT, //120 default!! aghh! need receive 120 chars before we see them
uart_intr.rx_timeout_thresh =
1; // UART_TOUT_THRESH_DEFAULT, //10 works well for my short messages I need send/receive
10; // UART_TOUT_THRESH_DEFAULT, //10 works well for my short messages I need send/receive
uart_intr.txfifo_empty_intr_thresh = 10; // UART_EMPTY_THRESH_DEFAULT
uart_intr_config(uartComp->get_hw_serial_number(), &uart_intr);
uart_intr_config(uart_num, &uart_intr);
// Creating UART event Task
xTaskCreatePinnedToCore(LinBusListener::_uartEventTask,
"uart_event_task", // name
ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE, // stack size (in words)
this, // input params
24, // priority
&_eventTask, // handle
ARDUINO_SERIAL_EVENT_TASK_RUNNING_CORE // core
);
if (_eventTask == NULL) {
ESP_LOGE(TAG, " -- UART%d Event Task not Created!", uart_num);
}
}
void LinBusListener::_uartEventTask(void *args) {
LinBusListener *instance = (LinBusListener *) args;
auto uartComp = static_cast<uart::truma_IDFUARTComponent *>(instance->parent_);
auto uart_num = uartComp->get_hw_serial_number();
auto uartEventQueue = uartComp->get_uart_event_queue();
uart_event_t event;
for (;;) {
// Waiting for UART event.
if (xQueueReceive(*uartEventQueue, (void *) &event, (portTickType) portMAX_DELAY)) {
if (event.type == UART_DATA && instance->available() > 0) {
instance->onReceive_();
} else if (event.type == UART_BREAK) {
// If the break is valid the `onReceive` is called first and the break is handeld. Therfore the expectation is
// that the state should be in waiting for `SYNC`.
if (instance->current_state_ != READ_STATE_SYNC) {
instance->current_state_ = READ_STATE_BREAK;
}
}
}
}
vTaskDelete(NULL);
}
} // namespace truma_inetbox
} // namespace esphome

View File

@ -66,7 +66,7 @@ const std::array<uint8_t, 4> TrumaiNetBoxApp::lin_identifier() {
}
void TrumaiNetBoxApp::lin_reset_device() {
this->device_registered_ = esp_timer_get_time();
this->device_registered_ = micros();
this->init_recieved_ = 0;
this->status_heater_valid_ = false;
@ -90,9 +90,8 @@ void TrumaiNetBoxApp::lin_reset_device() {
}
void TrumaiNetBoxApp::register_listener(const std::function<void(const StatusFrameHeater *)> &func) {
auto listener = StatusFrameListener{
.on_heater_change = func,
};
StatusFrameListener listener = {};
listener.on_heater_change = func;
this->listeners_heater_.push_back(std::move(listener));
if (this->status_heater_valid_) {
@ -100,9 +99,8 @@ void TrumaiNetBoxApp::register_listener(const std::function<void(const StatusFra
}
}
void TrumaiNetBoxApp::register_listener(const std::function<void(const StatusFrameTimer *)> &func) {
auto listener = StatusFrameListener{
.on_timer_change = func,
};
StatusFrameListener listener = {};
listener.on_timer_change = func;
this->listeners_heater_.push_back(std::move(listener));
if (this->status_timer_valid_) {
@ -110,9 +108,8 @@ void TrumaiNetBoxApp::register_listener(const std::function<void(const StatusFra
}
}
void TrumaiNetBoxApp::register_listener(const std::function<void(const StatusFrameClock *)> &func) {
auto listener = StatusFrameListener{
.on_clock_change = func,
};
StatusFrameListener listener = {};
listener.on_clock_change = func;
this->listeners_heater_.push_back(std::move(listener));
if (this->status_clock_valid_) {
@ -120,9 +117,8 @@ void TrumaiNetBoxApp::register_listener(const std::function<void(const StatusFra
}
}
void TrumaiNetBoxApp::register_listener(const std::function<void(const StatusFrameConfig *)> &func) {
auto listener = StatusFrameListener{
.on_config_change = func,
};
StatusFrameListener listener = {};
listener.on_config_change = func;
this->listeners_heater_.push_back(std::move(listener));
if (this->status_config_valid_) {
@ -190,7 +186,7 @@ bool TrumaiNetBoxApp::answer_lin_order_(const u_int8_t pid) {
}
bool TrumaiNetBoxApp::lin_read_field_by_identifier_(u_int8_t identifier, std::array<u_int8_t, 5> *response) {
this->device_registered_ = esp_timer_get_time();
this->device_registered_ = micros();
if (identifier == 0x00 /* LIN Product Identification */) {
auto lin_identifier = this->lin_identifier();
(*response)[0] = lin_identifier[0];
@ -389,7 +385,7 @@ const u_int8_t *TrumaiNetBoxApp::lin_multiframe_recieved(const u_int8_t *message
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0C.0B.00.27.02.01.01.00.40.03.22.02.00.01.00.00 - H2.00.01 0340.22
auto device = statusFrame->inner.device;
this->init_recieved_ = esp_timer_get_time();
this->init_recieved_ = micros();
ESP_LOGV(TAG, "StatusFrameDevice %d/%d - %d.%02d.%02d %04x.%02x", device.device_id + 1, device.device_count,
device.software_revision[0], device.software_revision[1], device.software_revision[2],
@ -405,28 +401,28 @@ const u_int8_t *TrumaiNetBoxApp::lin_multiframe_recieved(const u_int8_t *message
bool TrumaiNetBoxApp::has_update_to_submit_() {
if (this->init_requested_ == 0) {
this->init_requested_ = esp_timer_get_time();
this->init_requested_ = micros();
ESP_LOGV(TAG, "Requesting initial data.");
return true;
} else if (this->init_recieved_ == 0) {
auto init_wait_time = esp_timer_get_time() - this->init_requested_;
auto init_wait_time = micros() - this->init_requested_;
// it has been 5 seconds and i am still awaiting the init data.
if (init_wait_time > 1000 * 1000 * 5) {
ESP_LOGV(TAG, "Requesting initial data again.");
this->init_requested_ = esp_timer_get_time();
this->init_requested_ = micros();
return true;
}
} else if (this->update_status_heater_unsubmitted_ || this->update_status_timer_unsubmitted_ ||
this->update_status_clock_unsubmitted_) {
if (this->update_time_ == 0) {
ESP_LOGV(TAG, "Notify CP Plus I got updates.");
this->update_time_ = esp_timer_get_time();
this->update_time_ = micros();
return true;
}
auto update_wait_time = esp_timer_get_time() - this->update_time_;
auto update_wait_time = micros() - this->update_time_;
if (update_wait_time > 1000 * 1000 * 5) {
ESP_LOGV(TAG, "Notify CP Plus again I still got updates.");
this->update_time_ = esp_timer_get_time();
this->update_time_ = micros();
return true;
}
}

View File

@ -326,10 +326,10 @@ union StatusFrame { // NOLINT(altera-struct-pack-align)
} __attribute__((packed));
struct StatusFrameListener {
std::function<void(const StatusFrameHeater *)> on_heater_change;
std::function<void(const StatusFrameTimer *)> on_timer_change;
std::function<void(const StatusFrameClock *)> on_clock_change;
std::function<void(const StatusFrameConfig *)> on_config_change;
std::function<void(const StatusFrameHeater *)> on_heater_change = nullptr;
std::function<void(const StatusFrameTimer *)> on_timer_change = nullptr;
std::function<void(const StatusFrameClock *)> on_clock_change = nullptr;
std::function<void(const StatusFrameConfig *)> on_config_change = nullptr;
};
class TrumaiNetBoxApp : public LinBusProtocol {
@ -395,9 +395,9 @@ class TrumaiNetBoxApp : public LinBusProtocol {
time::RealTimeClock *time_;
// Truma CP Plus needs init (reset). This device is not registered.
int64_t device_registered_ = 0;
int64_t init_requested_ = 0;
int64_t init_recieved_ = 0;
uint32_t device_registered_ = 0;
uint32_t init_requested_ = 0;
uint32_t init_recieved_ = 0;
u_int8_t message_counter = 1;
std::vector<StatusFrameListener> listeners_heater_;
@ -424,7 +424,7 @@ class TrumaiNetBoxApp : public LinBusProtocol {
StatusFrameConfig status_config_;
// last time CP plus was informed I got an update msg.
int64_t update_time_ = 0;
uint32_t update_time_ = 0;
// Prepared means `update_status_heater_` was copied from `status_heater_`.
bool update_status_heater_prepared_ = false;
// Prepared means an update is already awating fetch from CP plus.

View File

@ -6,7 +6,6 @@ from esphome.const import (
CONF_ID,
CONF_CS_PIN,
CONF_TEMPERATURE,
CONF_ON_MESSAGE,
CONF_TRIGGER_ID,
CONF_STOP,
CONF_TIME_ID,
@ -62,7 +61,6 @@ CONFIG_SCHEMA = cv.All(
# Reading and communication is done in a seperate thread/core.
.extend(cv.polling_component_schema("500ms"))
.extend(uart.UART_DEVICE_SCHEMA),
cv.only_with_arduino,
cv.only_on(["esp32"]),
)
FINAL_VALIDATE_SCHEMA = cv.All(

View File

@ -13,7 +13,7 @@ void TrumaCpPlusBinarySensor::update() {
return;
}
auto timeout = this->parent_->get_last_cp_plus_request() + 30 * 1000 * 1000 /* 30 seconds*/;
this->publish_state(esp_timer_get_time() < timeout);
this->publish_state(micros() < timeout);
}
void TrumaCpPlusBinarySensor::dump_config() { ESP_LOGCONFIG("", "Truma CP Plus Binary Sensor"); }

View File

@ -30,6 +30,8 @@ void TrumaHeaterBinarySensor::setup() {
case TRUMA_BINARY_SENSOR_TYPE::HEATER_ELECTRICITY:
this->publish_state(status_heater->energy_mix_a == EnergyMix::ENERGY_MIX_ELECTRICITY);
break;
default:
break;
}
});
}

View File

@ -19,6 +19,8 @@ void TrumaTimerBinarySensor::setup() {
case TRUMA_BINARY_SENSOR_TYPE::TIMER_WATER:
this->publish_state(status_timer->timer_target_temp_water != TargetTemp::TARGET_TEMP_OFF);
break;
default:
break;
}
});
}

View File

@ -89,7 +89,7 @@ const std::string operating_status_to_str(OperatingStatus val) {
} else if (val == OperatingStatus::OPERATING_STATUS_ON_9) {
return "ON (9)";
} else {
return str_snprintf("ON %x", (uint8_t) val);
return esphome::str_snprintf("ON %u", 6, (uint8_t) val);
}
}

View File

@ -9,7 +9,6 @@ namespace uart {
class truma_ESP32ArduinoUARTComponent : public ESP32ArduinoUARTComponent {
public:
bool is_hw_serial() { return true; }
HardwareSerial *get_hw_serial() { return this->hw_serial_; }
uint8_t get_hw_serial_number() { return this->number_; }
};

View File

@ -9,8 +9,9 @@ namespace uart {
class truma_IDFUARTComponent : public IDFUARTComponent {
public:
bool is_hw_serial() { return true; }
uint8_t get_hw_serial_number() { return this->uart_num_; }
// `QueueHandle_t uart_event_queue_;` is also added to base class.
QueueHandle_t *get_uart_event_queue() { return &this->uart_event_queue_; }
};
} // namespace uart

View File

@ -79,7 +79,11 @@ void IDFUARTComponent::setup() {
return;
}
err = uart_driver_install(this->uart_num_, this->rx_buffer_size_, 0, 0, nullptr, 0);
err = uart_driver_install(this->uart_num_, this->rx_buffer_size_,
0, // UART TX ring buffer size.
// If set to zero, driver will not use TX buffer, TX function will block task until all
// data have been sent out.
20, &(this->uart_event_queue_), 0);
if (err != ESP_OK) {
ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
this->mark_failed();

View File

@ -26,6 +26,7 @@ class IDFUARTComponent : public UARTComponent, public Component {
protected:
void check_logger_conflict() override;
uart_port_t uart_num_;
QueueHandle_t uart_event_queue_;
uart_config_t get_config_();
SemaphoreHandle_t lock_;