Add support for RP2040. Make time optional.

This commit is contained in:
Your Name 2023-02-16 11:51:36 +01:00
parent f2097c76af
commit 127df8b9b6
10 changed files with 437 additions and 113 deletions

View File

@ -21,20 +21,31 @@ 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.1f;
this->time_per_pid_ = this->time_per_baud_ * this->frame_length_ * 1.1f;
this->time_per_first_byte_ = this->time_per_baud_ * this->frame_length_ * 5.0f;
this->time_per_byte_ = this->time_per_baud_ * this->frame_length_ * 1.1f;
if (this->cs_pin_ != nullptr) {
this->cs_pin_->setup();
}
if (this->fault_pin_ != nullptr) {
this->fault_pin_->setup();
}
// call device specific function
this->setup_framework();
if (this->cs_pin_ != nullptr) {
this->cs_pin_->digital_write(true);
// Enable LIN driver if not in oberserver mode.
this->cs_pin_->digital_write(!this->observer_mode_);
}
}
void LinBusListener::update() { this->check_for_lin_fault_(); }
void LinBusListener::write_lin_answer_(const u_int8_t *data, size_t len) {
if (!this->can_write_lin_answer_) {
ESP_LOGE(TAG, "Cannot answer LIN because there is no open order from master.");
@ -62,33 +73,53 @@ void LinBusListener::write_lin_answer_(const u_int8_t *data, size_t len) {
// It is working when I answer quicker.
if (!this->observer_mode_) {
this->current_PID_order_answered_ = true;
this->write_array(data, len);
this->write(data_CRC);
this->flush();
ESP_LOGV(TAG, "RESPONSE %02X %s %02X", this->current_PID_, format_hex_pretty(data, len).c_str(), data_CRC);
} else {
ESP_LOGV(TAG, "RESPONSE %02X %s %02X - NOT SEND (OBSERVER MODE)", this->current_PID_,
format_hex_pretty(data, len).c_str(), data_CRC);
}
ESP_LOGV(TAG, "RESPONSE %02x %s %02x", this->current_PID_, format_hex_pretty(data, len).c_str(), data_CRC);
}
void LinBusListener::onReceive_() {
bool LinBusListener::check_for_lin_fault_() {
// Check if Lin Bus is faulty.
if (this->fault_pin_ != nullptr) {
// Fault pin is inverted (HIGH = no fault)
if (!this->fault_pin_->digital_read()) {
if (!this->fault_on_lin_bus_reported_) {
this->fault_on_lin_bus_reported_ = true;
if (this->fault_on_lin_bus_reported_ < 0xFF) {
this->fault_on_lin_bus_reported_++;
} else {
this->fault_on_lin_bus_reported_ = 0x0F;
}
if (this->fault_on_lin_bus_reported_ % 3 == 0) {
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;
} else if (this->get_lin_bus_fault()) {
this->fault_on_lin_bus_reported_ = 0;
ESP_LOGI(TAG, "Fault on LIN BUS fixed.");
} else {
this->fault_on_lin_bus_reported_ = 0;
}
}
if (!this->fault_on_lin_bus_reported_) {
if (this->get_lin_bus_fault()) {
this->current_state_reset_();
// Ignore any data present in buffer
this->clear_uart_buffer_();
return true;
} else {
return false;
}
}
void LinBusListener::onReceive_() {
if (!this->check_for_lin_fault_()) {
while (this->available()) {
// this->last_data_recieved_ = micros();
this->read_lin_frame_();
this->last_data_recieved_ = micros();
}
}
}
@ -98,33 +129,36 @@ void LinBusListener::read_lin_frame_() {
switch (this->current_state_) {
case READ_STATE_BREAK:
// Check if there was an unanswered message before break.
if (this->current_PID_with_parity_ != 0x00 && this->current_PID_ != 0x00 && this->current_data_valid &&
this->current_data_count_ == 0) {
ESP_LOGV(TAG, "PID %02x (%02x) order no answer", this->current_PID_, this->current_PID_with_parity_);
if (this->current_PID_with_parity_ != 0x00 && this->current_PID_ != 0x00 && this->current_data_valid) {
if (this->current_PID_order_answered_ && this->current_data_count_ < 8) {
// Expectation is that I can see an echo of my data from the lin driver chip.
ESP_LOGE(TAG, "PID %02X (%02X) order - unable to send response", this->current_PID_, this->current_PID_with_parity_);
} else if (this->current_data_count_ == 0) {
ESP_LOGV(TAG, "PID %02X (%02X) order no answer", this->current_PID_, this->current_PID_with_parity_);
} else if (this->current_data_count_ < 8) {
ESP_LOGW(TAG, "PID %02X (%02X) %s partial data received", this->current_PID_, this->current_PID_with_parity_,
format_hex_pretty(this->current_data_, this->current_data_count_).c_str());
}
}
// Reset current state
{
this->current_PID_with_parity_ = 0x00;
this->current_PID_ = 0x00;
this->current_data_valid = true;
this->current_data_count_ = 0;
memset(this->current_data_, 0, sizeof(this->current_data_));
}
this->current_state_reset_();
// First is Break expected
if (!this->read_byte(&buf) || buf != LIN_BREAK) {
ESP_LOGVV(TAG, "Expected BREAK not received.");
ESP_LOGVV(TAG, "0x%02X Expected BREAK not received.", buf);
} else {
// ESP_LOGVV(TAG, "%02X BREAK received.", buf);
this->current_state_ = READ_STATE_SYNC;
}
break;
case READ_STATE_SYNC:
// Second is Sync expected
if (!this->read_byte(&buf) || buf != LIN_SYNC) {
ESP_LOGVV(TAG, "Expected SYNC not found.");
this->current_state_ = READ_STATE_BREAK;
ESP_LOGVV(TAG, "0x%02X Expected SYNC not found.", buf);
this->current_state_ = buf == LIN_BREAK ? READ_STATE_SYNC : READ_STATE_BREAK;
} else {
// ESP_LOGVV(TAG, "%02X SYNC found.", buf);
this->current_state_ = READ_STATE_SID;
}
break;
@ -133,7 +167,7 @@ void LinBusListener::read_lin_frame_() {
this->current_PID_ = this->current_PID_with_parity_ & 0x3F;
if (this->lin_checksum_ == LIN_CHECKSUM::LIN_CHECKSUM_VERSION_2) {
if (this->current_PID_with_parity_ != (this->current_PID_ | (addr_parity(this->current_PID_) << 6))) {
ESP_LOGW(TAG, "LIN CRC error on SID.");
ESP_LOGW(TAG, "0x%02X LIN CRC error on SID.", this->current_PID_with_parity_);
this->current_data_valid = false;
}
}
@ -148,7 +182,14 @@ void LinBusListener::read_lin_frame_() {
// Even on error read data.
this->current_state_ = READ_STATE_DATA;
break;
case READ_STATE_DATA:
case READ_STATE_DATA: {
auto current = micros();
if (current > (this->last_data_recieved_ + this->time_per_first_byte_)) {
// timeout occured.
this->current_state_ = READ_STATE_BREAK;
return;
}
}
this->read_byte(&buf);
this->current_data_[this->current_data_count_] = buf;
this->current_data_count_++;
@ -195,13 +236,15 @@ void LinBusListener::read_lin_frame_() {
}
// Mark the PID of the TRUMA Combi heater as very verbose message.
if (this->current_PID_ == 0x20 || this->current_PID_ == 0x21 || this->current_PID_ == 0x22) {
ESP_LOGVV(TAG, "PID %02x (%02x) %s %s %s", this->current_PID_, this->current_PID_with_parity_,
if (this->current_PID_ == 0x20 || this->current_PID_ == 0x21 || this->current_PID_ == 0x22 ||
((this->current_PID_ == DIAGNOSTIC_FRAME_MASTER || this->current_PID_ == DIAGNOSTIC_FRAME_SLAVE) &&
this->current_data_[0] == 0x01 /* ID of heater */)) {
ESP_LOGVV(TAG, "PID %02X (%02X) %s %s %s", this->current_PID_, this->current_PID_with_parity_,
format_hex_pretty(this->current_data_, this->current_data_count_).c_str(),
message_source_know ? (message_from_master ? " - MASTER" : " - SLAVE") : "",
this->current_data_valid ? "" : "INVALID");
} else {
ESP_LOGV(TAG, "PID %02x (%02x) %s %s %S", this->current_PID_, this->current_PID_with_parity_,
ESP_LOGV(TAG, "PID %02X (%02X) %s %s %S", this->current_PID_, this->current_PID_with_parity_,
format_hex_pretty(this->current_data_, this->current_data_count_).c_str(),
message_source_know ? (message_from_master ? " - MASTER" : " - SLAVE") : "",
this->current_data_valid ? "" : "INVALID");

View File

@ -1,6 +1,5 @@
#pragma once
#include <vector>
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
@ -8,6 +7,9 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#endif // USE_ESP32_FRAMEWORK_ESP_IDF
#ifdef USE_RP2040
#include <hardware/uart.h>
#endif // USE_RP2040
namespace esphome {
namespace truma_inetbox {
@ -20,12 +22,18 @@ class LinBusListener : public PollingComponent, public uart::UARTDevice {
void dump_config() override;
void setup() override;
void update() override;
void set_lin_checksum(LIN_CHECKSUM val) { this->lin_checksum_ = val; }
void set_cs_pin(GPIOPin *pin) { this->cs_pin_ = pin; }
void set_fault_pin(GPIOPin *pin) { this->fault_pin_ = pin; }
void set_observer_mode(bool val) { this->observer_mode_ = val; }
bool get_lin_bus_fault() { return fault_on_lin_bus_reported_; }
bool get_lin_bus_fault() { return fault_on_lin_bus_reported_ > 3; }
#ifdef USE_RP2040
// Return is the expected wait time till next data check is recommended.
u_int32_t onSerialEvent();
#endif // USE_RP2040
protected:
LIN_CHECKSUM lin_checksum_;
@ -34,26 +42,26 @@ class LinBusListener : public PollingComponent, public uart::UARTDevice {
bool observer_mode_ = false;
void write_lin_answer_(const u_int8_t *data, size_t len);
bool check_for_lin_fault_();
virtual bool answer_lin_order_(const u_int8_t pid) = 0;
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
const u_int8_t lin_break_length = 13;
// Microseconds per LIN Break
u_int32_t time_per_lin_break_;
const 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;
u_int8_t fault_on_lin_bus_reported_ = 0;
bool can_write_lin_answer_ = false;
enum read_state {
@ -66,13 +74,23 @@ class LinBusListener : public PollingComponent, public uart::UARTDevice {
read_state current_state_ = READ_STATE_BREAK;
u_int8_t current_PID_with_parity_ = 0x00;
u_int8_t current_PID_ = 0x00;
bool current_PID_order_answered_ = false;
bool current_data_valid = true;
u_int8_t current_data_count_ = 0;
// up to 8 byte data frame + CRC
u_int8_t current_data_[9] = {};
// // Time when the last LIN data was available.
// uint32_t last_data_recieved_;
uint32_t last_data_recieved_ = 0;
void current_state_reset_() {
this->current_state_ = READ_STATE_BREAK;
this->current_PID_with_parity_ = 0x00;
this->current_PID_ = 0x00;
this->current_PID_order_answered_ = false;
this->current_data_valid = true;
this->current_data_count_ = 0;
memset(this->current_data_, 0, sizeof(this->current_data_));
};
void onReceive_();
void read_lin_frame_();
void clear_uart_buffer_();
@ -82,7 +100,11 @@ class LinBusListener : public PollingComponent, public uart::UARTDevice {
TaskHandle_t _eventTask;
static void _uartEventTask(void *args);
#endif // USE_ESP32_FRAMEWORK_ESP_IDF
#ifdef USE_RP2040
u_int8_t uart_number_ = 0;
uart_inst_t *uart_ = nullptr;
#endif // USE_RP2040
};
} // namespace truma_inetbox
} // namespace esphome
} // namespace esphome

View File

@ -3,6 +3,12 @@
#include "esphome/core/log.h"
#include "esphome/components/uart/truma_uart_component_rp2040.h"
#include "esphome/components/uart/uart_component_rp2040.h"
#include <SerialUART.h>
// Instance 1 for UART port 0
static esphome::truma_inetbox::LinBusListener *LIN_BUS_LISTENER_INSTANCE_1 = nullptr;
// Instance 2 for UART port 1
static esphome::truma_inetbox::LinBusListener *LIN_BUS_LISTENER_INSTANCE_2 = nullptr;
namespace esphome {
namespace truma_inetbox {
@ -10,9 +16,90 @@ namespace truma_inetbox {
static const char *const TAG = "truma_inetbox.LinBusListener";
void LinBusListener::setup_framework() {
// truma_RP2040UartComponent
auto uartComp = static_cast<esphome::uart::truma_RP2040UartComponent *>(this->parent_);
auto is_hw_serial = uartComp->is_hw_serial();
if (!is_hw_serial) {
ESP_LOGW(TAG, "Must use hardware serial SerialPIO is not supported.");
this->mark_failed();
}
// auto hw_serial = static_cast<SerialUART *>(uartComp->get_hw_serial());
auto hw_serial = uartComp->get_hw_serial();
if ((*hw_serial) == Serial1) {
LIN_BUS_LISTENER_INSTANCE_1 = this;
this->uart_number_ = 1;
this->uart_ = uart0;
} else if ((*hw_serial) == Serial2) {
LIN_BUS_LISTENER_INSTANCE_2 = this;
this->uart_number_ = 2;
this->uart_ = uart1;
}
if (this->uart_ != nullptr) {
// Turn off FIFO's - we want to do this character by character
uart_set_fifo_enabled(this->uart_, false);
}
}
u_int32_t LinBusListener::onSerialEvent() {
this->onReceive_();
if (this->uart_ != nullptr) {
// Receive Status Register/Error Clear Register, UARTRSR/UARTECR
// 0x00000004 [2] : BE (0): Break error
auto receive_status_register = uart_get_hw(this->uart_)->rsr;
if ((receive_status_register & UART_UARTRSR_BE_BITS) == UART_UARTRSR_BE_BITS) {
ESP_LOGVV(TAG, "UART%d RX break.", this->uart_number_);
// 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 (this->current_state_ != READ_STATE_BREAK || this->current_state_ != READ_STATE_SYNC) {
this->current_state_ = READ_STATE_SYNC;
}
// Clear Receive Status Register
hw_clear_bits(&uart_get_hw(this->uart_)->rsr, UART_UARTRSR_BE_BITS);
}
}
if (this->current_state_ == READ_STATE_BREAK) {
// Next is a break. CP Plus has an inter data break of ~35ms
auto current = micros();
if ((this->last_data_recieved_ + (1000 * 1000 /* 1 second */)) < current) {
// I have not recieved data for a while. Sleep deeper
return 750;
} else if ((this->last_data_recieved_ + (50 * 1000 /* 0.1 second */)) < current) {
// I have not recieved data for a while. Sleep deep
return 50;
} else {
// Expecting a SYNC.
return 10;
}
} else {
// Expecting a byte.
return 1;
}
}
} // namespace truma_inetbox
} // namespace esphome
extern void loop1() {
if (LIN_BUS_LISTENER_INSTANCE_1 == nullptr && LIN_BUS_LISTENER_INSTANCE_2 == nullptr) {
// Wait for setup_framework to finish.
delay(100);
} else {
u_int32_t sleep1 = 0xFFFFFFFF;
u_int32_t sleep2 = 0xFFFFFFFF;
if (LIN_BUS_LISTENER_INSTANCE_1 != nullptr) {
sleep1 = LIN_BUS_LISTENER_INSTANCE_1->onSerialEvent();
}
if (LIN_BUS_LISTENER_INSTANCE_2 != nullptr) {
sleep2 = LIN_BUS_LISTENER_INSTANCE_2->onSerialEvent();
}
delay(sleep1 > sleep2 ? sleep2 : sleep1);
}
}
#endif // USE_RP2040

View File

@ -18,10 +18,6 @@ static const char *const TAG = "truma_inetbox.LinBusProtocol";
#define LIN_SID_READ_BY_IDENTIFIER_RESPONSE (LIN_SID_READ_BY_IDENTIFIER | LIN_SID_RESPONSE)
#define LIN_SID_HEARTBEAT 0xB9
#define LIN_SID_HEARTBEAT_RESPONSE (LIN_SID_HEARTBEAT | LIN_SID_RESPONSE)
#define LIN_SID_READ_STATE_BUFFER 0xBA
#define LIN_SID_READ_STATE_BUFFER_RESPONSE (LIN_SID_READ_STATE_BUFFER | LIN_SID_RESPONSE)
#define LIN_SID_FIll_STATE_BUFFFER 0xBB
#define LIN_SID_FIll_STATE_BUFFFER_BRESPONSE (LIN_SID_FIll_STATE_BUFFFER | LIN_SID_RESPONSE)
bool LinBusProtocol::answer_lin_order_(const u_int8_t pid) {
// Send requested answer
@ -77,9 +73,7 @@ void LinBusProtocol::lin_message_recieved_(const u_int8_t pid, const u_int8_t *m
this->lin_msg_diag_first_(message, length);
} else if ((protocol_control_information & 0xF0) == 0x20) {
// Consecutive Frames
this->lin_msg_diag_consecutive_(message, length);
// Check if this was the last consecutive message.
if (this->multi_pdu_message_len_ == this->multi_pdu_message_expected_size_) {
if (this->lin_msg_diag_consecutive_(message, length)) {
this->lin_msg_diag_multi_();
}
}
@ -144,10 +138,12 @@ void LinBusProtocol::lin_msg_diag_single_(const u_int8_t *message, u_int8_t leng
response[2] = LIN_SID_HEARTBEAT_RESPONSE;
response[3] = 0x00;
this->prepare_update_msg_(response);
this->lin_heartbeat();
//}
} else if (broadcast_address && service_identifier == LIN_SID_ASSIGN_NAD && message_length == 6) {
if (this->is_matching_identifier_(&message[3])) {
ESP_LOGI(TAG, "Assigned new SID %02x and reset device", message[7]);
ESP_LOGI(TAG, "Assigned new SID %02X and reset device", message[7]);
// send response with old node address.
std::array<u_int8_t, 8> response = this->lin_empty_response_;
@ -162,9 +158,9 @@ void LinBusProtocol::lin_msg_diag_single_(const u_int8_t *message, u_int8_t leng
}
} else {
if (my_node_address) {
ESP_LOGD(TAG, "SID %02x MY - %s - Unhandled", service_identifier, format_hex_pretty(message, length).c_str());
ESP_LOGD(TAG, "SID %02X MY - %s - Unhandled", service_identifier, format_hex_pretty(message, length).c_str());
} else if (broadcast_address) {
ESP_LOGD(TAG, "SID %02x BC - %s - Unhandled", service_identifier, format_hex_pretty(message, length).c_str());
ESP_LOGD(TAG, "SID %02X BC - %s - Unhandled", service_identifier, format_hex_pretty(message, length).c_str());
}
}
}
@ -192,16 +188,16 @@ void LinBusProtocol::lin_msg_diag_first_(const u_int8_t *message, u_int8_t lengt
}
}
void LinBusProtocol::lin_msg_diag_consecutive_(const u_int8_t *message, u_int8_t length) {
bool LinBusProtocol::lin_msg_diag_consecutive_(const u_int8_t *message, u_int8_t length) {
if (this->multi_pdu_message_len_ >= this->multi_pdu_message_expected_size_) {
// ignore, because i don't await a consecutive frame
return;
return false;
}
u_int8_t protocol_control_information = message[1];
u_int8_t frame_counter = protocol_control_information & 0x0F;
if (frame_counter != this->multi_pdu_message_frame_counter_) {
// ignore, because i don't await this consecutive frame
return;
return false;
}
this->multi_pdu_message_frame_counter_++;
if (this->multi_pdu_message_frame_counter_ > 0x0F) {
@ -215,6 +211,9 @@ void LinBusProtocol::lin_msg_diag_consecutive_(const u_int8_t *message, u_int8_t
this->multi_pdu_message_[this->multi_pdu_message_len_++] = message[i];
}
}
// Check if this was the last consecutive message.
return this->multi_pdu_message_len_ == this->multi_pdu_message_expected_size_;
}
void LinBusProtocol::lin_msg_diag_multi_() {

View File

@ -1,6 +1,5 @@
#pragma once
#include <vector>
#include <queue>
#include "LinBusListener.h"
@ -9,6 +8,7 @@ namespace truma_inetbox {
class LinBusProtocol : public LinBusListener {
public:
virtual const std::array<u_int8_t, 4> lin_identifier() = 0;
virtual void lin_heartbeat() = 0;
virtual void lin_reset_device() = 0;
protected:
@ -35,7 +35,7 @@ class LinBusProtocol : public LinBusListener {
u_int8_t multi_pdu_message_[64];
void lin_msg_diag_single_(const u_int8_t *message, u_int8_t length);
void lin_msg_diag_first_(const u_int8_t *message, u_int8_t length);
void lin_msg_diag_consecutive_(const u_int8_t *message, u_int8_t length);
bool lin_msg_diag_consecutive_(const u_int8_t *message, u_int8_t length);
void lin_msg_diag_multi_();
};

View File

@ -44,6 +44,7 @@ void TrumaiNetBoxApp::update() {
this->status_clock_updated_ = false;
this->status_config_updated_ = false;
}
LinBusProtocol::update();
}
const std::array<uint8_t, 4> TrumaiNetBoxApp::lin_identifier() {
@ -65,6 +66,8 @@ const std::array<uint8_t, 4> TrumaiNetBoxApp::lin_identifier() {
return {0x17 /*Supplied Id*/, 0x46 /*Supplied Id*/, 0x00 /*Function Id*/, 0x1F /*Function Id*/};
}
void TrumaiNetBoxApp::lin_heartbeat() { this->device_registered_ = micros(); }
void TrumaiNetBoxApp::lin_reset_device() {
this->device_registered_ = micros();
this->init_recieved_ = 0;
@ -186,7 +189,6 @@ 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_ = micros();
if (identifier == 0x00 /* LIN Product Identification */) {
auto lin_identifier = this->lin_identifier();
(*response)[0] = lin_identifier[0];
@ -226,18 +228,17 @@ const u_int8_t *TrumaiNetBoxApp::lin_multiframe_recieved(const u_int8_t *message
}
if (message[0] == LIN_SID_READ_STATE_BUFFER) {
ESP_LOGI(TAG, "Requested read update");
// Example: BA.00.1F.00.1E.00.00.22.FF.FF.FF (11)
memset(response, 0, sizeof(response));
auto response_frame = reinterpret_cast<StatusFrame *>(response);
// The order must match with the method 'has_update_to_submit_'.
if (this->init_recieved_ == 0) {
ESP_LOGI(TAG, "Sending init");
ESP_LOGD(TAG, "Requested read: Sending init");
status_frame_create_init(response_frame, return_len, this->message_counter++);
return response;
} else if (this->update_status_heater_unsubmitted_) {
ESP_LOGI(TAG, "Sending heater update");
ESP_LOGD(TAG, "Requested read: Sending heater update");
status_frame_create_update_heater(
response_frame, return_len, this->message_counter++, this->update_status_heater_.target_temp_room,
this->update_status_heater_.target_temp_water, this->update_status_heater_.heating_mode,
@ -249,7 +250,7 @@ const u_int8_t *TrumaiNetBoxApp::lin_multiframe_recieved(const u_int8_t *message
this->update_status_heater_stale_ = true;
return response;
} else if (this->update_status_timer_unsubmitted_) {
ESP_LOGI(TAG, "Sending timer update");
ESP_LOGD(TAG, "Requested read: Sending timer update");
status_frame_create_update_timer(
response_frame, return_len, this->message_counter++, this->update_status_timer_.timer_resp_active,
this->update_status_timer_.timer_resp_start_hours, this->update_status_timer_.timer_resp_start_minutes,
@ -263,18 +264,21 @@ const u_int8_t *TrumaiNetBoxApp::lin_multiframe_recieved(const u_int8_t *message
this->update_status_timer_unsubmitted_ = false;
this->update_status_timer_stale_ = true;
return response;
#ifdef USE_TIME
} else if (this->update_status_clock_unsubmitted_) {
ESP_LOGI(TAG, "Sending clock update");
// read time live
auto now = this->time_->now();
status_frame_create_update_clock(response_frame, return_len, this->message_counter++, now.hour, now.minute,
now.second, this->status_clock_.clock_mode);
if (this->time_ != nullptr) {
ESP_LOGD(TAG, "Requested read: Sending clock update");
// read time live
auto now = this->time_->now();
status_frame_create_update_clock(response_frame, return_len, this->message_counter++, now.hour, now.minute,
now.second, this->status_clock_.clock_mode);
}
this->update_status_clock_unsubmitted_ = false;
return response;
#endif // USE_TIME
} else {
ESP_LOGW(TAG, "CP Plus asks for an update, but I have none.");
ESP_LOGW(TAG, "Requested read: CP Plus asks for an update, but I have none.");
}
}
@ -337,7 +341,7 @@ const u_int8_t *TrumaiNetBoxApp::lin_multiframe_recieved(const u_int8_t *message
} else {
ESP_LOGI(TAG, "StatusFrameResponseAck");
}
ESP_LOGV(TAG, "StatusFrameResponseAck %02x %s %02x", statusFrame->inner.genericHeader.command_counter,
ESP_LOGV(TAG, "StatusFrameResponseAck %02X %s %02X", statusFrame->inner.genericHeader.command_counter,
data.error_code == ResponseAckResult::RESPONSE_ACK_RESULT_OKAY ? " OKAY " : " FAILED ",
(u_int8_t) data.error_code);
@ -387,13 +391,13 @@ const u_int8_t *TrumaiNetBoxApp::lin_multiframe_recieved(const u_int8_t *message
this->init_recieved_ = micros();
ESP_LOGV(TAG, "StatusFrameDevice %d/%d - %d.%02d.%02d %04x.%02x", device.device_id + 1, device.device_count,
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],
device.hardware_revision_major, device.hardware_revision_minor);
return response;
} else {
ESP_LOGW(TAG, "Unkown message type %02x", header->message_type);
ESP_LOGW(TAG, "Unkown message type %02X", header->message_type);
}
(*return_len) = 1;
return nullptr;
@ -402,26 +406,26 @@ 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_ = micros();
ESP_LOGV(TAG, "Requesting initial data.");
ESP_LOGD(TAG, "Requesting initial data.");
return true;
} else if (this->init_recieved_ == 0) {
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.");
ESP_LOGD(TAG, "Requesting initial data again.");
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.");
ESP_LOGD(TAG, "Notify CP Plus I got updates.");
this->update_time_ = micros();
return true;
}
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.");
ESP_LOGD(TAG, "Notify CP Plus again I still got updates.");
this->update_time_ = micros();
return true;
}

View File

@ -3,7 +3,10 @@
#include <vector>
#include "LinBusProtocol.h"
#include "esphome/core/automation.h"
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
#endif // USE_TIME
namespace esphome {
namespace truma_inetbox {
@ -339,10 +342,12 @@ class TrumaiNetBoxApp : public LinBusProtocol {
void update() override;
const std::array<u_int8_t, 4> lin_identifier() override;
void lin_heartbeat() override;
void lin_reset_device() override;
#ifdef USE_TIME
void set_time(time::RealTimeClock *time) { time_ = time; }
time::RealTimeClock *get_time() const { return time_; }
#endif // USE_TIME
bool get_status_heater_valid() { return this->status_heater_valid_; }
const StatusFrameHeater *get_status_heater() { return &this->status_heater_; }
@ -368,8 +373,10 @@ class TrumaiNetBoxApp : public LinBusProtocol {
StatusFrameTimerResponse *update_timer_prepare();
void update_timer_submit() { this->update_status_timer_unsubmitted_ = true; }
#ifdef USE_TIME
bool truma_clock_can_update() { return this->status_clock_valid_; }
void update_clock_submit() { this->update_status_clock_unsubmitted_ = true; }
#endif // USE_TIME
int64_t get_last_cp_plus_request() { return this->device_registered_; }
@ -388,11 +395,14 @@ class TrumaiNetBoxApp : public LinBusProtocol {
HeatingMode mode = HeatingMode::HEATING_MODE_OFF, u_int8_t water_temperature = 0,
EnergyMix energy_mix = EnergyMix::ENERGY_MIX_NONE,
ElectricPowerLevel el_power_level = ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0);
bool action_read_time();
#ifdef USE_TIME
bool action_write_time();
#endif // USE_TIME
protected:
time::RealTimeClock *time_;
#ifdef USE_TIME
time::RealTimeClock *time_ = nullptr;
#endif // USE_TIME
// Truma CP Plus needs init (reset). This device is not registered.
uint32_t device_registered_ = 0;

View File

@ -217,21 +217,18 @@ bool TrumaiNetBoxApp::action_timer_activate(u_int16_t start, u_int16_t stop, u_i
return true;
}
bool TrumaiNetBoxApp::action_read_time() {
// int ret = settimeofday(&timev, &tz);
// if (ret == EINVAL) {
// // Some ESP8266 frameworks abort when timezone parameter is not NULL
// // while ESP32 expects it not to be NULL
// ret = settimeofday(&timev, nullptr);
// }
return false;
}
#ifdef USE_TIME
bool TrumaiNetBoxApp::action_write_time() {
if (!this->truma_clock_can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
if (this->time_ == nullptr) {
ESP_LOGW(TAG, "Missing system time component.");
return false;
}
auto now = this->time_->now();
if (!now.is_valid()) {
ESP_LOGW(TAG, "Invalid system time, not syncing to CP Plus.");
@ -245,6 +242,7 @@ bool TrumaiNetBoxApp::action_write_time() {
this->update_clock_submit();
return true;
}
#endif // USE_TIME
} // namespace truma_inetbox
} // namespace esphome

View File

@ -1,15 +1,32 @@
from typing import Optional
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome import pins, automation
from esphome.components import sensor, uart, time
from esphome.components import uart, time
from esphome.const import (
CONF_ID,
CONF_NUMBER,
CONF_BAUD_RATE,
CONF_UART_ID,
CONF_RX_PIN,
CONF_TX_PIN,
CONF_INVERTED,
CONF_CS_PIN,
CONF_TEMPERATURE,
CONF_TRIGGER_ID,
CONF_STOP,
CONF_TIME_ID,
CONF_TIME,
)
from esphome.components.uart import (
CONF_STOP_BITS,
CONF_DATA_BITS,
CONF_PARITY,
KEY_UART_DEVICES,
)
from esphome.core import CORE
from .entity_helpers import count_id_usage
DEPENDENCIES = ["uart"]
@ -41,11 +58,154 @@ CONF_SUPPORTED_LIN_CHECKSUM = {
"VERSION_2": LIN_CHECKSUM_dummy_ns.LIN_CHECKSUM_VERSION_2,
}
# [RP2040] Hardware serial of uart validation:
# constexpr uint32_t valid_tx_uart_0 = __bitset({0, 12, 16, 28});
# constexpr uint32_t valid_tx_uart_1 = __bitset({4, 8, 20, 24});
# constexpr uint32_t valid_rx_uart_0 = __bitset({1, 13, 17, 29});
# constexpr uint32_t valid_rx_uart_1 = __bitset({5, 9, 21, 25});
CONF_RP2040_HARDWARE_UART = {
CONF_TX_PIN: {
# Pin : Hardware UART number
0: 0,
12: 0,
16: 0,
28: 0,
4: 1,
8: 1,
20: 1,
24: 1,
},
CONF_RX_PIN: {
# Pin : Hardware UART number
1: 0,
13: 0,
17: 0,
29: 0,
5: 1,
9: 1,
21: 1,
25: 1,
}
}
def final_validate_device_schema(
name: str,
*,
baud_rate: Optional[int] = None,
require_tx: bool = False,
require_rx: bool = False,
stop_bits: Optional[int] = None,
data_bits: Optional[int] = None,
parity: str = None,
require_hardware_uart: Optional[bool] = None,
):
def validate_baud_rate(value):
if value != baud_rate:
raise cv.Invalid(
f"Component {name} required baud rate {baud_rate} for the uart bus"
)
return value
def validate_pin(opt, device):
def validator(value):
if opt in device:
raise cv.Invalid(
f"The uart {opt} is used both by {name} and {device[opt]}, "
f"but can only be used by one. Please create a new uart bus for {name}."
)
device[opt] = name
return value
return validator
def validate_stop_bits(value):
if value != stop_bits:
raise cv.Invalid(
f"Component {name} required stop bits {stop_bits} for the uart bus"
)
return value
def validate_data_bits(value):
if value != data_bits:
raise cv.Invalid(
f"Component {name} required data bits {data_bits} for the uart bus"
)
return value
def validate_parity(value):
if value != parity:
raise cv.Invalid(
f"Component {name} required parity {parity} for the uart bus"
)
return value
def validate_hardware_uart(opt, opt2=None, declaration_config=None):
def validator(value):
if (CORE.is_rp2040):
if value[CONF_INVERTED]:
raise cv.Invalid(
f"Component {name} required Hardware UART. Inverted is not supported by Hardware UART.")
if value[CONF_NUMBER] not in CONF_RP2040_HARDWARE_UART[opt]:
raise cv.Invalid(
f"Component {name} required Hardware UART. {opt} is not a Hardware UART pin.")
if opt2 and declaration_config and CONF_RP2040_HARDWARE_UART[opt2][declaration_config[opt2][CONF_NUMBER]] != CONF_RP2040_HARDWARE_UART[opt][value[CONF_NUMBER]]:
raise cv.Invalid(
f"Component {name} required Hardware UART. {opt} and {opt2} are not a matching Hardware UART pin set.")
return value
return validator
def validate_hub(hub_config):
hub_schema = {}
uart_id = hub_config[CONF_ID]
devices = fv.full_config.get().data.setdefault(KEY_UART_DEVICES, {})
device = devices.setdefault(uart_id, {})
if require_tx:
hub_schema[
cv.Required(
CONF_TX_PIN,
msg=f"Component {name} requires this uart bus to declare a tx_pin",
)
] = validate_pin(CONF_TX_PIN, device)
if require_rx:
hub_schema[
cv.Required(
CONF_RX_PIN,
msg=f"Component {name} requires this uart bus to declare a rx_pin",
)
] = validate_pin(CONF_RX_PIN, device)
if baud_rate is not None:
hub_schema[cv.Required(CONF_BAUD_RATE)] = validate_baud_rate
if stop_bits is not None:
hub_schema[cv.Required(CONF_STOP_BITS)] = validate_stop_bits
if data_bits is not None:
hub_schema[cv.Required(CONF_DATA_BITS)] = validate_data_bits
if parity is not None:
hub_schema[cv.Required(CONF_PARITY)] = validate_parity
if require_hardware_uart is not None:
fconf = fv.full_config.get()
path = fconf.get_path_for_id(uart_id)[:-1]
declaration_config = fconf.get_config_for_path(path)
hub_schema[cv.Required(CONF_TX_PIN)] = validate_hardware_uart(
CONF_TX_PIN)
hub_schema[cv.Required(CONF_RX_PIN)] = validate_hardware_uart(
CONF_RX_PIN, CONF_TX_PIN, declaration_config)
return cv.Schema(hub_schema, extra=cv.ALLOW_EXTRA)(hub_config)
return cv.Schema(
{cv.Required(CONF_UART_ID)
: fv.id_declaration_match_schema(validate_hub)},
extra=cv.ALLOW_EXTRA,
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(TrumaINetBoxApp),
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
cv.Optional(CONF_LIN_CHECKSUM, "VERSION_2"): cv.enum(CONF_SUPPORTED_LIN_CHECKSUM, upper=True),
cv.Optional(CONF_CS_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_FAULT_PIN): pins.gpio_input_pin_schema,
@ -61,24 +221,22 @@ 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_on(["esp32"]),
cv.only_on(["esp32", "rp2040"]),
)
FINAL_VALIDATE_SCHEMA = cv.All(
uart.final_validate_device_schema(
# TODO: Validate 2 Stop bits are configured.
"truma_inetbox", baud_rate=9600, require_tx=True, require_rx=True
),
final_validate_device_schema(
"truma_inetbox", baud_rate=9600, require_tx=True, require_rx=True, stop_bits=2, data_bits=8, parity="NONE", require_hardware_uart=True),
count_id_usage(CONF_NUMBER_OF_CHILDREN, [
CONF_TRUMA_INETBOX_ID, CONF_ID], TrumaINetBoxApp),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID], config[CONF_NUMBER_OF_CHILDREN])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
time_ = await cg.get_variable(config[CONF_TIME_ID])
cg.add(var.set_time(time_))
if (CONF_TIME_ID in config):
time_ = await cg.get_variable(config[CONF_TIME_ID])
cg.add(var.set_time(time_))
if CONF_LIN_CHECKSUM in config:
cg.add(var.set_lin_checksum(
@ -360,7 +518,8 @@ async def truma_inetbox_timer_activate_to_code(config, action_id, template_arg,
automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(TrumaINetBoxApp),
}
},
cv.requires_component(CONF_TIME),
),
)
async def truma_inetbox_clock_set_to_code(config, action_id, template_arg, args):

View File

@ -75,10 +75,12 @@ template<typename... Ts> class TimerActivateAction : public Action<Ts...>, publi
}
};
#ifdef USE_TIME
template<typename... Ts> class WriteTimeAction : public Action<Ts...>, public Parented<TrumaiNetBoxApp> {
public:
void play(Ts... x) override { this->parent_->action_write_time(); }
};
#endif // USE_TIME
class TrumaiNetBoxAppHeaterMessageTrigger : public Trigger<const StatusFrameHeater *> {
public: