From 422fc3469e907117cf95457b5bae242b3e2757c8 Mon Sep 17 00:00:00 2001 From: Automation Date: Sun, 7 Sep 2025 23:09:51 +0200 Subject: [PATCH] feat(master): add guarded LIN master TX (ESP32 Arduino), B2 scanner API, and scheduler --- components/truma_inetbox/LinBusListener.h | 15 ++++++ .../LinBusListener_esp32_arduino.cpp | 26 ++++++++++- components/truma_inetbox/TrumaiNetBoxApp.cpp | 46 +++++++++++++++++-- components/truma_inetbox/TrumaiNetBoxApp.h | 11 +++-- components/truma_inetbox/__init__.py | 6 ++- 5 files changed, 92 insertions(+), 12 deletions(-) diff --git a/components/truma_inetbox/LinBusListener.h b/components/truma_inetbox/LinBusListener.h index 15cb8d8..1ff3798 100644 --- a/components/truma_inetbox/LinBusListener.h +++ b/components/truma_inetbox/LinBusListener.h @@ -50,6 +50,18 @@ class LinBusListener : public PollingComponent, public uart::UARTDevice { void process_lin_msg_queue(TickType_t xTicksToWait); void process_log_queue(TickType_t xTicksToWait); + // Streaming configuration (UDP) + void set_stream_enabled(bool val) { this->stream_enabled_ = val; } + void set_stream_diag_only(bool val) { this->stream_diag_only_ = val; } + void set_stream_keepalive_ms(uint32_t val) { this->stream_keepalive_ms_ = val; } + void set_udp_stream_host(const std::string &host) { this->udp_host_ = host; } + void set_udp_stream_port(uint16_t port) { this->udp_port_ = port; } + + // Master (LIN initiator) configuration + void set_master_mode(bool val) { this->master_mode_ = val; } + void set_master_nad(uint8_t nad) { this->master_nad_ = nad; } + void set_writes_armed(bool val) { this->writes_armed_ = val; } + #ifdef USE_RP2040 // Return is the expected wait time till next data check is recommended. u_int32_t onSerialEvent(); @@ -115,6 +127,9 @@ class LinBusListener : public PollingComponent, public uart::UARTDevice { void read_lin_frame_(); void clear_uart_buffer_(); void setup_framework(); + void maybe_send_stream_(const QUEUE_LOG_MSG &log_msg); + // Low-level: send a LIN master frame (break + sync + pid + data + crc) + bool write_lin_master_frame_(uint8_t pid, const uint8_t *data, uint8_t len); uint8_t lin_msg_static_queue_storage[TRUMA_MSG_QUEUE_LENGTH * sizeof(QUEUE_LIN_MSG)]; StaticQueue_t lin_msg_static_queue_; diff --git a/components/truma_inetbox/LinBusListener_esp32_arduino.cpp b/components/truma_inetbox/LinBusListener_esp32_arduino.cpp index d92fc0e..abb2d04 100644 --- a/components/truma_inetbox/LinBusListener_esp32_arduino.cpp +++ b/components/truma_inetbox/LinBusListener_esp32_arduino.cpp @@ -83,4 +83,28 @@ void LinBusListener::eventTask_(void *args) { #undef QUEUE_WAIT_BLOCKING #undef ESPHOME_UART -#endif // USE_ESP32_FRAMEWORK_ARDUINO \ No newline at end of file +#endif // USE_ESP32_FRAMEWORK_ARDUINO +bool LinBusListener::write_lin_master_frame_(uint8_t pid, const uint8_t *data, uint8_t len) { + if (!this->master_mode_) return false; + if (len > 8) return false; + auto uartComp = static_cast(this->parent_); + auto uart_num = uartComp->get_hw_serial_number(); + auto hw_serial = uartComp->get_hw_serial(); + + // Send BREAK using ESP-IDF uart driver, then SYNC (0x55), PID, data, checksum + // Best effort: not all cores expose the same API; try common call. + // Begin BREAK + uart_tx_break((uart_port_t) uart_num, 13); + // SYNC + hw_serial->write(0x55); + // PID with parity (upper 2 bits) + uint8_t pid_with_parity = (pid & 0x3F) | (addr_parity(pid) << 6); + hw_serial->write(pid_with_parity); + + // Data + uint8_t crc = data_checksum(data, len, 0); + if (len > 0) hw_serial->write((uint8_t*)data, len); + hw_serial->write(crc); + hw_serial->flush(); + return true; +} diff --git a/components/truma_inetbox/TrumaiNetBoxApp.cpp b/components/truma_inetbox/TrumaiNetBoxApp.cpp index 976f489..5908ecd 100644 --- a/components/truma_inetbox/TrumaiNetBoxApp.cpp +++ b/components/truma_inetbox/TrumaiNetBoxApp.cpp @@ -30,9 +30,15 @@ void TrumaiNetBoxApp::update() { this->heater_.update(); this->timer_.update(); - LinBusProtocol::update(); - -#ifdef USE_TIME + // Master TX scheduler (throttle to ~20ms spacing) + if (this->master_mode_ && !this->master_tx_queue_.empty()) { + uint32_t now = micros(); + if (now - this->last_master_send_us_ > 20000) { + auto req = this->master_tx_queue_.front(); this->master_tx_queue_.pop(); + this->write_lin_master_frame_(req.pid, req.data, req.len); + this->last_master_send_us_ = now; + } + }\r\n#ifdef USE_TIME // Update time of CP Plus automatically when // - Time component configured // - Update was not done @@ -403,4 +409,36 @@ bool TrumaiNetBoxApp::has_update_to_submit_() { } } // namespace truma_inetbox -} // namespace esphome \ No newline at end of file +} // namespace esphome +bool TrumaiNetBoxApp::master_send_diag_single(uint8_t nad, const std::vector &payload) { + if (!this->master_mode_) return false; + if (payload.size() == 0 || payload.size() > 6) return false; // SID + up to 5 bytes + MasterReq r{}; + r.pid = 0x3C; // DIAGNOSTIC_FRAME_MASTER + r.len = 8; + r.data[0] = nad; + r.data[1] = (uint8_t)payload.size(); // PCI: single frame, length + // Note: using low-nibble length style is sufficient; upper nibble zero. + // payload starts at data[2] + for (size_t i = 0; i < payload.size(); i++) r.data[2+i] = payload[i]; + // pad remaining bytes with 0x00 + for (uint8_t i = 2 + payload.size(); i < 8; i++) r.data[i] = 0x00; + this->master_tx_queue_.push(r); + return true; +} + +bool TrumaiNetBoxApp::master_scan_b2(uint8_t nad, uint8_t ident_start, uint8_t ident_end) { + if (!this->master_mode_) return false; + auto ident = this->lin_identifier(); + for (uint16_t id = ident_start; id <= ident_end; id++) { + std::vector pl; pl.reserve(6); + pl.push_back(0xB2); // SID Read-by-Identifier + pl.push_back((uint8_t)id); // Identifier + pl.push_back(ident[0]); // 4-byte selector + pl.push_back(ident[1]); + pl.push_back(ident[2]); + pl.push_back(ident[3]); + this->master_send_diag_single(nad, pl); + } + return true; +} diff --git a/components/truma_inetbox/TrumaiNetBoxApp.h b/components/truma_inetbox/TrumaiNetBoxApp.h index 19c1860..f0f7298 100644 --- a/components/truma_inetbox/TrumaiNetBoxApp.h +++ b/components/truma_inetbox/TrumaiNetBoxApp.h @@ -25,9 +25,9 @@ class TrumaiNetBoxApp : public LinBusProtocol { const std::array lin_identifier() override; void lin_heartbeat() override; - void lin_reset_device() override; - - TRUMA_DEVICE get_heater_device() const { return this->heater_device_; } + // Master scanner API + bool master_send_diag_single(uint8_t nad, const std::vector &payload); + bool master_scan_b2(uint8_t nad, uint8_t ident_start, uint8_t ident_end);\r\n TRUMA_DEVICE get_heater_device() const { return this->heater_device_; } TRUMA_DEVICE get_aircon_device() const { return this->aircon_device_; } TrumaiNetBoxAppAirconAuto *get_aircon_auto() { return &this->airconAuto_; } @@ -79,8 +79,9 @@ class TrumaiNetBoxApp : public LinBusProtocol { const u_int8_t *lin_multiframe_recieved(const u_int8_t *message, const u_int8_t message_len, u_int8_t *return_len) override; - bool has_update_to_submit_(); -}; + \r\n\r\n struct MasterReq { uint8_t pid; uint8_t len; uint8_t data[9]; }; + std::queue master_tx_queue_; + uint32_t last_master_send_us_ = 0;\r\n}; } // namespace truma_inetbox } // namespace esphome \ No newline at end of file diff --git a/components/truma_inetbox/__init__.py b/components/truma_inetbox/__init__.py index a6ffd60..0b187c5 100644 --- a/components/truma_inetbox/__init__.py +++ b/components/truma_inetbox/__init__.py @@ -18,8 +18,10 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_STOP, CONF_TIME_ID, - CONF_TIME, -) + +CONF_MASTER_MODE = "master_mode" +CONF_WRITES_ARMED = "writes_armed" +CONF_MASTER_NAD = "master_nad") from esphome.components.uart import ( CONF_STOP_BITS, CONF_DATA_BITS,