From 8224dd24b8eb80972547710e1c0d6d085582beb5 Mon Sep 17 00:00:00 2001 From: ElVit Date: Wed, 18 Feb 2026 10:26:00 +0100 Subject: [PATCH 01/11] Used code from oxyde42-patch-1 --- components/panasonic_heatpump/helpers.cpp | 13 +- components/panasonic_heatpump/helpers.h | 4 +- .../panasonic_heatpump/panasonic_heatpump.cpp | 486 +++++++++--------- .../panasonic_heatpump/panasonic_heatpump.h | 53 +- 4 files changed, 288 insertions(+), 268 deletions(-) diff --git a/components/panasonic_heatpump/helpers.cpp b/components/panasonic_heatpump/helpers.cpp index a2fdaf8..99cd998 100644 --- a/components/panasonic_heatpump/helpers.cpp +++ b/components/panasonic_heatpump/helpers.cpp @@ -4,13 +4,13 @@ namespace esphome { namespace panasonic_heatpump { static const char* const TAG = "panasonic_heatpump"; -void PanasonicHelpers::log_uart_hex(UartLogDirection direction, const std::vector& data, - const char separator) { - PanasonicHelpers::log_uart_hex(direction, &data[0], data.size(), separator); +void PanasonicHelpers::write_uart_log(UartLogDirection direction, const std::vector& data, + const char separator, bool logBytes) { + PanasonicHelpers::write_uart_log(direction, &data[0], data.size(), separator, logBytes); } -void PanasonicHelpers::log_uart_hex(UartLogDirection direction, const uint8_t* data, const size_t length, - const char separator) { +void PanasonicHelpers::write_uart_log(UartLogDirection direction, const uint8_t* data, const size_t length, + const char separator, bool logBytes) { std::string logStr = ""; std::string msgDir = direction == UART_LOG_TX ? ">>>" : "<<<"; std::string msgType = direction == UART_LOG_TX ? "request" : "response"; @@ -31,6 +31,9 @@ void PanasonicHelpers::log_uart_hex(UartLogDirection direction, const uint8_t* d ESP_LOGI(TAG, "%s %s[%i]", msgDir.c_str(), msgType.c_str(), length); delay(10); + if (!logBytes) + return; + logStr += byte_array_to_hex_string(data, length, separator); for (size_t i = 0; i < logStr.length(); i += UART_LOG_CHUNK_SIZE) { diff --git a/components/panasonic_heatpump/helpers.h b/components/panasonic_heatpump/helpers.h index af71f9c..05cc0cf 100644 --- a/components/panasonic_heatpump/helpers.h +++ b/components/panasonic_heatpump/helpers.h @@ -18,8 +18,8 @@ enum UartLogDirection : uint8_t { class PanasonicHelpers { public: - static void log_uart_hex(UartLogDirection direction, const std::vector& data, const char separator); - static void log_uart_hex(UartLogDirection direction, const uint8_t* data, const size_t length, const char separator); + static void write_uart_log(UartLogDirection direction, const std::vector& data, const char separator, bool logBytes); + static void write_uart_log(UartLogDirection direction, const uint8_t* data, const size_t length, const char separator, bool logBytes); static std::string byte_array_to_hex_string(const std::vector& data, const char separator); static std::string byte_array_to_hex_string(const uint8_t* data, const size_t length, const char separator); }; diff --git a/components/panasonic_heatpump/panasonic_heatpump.cpp b/components/panasonic_heatpump/panasonic_heatpump.cpp index d9e75f3..82133f0 100644 --- a/components/panasonic_heatpump/panasonic_heatpump.cpp +++ b/components/panasonic_heatpump/panasonic_heatpump.cpp @@ -7,52 +7,80 @@ static const char* const TAG = "panasonic_heatpump"; void PanasonicHeatpumpComponent::dump_config() { ESP_LOGW(TAG, "*** Panasonic Heatpump Component v%s ***", PANASONIC_HEATPUMP_VERSION); - delay(10); // NOLINT } void PanasonicHeatpumpComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Panasonic Heatpump ..."); - delay(10); // NOLINT this->check_uart_settings(9600, 1, uart::UART_CONFIG_PARITY_EVEN, 8); - this->update(); + this->response_queue_handle_ = xQueueCreate(8, sizeof(std::vector*)); + if (this->response_queue_handle_ == nullptr) { + ESP_LOGE(TAG, "Failed to create response queue!"); + this->mark_failed(); + return; + } + this->request_queue_handle_ = xQueueCreate(8, sizeof(std::vector*)); + if (this->request_queue_handle_ == nullptr) { + ESP_LOGE(TAG, "Failed to create request queue!"); + this->mark_failed(); + return; + } + + // Start task + xTaskCreatePinnedToCore(PanasonicHeatpumpComponent::uart_task, "uart_handler", 4096, this, + tskIDLE_PRIORITY + 1, // Low priority, important for single-core C3 + &this->uart_task_handle_, + tskNO_AFFINITY // important for single-core C3 + ); + if (this->uart_client_ != nullptr) { + xTaskCreatePinnedToCore(PanasonicHeatpumpComponent::uart_client_task, "uart_client_handler", 4096, this, + tskIDLE_PRIORITY + 1, // Low priority, important for single-core C3 + &this->uart_client_task_handle_, + tskNO_AFFINITY // important for single-core C3 + ); + } + + // Disable any self-initiated traffic if uart_client_timeout_ is used as "disable" criterion. + if (this->uart_client_ != nullptr && this->uart_client_timeout_ < 100) { + ESP_LOGI(TAG, "Self polling disabled (uart_client_timeout_ < 100ms). Not sending initial request."); + return; + } } void PanasonicHeatpumpComponent::update() { - if (this->uart_client_ != nullptr) + // Hard disable: never poll if uart_client_timeout_ < 100ms and a client exists + if (this->uart_client_ != nullptr && this->uart_client_timeout_ < 100) { return; - this->next_request_ = this->send_extra_request_ ? RequestType::POLLING_EXTRA : RequestType::POLLING; + } + + if (this->uart_client_ != nullptr && !this->uart_client_timeout_exceeded_) { + if (millis() - this->last_client_request_time_ > uart_client_timeout_) + this->uart_client_timeout_exceeded_ = true; + else + return; + } + + ESP_LOGD(TAG, "Queue polling request"); + this->queue_request(message_build(PanasonicCommand::PollingMessage)); } void PanasonicHeatpumpComponent::loop() { - // Check if no request was sent for uart_client_timeout when uart_client is configured - if (this->uart_client_ != nullptr && this->uart_client_timeout_ > 100) { - uint32_t current_time = millis(); - if (current_time - this->last_request_time_ >= this->uart_client_timeout_) { - this->next_request_ = RequestType::POLLING; - this->uart_client_timeout_exceeded_ = true; - } - } - switch (this->loop_state_) { - case LoopState::READ_RESPONSE: - this->read_response(); - this->loop_state_ = LoopState::CHECK_RESPONSE; - break; - case LoopState::CHECK_RESPONSE: - this->current_response_ = this->check_response(this->response_message_); - switch (this->current_response_) { - case ResponseType::UNKNOWN: - this->loop_state_ = LoopState::SEND_REQUEST; - break; + case LoopState::PROCESS_RESPONSE: { + auto current_response = this->process_response(); + switch (current_response) { case ResponseType::STANDARD: this->loop_state_ = LoopState::PUBLISH_SENSOR; break; case ResponseType::EXTRA: this->loop_state_ = LoopState::PUBLISH_EXTRA_SENSOR; break; + default: + this->loop_state_ = LoopState::SEND_REQUEST; + break; }; break; + } case LoopState::PUBLISH_SENSOR: for (auto* entity : this->sensors_) { entity->publish_new_state(this->heatpump_default_message_); @@ -108,170 +136,179 @@ void PanasonicHeatpumpComponent::loop() { this->loop_state_ = LoopState::SEND_REQUEST; break; case LoopState::SEND_REQUEST: - this->send_request(this->next_request_); - this->loop_state_ = LoopState::READ_REQUEST; - break; - case LoopState::READ_REQUEST: - this->read_request(); - this->loop_state_ = LoopState::RESTART_LOOP; - break; + this->send_request(); + // fallthrough default: - this->loop_state_ = LoopState::READ_RESPONSE; + this->loop_state_ = LoopState::PROCESS_RESPONSE; break; }; } -void PanasonicHeatpumpComponent::read_response() { - while (this->available()) { - // Read each byte from heatpump and forward it directly to the client (CZ-TAW1) - this->read_byte(&byte_); - if (this->uart_client_ != nullptr) { - this->uart_client_->write_byte(byte_); - } +void PanasonicHeatpumpComponent::uart_task(void* pvParameters) { + auto* self = static_cast(pvParameters); + std::vector rx_buffer; + rx_buffer.reserve(256); - // Message shall start with 0x31, 0x71 or 0xF1, if not skip this byte - if (!this->response_receiving_) { - if (byte_ != 0x31 && byte_ != 0x71 && byte_ != 0xF1) - continue; - this->response_message_.clear(); - this->response_receiving_ = true; - } - // Add current byte to message buffer - this->response_message_.push_back(byte_); - - // 2. byte contains the payload size - if (this->response_message_.size() == 2) { - this->payload_length_ = byte_; - } - // 3. byte shall be 0x01 or 0x10 - if (this->response_message_.size() == 3 && byte_ != 0x01 && byte_ != 0x10) { - this->response_receiving_ = false; - ESP_LOGW(TAG, "Invalid response message: 0x%s. Expected last byte to be 0x01 or 0x10", - PanasonicHelpers::byte_array_to_hex_string(this->response_message_, ',').c_str()); - delay(10); // NOLINT - continue; - } - // 4. byte shall be 0x01, 0x10 or 0x21 - if (this->response_message_.size() == 4 && byte_ != 0x01 && byte_ != 0x10 && byte_ != 0x21) { - this->response_receiving_ = false; - ESP_LOGW(TAG, "Invalid response message: 0x%s. Expected last byte to be 0x01, 0x10 or 0x21", - PanasonicHelpers::byte_array_to_hex_string(this->response_message_, ',').c_str()); - delay(10); // NOLINT - continue; - } - - // Check if message is complete - if (this->response_message_.size() > 2 && this->response_message_.size() == this->payload_length_ + 3) { - this->response_receiving_ = false; - this->current_response_count_++; - if (this->log_uart_msg_) - PanasonicHelpers::log_uart_hex(UART_LOG_RX, this->response_message_, ','); - } - } -} - -void PanasonicHeatpumpComponent::send_request(RequestType requestType) { - switch (requestType) { - case RequestType::COMMAND: - if (this->log_uart_msg_) - PanasonicHelpers::log_uart_hex(UART_LOG_TX, this->command_message_, ','); - this->write_array(this->command_message_); - this->flush(); - break; - case RequestType::INITIAL: - // Probably not necessary but CZ-TAW1 sends this query on boot - if (this->log_uart_msg_) - PanasonicHelpers::log_uart_hex(UART_LOG_TX, PanasonicCommand::InitialRequest, INIT_REQUEST_SIZE, ','); - this->write_array(PanasonicCommand::InitialRequest, INIT_REQUEST_SIZE); - this->flush(); - break; - case RequestType::POLLING: - if (this->log_uart_msg_) - PanasonicHelpers::log_uart_hex(UART_LOG_TX, PanasonicCommand::PollingMessage, DATA_MESSAGE_SIZE, ','); - this->write_array(PanasonicCommand::PollingMessage, DATA_MESSAGE_SIZE); - this->flush(); - break; - case RequestType::POLLING_EXTRA: - if (this->log_uart_msg_) - PanasonicHelpers::log_uart_hex(UART_LOG_TX, PanasonicCommand::PollingExtraMessage, DATA_MESSAGE_SIZE, ','); - this->write_array(PanasonicCommand::PollingExtraMessage, DATA_MESSAGE_SIZE); - this->flush(); - break; - }; - - if (requestType != RequestType::NONE && requestType != RequestType::INITIAL) { - // Update last request time when request was sent - this->last_request_time_ = millis(); - } - - this->next_request_ = RequestType::NONE; -} - -void PanasonicHeatpumpComponent::read_request() { - if (this->uart_client_ == nullptr) - return; - - while (this->uart_client_->available()) { - // Read each byte from client and forward it directly to the heatpump - this->uart_client_->read_byte(&byte_); - this->write_byte(byte_); - - // Message shall start with 0x31, 0x71 or 0xF1, if not skip this byte - if (!this->request_receiving_) { - if (byte_ != 0x31 && byte_ != 0x71 && byte_ != 0xF1) - continue; - this->request_message_.clear(); - this->request_receiving_ = true; - } - // Add current byte to message buffer - this->request_message_.push_back(byte_); - - // 2. byte contains the payload size - if (this->request_message_.size() == 2) { - this->payload_length_ = byte_; - } - // 3. byte shall be 0x01 or 0x10 - if (this->request_message_.size() == 3 && byte_ != 0x01 && byte_ != 0x10) { - this->request_receiving_ = false; - ESP_LOGW(TAG, "Invalid request message: 0x%s. Expected last byte to be 0x01 or 0x10", - PanasonicHelpers::byte_array_to_hex_string(this->request_message_, ',').c_str()); - delay(10); // NOLINT - continue; - } - // 4. byte shall be 0x01, 0x10 or 0x21 - if (this->request_message_.size() == 4 && byte_ != 0x01 && byte_ != 0x10 && byte_ != 0x21) { - this->request_receiving_ = false; - ESP_LOGW(TAG, "Invalid request message: 0x%s. Expected last byte to be 0x01, 0x10 or 0x21", - PanasonicHelpers::byte_array_to_hex_string(this->request_message_, ',').c_str()); - delay(10); // NOLINT - continue; - } - - // Check if message is complete - if (this->request_message_.size() > 2 && this->request_message_.size() == this->payload_length_ + 3) { - this->request_receiving_ = false; - if (this->log_uart_msg_) - PanasonicHelpers::log_uart_hex(UART_LOG_TX, this->request_message_, ','); - - if (this->request_message_[0] != 0x31) { - // Update last request time when request is complete - this->last_request_time_ = millis(); - this->uart_client_timeout_exceeded_ = false; + while (true) { + // We process the data from the primary interface + if (self->receive_from_uart(self->parent_, rx_buffer)) { + auto* message = new std::vector(rx_buffer); + if (xQueueSend(self->response_queue_handle_, &message, 0) != pdPASS) { + ESP_LOGW(TAG, "Response queue full, dropping heatpump packet"); + delete message; } + // ... and pass on a copy to CZ-TAW1 + if (self->uart_client_ != nullptr) { + self->uart_client_->write_array(rx_buffer); + } + } else { + vTaskDelay(pdMS_TO_TICKS(10)); } } } -int PanasonicHeatpumpComponent::getResponseByte(const int index) { - if (this->heatpump_default_message_.size() > index) - return this->heatpump_default_message_[index]; - return -1; +void PanasonicHeatpumpComponent::uart_client_task(void* pvParameters) { + auto* self = static_cast(pvParameters); + std::vector rx_buffer; + rx_buffer.reserve(256); + + while (true) { + // We process the data from the client interface + if (self->receive_from_uart(self->uart_client_, rx_buffer)) { + auto* message = new std::vector(rx_buffer); + if (xQueueSend(self->request_queue_handle_, &message, 0) != pdPASS) { + ESP_LOGW(TAG, "Request queue full, dropping CZ-TAW packet"); + delete message; + } + self->last_client_request_time_ = millis(); + self->uart_client_timeout_exceeded_ = false; + } else { + vTaskDelay(pdMS_TO_TICKS(10)); + } + } } -int PanasonicHeatpumpComponent::getExtraResponseByte(const int index) { - if (this->heatpump_extra_message_.size() > index) - return this->heatpump_extra_message_[index]; - return -1; +// used for both uart interfaces +bool PanasonicHeatpumpComponent::receive_from_uart(uart::UARTComponent* uartComp, std::vector& buffer) { + uint8_t start_byte; + // We are in a separate thread, waiting to receive a byte + while (!uartComp->available()) + vTaskDelay(pdMS_TO_TICKS(5)); + + if (!uartComp->read_byte(&start_byte)) + return false; + + // Message shall start with 0x31, 0x71 or 0xF1, if not skip this byte + if (start_byte != 0x31 && start_byte != 0x71 && start_byte != 0xF1) { + return false; + } + + // packet starts, clear buffer + buffer.clear(); + buffer.reserve(256); // reserve space for the whole packet + // read whole header + buffer.resize(HEADER_SIZE); + // insert start byte + buffer[0] = start_byte; + // read the rest of the header + + if (uartComp->available() < HEADER_SIZE - 1) + vTaskDelay(pdMS_TO_TICKS(5)); + + auto succeed = uartComp->read_array(&buffer[1], HEADER_SIZE - 1); + if (!(succeed && is_valid_header(buffer))) { + ESP_LOGD(TAG, "Wrong Packet Header..."); + // timeout, start over + return false; + } + + // got header ... read the rest of the packet + size_t total_expected = get_packet_size(buffer); + size_t remaining = total_expected - buffer.size(); + + while (remaining > 0) { + size_t current_size = buffer.size(); + size_t to_read = std::min((size_t)8, remaining); + buffer.resize(current_size + to_read); + if (uartComp->available() < to_read) + vTaskDelay(pdMS_TO_TICKS(10)); + if (!uartComp->read_array(&buffer[current_size], to_read)) { + // timeout + return false; + } + remaining -= to_read; + } + + // packet is complete, verify checksum + uint8_t checksum = 0; + for (const auto b : buffer) + checksum += b; + + if (checksum != 0) { + ESP_LOGW(TAG, "Invalid message: wrong checksum"); + return false; + } + // packet complete + return true; +} + +bool PanasonicHeatpumpComponent::is_valid_header(const std::vector& frame) { + return frame.size() >= HEADER_SIZE // is it a complete header? + && (frame[2] == 0x01 || frame[2] == 0x10) // 3. byte shall be 0x01 or 0x10 + && (frame[3] == 0x01 || frame[3] == 0x10 || frame[3] == 0x21); // 4. byte shall be 0x01, 0x10 or 0x21 +} + +uint8_t PanasonicHeatpumpComponent::get_packet_size(const std::vector& frame) { + return frame[1] + 3; // three more than stated in the header +} + +void PanasonicHeatpumpComponent::send_request() { + if (millis() - request_send_time_ < REQUEST_SEND_INTERVAL) { + // wait until the interval is over + return; + } + + std::vector* cmd_ptr{nullptr}; + if (xQueueReceive(this->request_queue_handle_, &cmd_ptr, 0) != pdPASS || cmd_ptr == nullptr) { + return; // nothing queued + } + PanasonicHelpers::write_uart_log(UART_LOG_TX, *cmd_ptr, ',', this->log_uart_msg_); + + // Send vector content over UART (robust API usage) + this->write_array(cmd_ptr->data(), cmd_ptr->size()); + delete cmd_ptr; + request_send_time_ = millis(); +} + +void PanasonicHeatpumpComponent::queue_request(const std::vector& message) { + auto* cmd = new std::vector(message); + + // Check request_queue_handle_, function is called before setup() initializes it! + if (this->request_queue_handle_ == nullptr || xQueueSend(this->request_queue_handle_, &cmd, 0) != pdPASS) { + ESP_LOGW(TAG, "Request queue unavailable or full, dropping packet"); + delete cmd; + } +} + +ResponseType PanasonicHeatpumpComponent::process_response() { + // Check if it is a new response and dequeue it for loop processing + std::vector* response_ptr{nullptr}; + + if (xQueueReceive(this->response_queue_handle_, &response_ptr, 0) != pdPASS || response_ptr == nullptr) { + // no response to process, try to send next request + return ResponseType::UNKNOWN; + } + PanasonicHelpers::write_uart_log(UART_LOG_RX, *response_ptr, ',', this->log_uart_msg_); + + auto current_response = this->check_response(*response_ptr); + if (current_response == ResponseType::STANDARD) { + this->heatpump_default_message_ = std::move(*response_ptr); + } else if (current_response == ResponseType::EXTRA) { + this->heatpump_extra_message_ = std::move(*response_ptr); + } + + delete response_ptr; + return current_response; } ResponseType PanasonicHeatpumpComponent::check_response(const std::vector& data) { @@ -279,62 +316,31 @@ ResponseType PanasonicHeatpumpComponent::check_response(const std::vector valid packet - - if (data.empty()) - return ResponseType::UNKNOWN; - if (data[0] != 0x71) - return ResponseType::UNKNOWN; - if (this->response_receiving_) - return ResponseType::UNKNOWN; if (data.size() != RESPONSE_MSG_SIZE) { ESP_LOGW(TAG, "Invalid response message length: recieved %d - expected %d", data.size(), RESPONSE_MSG_SIZE); - delay(10); // NOLINT return ResponseType::UNKNOWN; } - // Verify checksum - uint8_t checksum = 0; - for (int i = 0; i < data.size(); i++) { - checksum += data[i]; - } - // all bytes (including checksum byte) shall be 0x00 - if (checksum != 0) { - ESP_LOGW(TAG, "Invalid response message: checksum = 0x%02X, last_byte = 0x%02X", checksum, data[202]); - delay(10); // NOLINT - return ResponseType::UNKNOWN; - } - - this->send_extra_request_ = data[3] == 0x10 && data[199] > 0x02 && this->send_extra_request_ == false ? true : false; - // Get response type and save the response auto responseType = ResponseType::UNKNOWN; if (data[3] == 0x10) { responseType = ResponseType::STANDARD; - this->heatpump_default_message_ = data; + + // is an extra request required? + if (data[199] > 0x02) { + ESP_LOGD(TAG, "Queue extra polling request"); + this->queue_request(message_build(PanasonicCommand::PollingExtraMessage)); + } + } else if (data[3] == 0x21) { responseType = ResponseType::EXTRA; - this->heatpump_extra_message_ = data; } - if (responseType == ResponseType::UNKNOWN) { - ESP_LOGW(TAG, "Unknown response type (4. byte): 0x%02X. Expected 0x10 or 0x21.", data[3]); - delay(10); // NOLINT - return responseType; - } - - // Check if the current response is a new response - if (this->last_response_count_ == this->current_response_count_) - return ResponseType::UNKNOWN; - this->last_response_count_ = this->current_response_count_; - return responseType; } void PanasonicHeatpumpComponent::set_command_high_nibble(const uint8_t value, const uint8_t index) { - if (this->next_request_ != RequestType::COMMAND) { - // initialize the command - this->command_message_.assign(std::begin(PanasonicCommand::CommandMessage), - std::end(PanasonicCommand::CommandMessage)); - } + this->command_message_ = message_build(PanasonicCommand::CommandMessage); + uint8_t lowNibble = this->heatpump_default_message_[index] & 0b1111; uint8_t highNibble = value << 4; // set command byte @@ -343,16 +349,13 @@ void PanasonicHeatpumpComponent::set_command_high_nibble(const uint8_t value, co this->command_message_.back() = PanasonicCommand::calcChecksum(this->command_message_, this->command_message_.size() - 1); - // command will be send on next loop - this->next_request_ = RequestType::COMMAND; + ESP_LOGD(TAG, "Queue command request"); + this->queue_request(this->command_message_); } void PanasonicHeatpumpComponent::set_command_low_nibble(const uint8_t value, const uint8_t index) { - if (this->next_request_ != RequestType::COMMAND) { - // initialize the command - this->command_message_.assign(std::begin(PanasonicCommand::CommandMessage), - std::end(PanasonicCommand::CommandMessage)); - } + this->command_message_ = message_build(PanasonicCommand::CommandMessage); + uint8_t highNibble = this->heatpump_default_message_[index] & 0b11110000; uint8_t lowNibble = value & 0b1111; // set command byte @@ -361,32 +364,25 @@ void PanasonicHeatpumpComponent::set_command_low_nibble(const uint8_t value, con this->command_message_.back() = PanasonicCommand::calcChecksum(this->command_message_, this->command_message_.size() - 1); - // command will be send on next loop - this->next_request_ = RequestType::COMMAND; + ESP_LOGD(TAG, "Queue command request"); + this->queue_request(this->command_message_); } void PanasonicHeatpumpComponent::set_command_byte(const uint8_t value, const uint8_t index) { - if (this->next_request_ != RequestType::COMMAND) { - // initialize the command - this->command_message_.assign(std::begin(PanasonicCommand::CommandMessage), - std::end(PanasonicCommand::CommandMessage)); - } + this->command_message_ = message_build(PanasonicCommand::CommandMessage); + // set command byte this->command_message_[index] = value; // calculate and set set checksum (last element) this->command_message_.back() = PanasonicCommand::calcChecksum(this->command_message_, this->command_message_.size() - 1); - // command will be send on next loop - this->next_request_ = RequestType::COMMAND; + ESP_LOGD(TAG, "Queue command request"); + this->queue_request(this->command_message_); } void PanasonicHeatpumpComponent::set_command_curve(const uint8_t value, const uint8_t index) { - if (this->next_request_ != RequestType::COMMAND) { - // initialize the command - this->command_message_.assign(std::begin(PanasonicCommand::CommandMessage), - std::end(PanasonicCommand::CommandMessage)); - } + this->command_message_ = message_build(PanasonicCommand::CommandMessage); // Set zone 1 curve bytes if (index == 75 || index == 76 || index == 77 || index == 78 || index == 86 || index == 87 || index == 88 || @@ -419,8 +415,22 @@ void PanasonicHeatpumpComponent::set_command_curve(const uint8_t value, const ui this->command_message_.back() = PanasonicCommand::calcChecksum(this->command_message_, this->command_message_.size() - 1); - // command will be send on next loop - this->next_request_ = RequestType::COMMAND; + ESP_LOGD(TAG, "Queue command request"); + this->queue_request(this->command_message_); +} + +// This function can be used in esphome lambda to get a specific byte +int PanasonicHeatpumpComponent::getResponseByte(const int index) { + if (this->heatpump_default_message_.size() > index) + return this->heatpump_default_message_[index]; + return -1; +} + +// This function can be used in esphome lambda to get a specific byte +int PanasonicHeatpumpComponent::getExtraResponseByte(const int index) { + if (this->heatpump_extra_message_.size() > index) + return this->heatpump_extra_message_[index]; + return -1; } } // namespace panasonic_heatpump } // namespace esphome diff --git a/components/panasonic_heatpump/panasonic_heatpump.h b/components/panasonic_heatpump/panasonic_heatpump.h index fc20c1f..014bfad 100644 --- a/components/panasonic_heatpump/panasonic_heatpump.h +++ b/components/panasonic_heatpump/panasonic_heatpump.h @@ -12,14 +12,13 @@ #include "commands.h" #ifndef PANASONIC_HEATPUMP_VERSION -#define PANASONIC_HEATPUMP_VERSION "0.0.5-beta.2" +#define PANASONIC_HEATPUMP_VERSION "0.0.7-beta.1" #endif namespace esphome { namespace panasonic_heatpump { enum LoopState : uint8_t { - READ_RESPONSE, - CHECK_RESPONSE, + PROCESS_RESPONSE, PUBLISH_SENSOR, PUBLISH_BINARY_SENSOR, PUBLISH_TEXT_SENSOR, @@ -30,7 +29,6 @@ enum LoopState : uint8_t { PUBLISH_WATER_HEATER, PUBLISH_EXTRA_SENSOR, SEND_REQUEST, - READ_REQUEST, RESTART_LOOP, }; @@ -81,7 +79,7 @@ class PanasonicHeatpumpComponent : public PollingComponent, public uart::UARTDev void set_log_uart_msg(bool active) { this->log_uart_msg_ = active; } - // uart message variables to use in lambda functions + // functions to use in esphome lambda int getResponseByte(const int index); int getExtraResponseByte(const int index); // command functions @@ -123,27 +121,25 @@ class PanasonicHeatpumpComponent : public PollingComponent, public uart::UARTDev protected: // options variables - uart::UARTComponent* uart_client_{nullptr}; bool log_uart_msg_{false}; - uint32_t last_request_time_{0}; - uint32_t uart_client_timeout_{10000}; - // uart message variables + uint32_t last_client_request_time_{0}; + const uint32_t REQUEST_SEND_INTERVAL{250}; // 250ms = 0.25s + uint32_t request_send_time_{5000}; // transmit first request 5000ms = 5s after startup + uint32_t uart_client_timeout_{10000}; // 10000ms = 10s + static const size_t HEADER_SIZE = 4; + + // uart message variables, process in main loop + TaskHandle_t uart_task_handle_{nullptr}; + TaskHandle_t uart_client_task_handle_{nullptr}; + QueueHandle_t response_queue_handle_{nullptr}; + QueueHandle_t request_queue_handle_{nullptr}; + uart::UARTComponent* uart_client_{nullptr}; std::vector heatpump_default_message_; std::vector heatpump_extra_message_; - std::vector response_message_; - std::vector request_message_; std::vector command_message_; - uint8_t payload_length_; - uint8_t byte_; - uint8_t current_response_count_{0}; - uint8_t last_response_count_{0}; - bool response_receiving_{false}; - bool request_receiving_{false}; - bool send_extra_request_{false}; bool uart_client_timeout_exceeded_{false}; LoopState loop_state_{LoopState::RESTART_LOOP}; - RequestType next_request_{RequestType::INITIAL}; - ResponseType current_response_{ResponseType::UNKNOWN}; + // entity vectors std::vector binary_sensors_; std::vector climates_; @@ -156,10 +152,21 @@ class PanasonicHeatpumpComponent : public PollingComponent, public uart::UARTDev std::vector extra_sensors_; // uart message functions - void read_response(); - void send_request(RequestType requestType); - void read_request(); + static void uart_task(void* pvParameters); + static void uart_client_task(void* pvParameters); + bool receive_from_uart(uart::UARTComponent* src, std::vector& buffer); + + void send_request(); + void queue_request(const std::vector& message); + ResponseType process_response(); ResponseType check_response(const std::vector& data); + static bool is_valid_header(const std::vector& frame); + static uint8_t get_packet_size(const std::vector& frame); + + template + static std::vector message_build(const uint8_t (&msg)[N]) { + return std::vector(msg, msg + N); + } }; } // namespace panasonic_heatpump } // namespace esphome From 03a4fa501c0c74dde00bf4ced4ac42ce2c772762 Mon Sep 17 00:00:00 2001 From: ElVit Date: Wed, 18 Feb 2026 10:31:13 +0100 Subject: [PATCH 02/11] Fixed clang-format issues --- components/panasonic_heatpump/helpers.cpp | 4 ++-- components/panasonic_heatpump/helpers.h | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/components/panasonic_heatpump/helpers.cpp b/components/panasonic_heatpump/helpers.cpp index 99cd998..a5dbfb1 100644 --- a/components/panasonic_heatpump/helpers.cpp +++ b/components/panasonic_heatpump/helpers.cpp @@ -5,12 +5,12 @@ namespace panasonic_heatpump { static const char* const TAG = "panasonic_heatpump"; void PanasonicHelpers::write_uart_log(UartLogDirection direction, const std::vector& data, - const char separator, bool logBytes) { + const char separator, bool logBytes) { PanasonicHelpers::write_uart_log(direction, &data[0], data.size(), separator, logBytes); } void PanasonicHelpers::write_uart_log(UartLogDirection direction, const uint8_t* data, const size_t length, - const char separator, bool logBytes) { + const char separator, bool logBytes) { std::string logStr = ""; std::string msgDir = direction == UART_LOG_TX ? ">>>" : "<<<"; std::string msgType = direction == UART_LOG_TX ? "request" : "response"; diff --git a/components/panasonic_heatpump/helpers.h b/components/panasonic_heatpump/helpers.h index 05cc0cf..6eeb756 100644 --- a/components/panasonic_heatpump/helpers.h +++ b/components/panasonic_heatpump/helpers.h @@ -18,8 +18,10 @@ enum UartLogDirection : uint8_t { class PanasonicHelpers { public: - static void write_uart_log(UartLogDirection direction, const std::vector& data, const char separator, bool logBytes); - static void write_uart_log(UartLogDirection direction, const uint8_t* data, const size_t length, const char separator, bool logBytes); + static void write_uart_log(UartLogDirection direction, const std::vector& data, const char separator, + bool logBytes); + static void write_uart_log(UartLogDirection direction, const uint8_t* data, const size_t length, const char separator, + bool logBytes); static std::string byte_array_to_hex_string(const std::vector& data, const char separator); static std::string byte_array_to_hex_string(const uint8_t* data, const size_t length, const char separator); }; From 1c99601dc9473251318e9ca31511ac3cc44cc9dd Mon Sep 17 00:00:00 2001 From: ElVit Date: Wed, 18 Feb 2026 10:38:01 +0100 Subject: [PATCH 03/11] Remove ESP8266 unit tests because ESP8266 does only support arduiono framework --- .github/workflows/test_panasonic_heatpump.yml | 2 +- components/maidesite_desk/README.md | 2 +- components/panasonic_heatpump/README.md | 2 +- tests/README.md | 1 - tests/panasonic_heatpump/README.md | 6 +- .../test_panasonic_heatpump_esp8266.yaml | 81 ------------------- .../test_panasonic_heatpump_integration.py | 31 ------- 7 files changed, 5 insertions(+), 120 deletions(-) delete mode 100644 tests/panasonic_heatpump/test_panasonic_heatpump_esp8266.yaml diff --git a/.github/workflows/test_panasonic_heatpump.yml b/.github/workflows/test_panasonic_heatpump.yml index e77a641..8fba2d2 100644 --- a/.github/workflows/test_panasonic_heatpump.yml +++ b/.github/workflows/test_panasonic_heatpump.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - board: [esp8266, esp32s2, esp32c3, full] + board: [esp32s2, esp32c3, full] steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/components/maidesite_desk/README.md b/components/maidesite_desk/README.md index 43dc69f..573c22d 100644 --- a/components/maidesite_desk/README.md +++ b/components/maidesite_desk/README.md @@ -6,7 +6,7 @@ ### What you need -* ESPHome compatible microcontroller (e.g. ESP8266, ESP32, ESP32-S2, ESP32-C3, ...) +* ESPHome compatible microcontroller (e.g. ESP32, ESP32-S2, ESP32-C3, ...) * RJ12 cable (phone cable, RJ11 may also work) ### Wiring diff --git a/components/panasonic_heatpump/README.md b/components/panasonic_heatpump/README.md index d1ece4e..e7d2fb4 100644 --- a/components/panasonic_heatpump/README.md +++ b/components/panasonic_heatpump/README.md @@ -4,7 +4,7 @@ ### What you need -* ESPHome compatible microcontroller (e.g. ESP8266, ESP32, ESP32-S2, ESP32-C3, ...) +* ESPHome compatible microcontroller (e.g. ESP32, ESP32-S2, ESP32-C3, ...) * ADUM1201 Dual Channel Digital Magnetic Isolator (to convert 5V UART signal from the heatpump to 3.3V UART signal of the ESP controller) * CN-CNT cable/connectors to Heatpump/CZ-TAW1 (see [Heishamon](https://github.com/Egyras/HeishaMon) github site for more information) diff --git a/tests/README.md b/tests/README.md index d6ef58e..a7cf92d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -11,7 +11,6 @@ tests/ │ ├── README.md │ ├── test_panasonic_heatpump_unit.py # Unit tests │ ├── test_panasonic_heatpump_integration.py # Integration tests -│ ├── test_panasonic_heatpump_esp8266.yaml # ESP8266 test config │ ├── test_panasonic_heatpump_esp32s2.yaml # ESP32-S2 test config │ ├── test_panasonic_heatpump_esp32c3.yaml # ESP32-C3 test config │ ├── test_panasonic_heatpump_cztaw1.yaml # CZ-TAW1 test config diff --git a/tests/panasonic_heatpump/README.md b/tests/panasonic_heatpump/README.md index 6ae0b5a..9ee8793 100644 --- a/tests/panasonic_heatpump/README.md +++ b/tests/panasonic_heatpump/README.md @@ -14,13 +14,11 @@ This directory contains comprehensive tests for the `panasonic_heatpump` ESPHome ### Integration Tests (`test_panasonic_heatpump_integration.py`) - Tests minimal feature configuration on ESP32 board - Tests full feature configuration on ESP32 board -- Tests configuration on ESP8266 board - Tests configuration on ESP32-S2 board - Tests configuration on ESP32-C3 board - Tests configuration with CZ-TAW1 (UART-proxy) ### Test Configuration Files -- `test_panasonic_heatpump_esp8266.yaml` - ESP8266 Wemos D1 Mini - `test_panasonic_heatpump_esp32s2.yaml` - ESP32-S2 Wemos S2 Mini - `test_panasonic_heatpump_esp32c3.yaml` - ESP32-C3 mini (RISC-V) - `test_panasonic_heatpump_cztaw1.yaml` - UART proxy setup for CZ-TAW1 client support @@ -68,7 +66,7 @@ The GitHub Actions workflow (`.github/workflows/test_panasonic_heatpump.yml`) in ### Jobs 1. **test-build**: Validates and compiles the component - - Tests on multiple board types: ESP8266, ESP32 (full), ESP32-S2, ESP32-C3 and with CZ-TAW1 (UART-proxy) + - Tests on multiple board types: ESP32 (full), ESP32-S2, ESP32-C3 and with CZ-TAW1 (UART-proxy) - Uses esphome/build-action for firmware compilation 2. **lint-code**: Runs clang-format on C++ code @@ -196,7 +194,7 @@ All tests must pass before merging to main branch. The GitHub Actions workflow e - Code follows formatting standards - All unit and integration tests pass - Latest ESPHome versions is supported -- ESP8266 and ESP32 boards are supported +- Only ESP32 boards are supported ## Contributing diff --git a/tests/panasonic_heatpump/test_panasonic_heatpump_esp8266.yaml b/tests/panasonic_heatpump/test_panasonic_heatpump_esp8266.yaml deleted file mode 100644 index 39edccd..0000000 --- a/tests/panasonic_heatpump/test_panasonic_heatpump_esp8266.yaml +++ /dev/null @@ -1,81 +0,0 @@ ---- -# Test configuration for panasonic_heatpump component on ESP8266 -# ESP8266 has limited GPIO pins and only one UART - -esp8266: - board: d1_mini - -esphome: - name: test-panasonic-heatpump-esp8266 - friendly_name: "Test Panasonic Heatpump ESP8266" - -logger: - level: DEBUG - -external_components: - - source: - type: local - path: ../../components - components: [panasonic_heatpump] - -uart: - - id: uart_heatpump - tx_pin: GPIO13 - rx_pin: GPIO15 - baud_rate: 9600 - data_bits: 8 - parity: EVEN - stop_bits: 1 - -# Test basic configuration -panasonic_heatpump: - id: my_heatpump - uart_id: uart_heatpump - -# Test sensor types - first item only -sensor: - - platform: panasonic_heatpump - top1: - name: "Pump Flow" - -# Test binary sensors - first item only -binary_sensor: - - platform: panasonic_heatpump - top0: - name: "Heatpump State" - -# Test text sensors - first item only -text_sensor: - - platform: panasonic_heatpump - top4: - name: "Operating Mode State" - -# Test number controls - first item only -number: - - platform: panasonic_heatpump - set5: - name: "Set Z1 Heat Request Temperature" - -# Test select controls - first item only -select: - - platform: panasonic_heatpump - set2: - name: "Set Holiday Mode" - -# Test switch controls - first item only -switch: - - platform: panasonic_heatpump - set1: - name: "Set Heatpump" - -# Test climate controls - first item only -climate: - - platform: panasonic_heatpump - zone1: - name: "Zone 1" - -# Test water_heater controls - first item only -water_heater: - - platform: panasonic_heatpump - tank: - name: "DHW" diff --git a/tests/panasonic_heatpump/test_panasonic_heatpump_integration.py b/tests/panasonic_heatpump/test_panasonic_heatpump_integration.py index a826887..f189de4 100644 --- a/tests/panasonic_heatpump/test_panasonic_heatpump_integration.py +++ b/tests/panasonic_heatpump/test_panasonic_heatpump_integration.py @@ -12,17 +12,6 @@ import os class TestPanasonicHeatpumpIntegration: """Integration tests using ESPHome CLI.""" - @pytest.fixture - def test_yaml_esp8266(self): - """Get the path to the ESP8266 test YAML file.""" - base_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - return os.path.join( - base_dir, - "tests", - "panasonic_heatpump", - "test_panasonic_heatpump_esp8266.yaml", - ) - @pytest.fixture def test_yaml_esp32(self): """Get the path to the ESP32 test YAML file.""" @@ -64,26 +53,6 @@ class TestPanasonicHeatpumpIntegration: "test_panasonic_heatpump_cztaw1.yaml", ) - def test_validate_esp8266_config(self, test_yaml_esp8266: str): - """Test that the ESP8266 configuration is valid.""" - if not os.path.exists(test_yaml_esp8266): - pytest.skip(f"Test file not found: {test_yaml_esp8266}") - - try: - result = subprocess.run( - ["esphome", "config", test_yaml_esp8266], - capture_output=True, - text=True, - timeout=120, - ) - assert ( - result.returncode == 0 - ), f"ESP8266 config validation failed: {result.stderr}" - except FileNotFoundError: - pytest.skip("ESPHome not installed") - except subprocess.TimeoutExpired: - pytest.fail("ESPHome config validation timed out") - def test_validate_esp32_config(self, test_yaml_esp32: str): """Test that the ESP32 configuration is valid.""" if not os.path.exists(test_yaml_esp32): From 4badb5b4851a43912cffedea7339ed12fcbd6f3b Mon Sep 17 00:00:00 2001 From: ElVit Date: Tue, 24 Feb 2026 20:05:07 +0100 Subject: [PATCH 04/11] Refactor Panasonic Heatpump component: unify response byte retrieval method names and improve UART handling comments --- components/panasonic_heatpump/README.md | 4 +- .../climate/panasonic_heatpump_climate.cpp | 2 +- .../panasonic_heatpump/panasonic_heatpump.cpp | 239 ++++++++++-------- .../panasonic_heatpump/panasonic_heatpump.h | 22 +- .../panasonic_heatpump_water_heater.cpp | 2 +- 5 files changed, 142 insertions(+), 127 deletions(-) diff --git a/components/panasonic_heatpump/README.md b/components/panasonic_heatpump/README.md index e7d2fb4..b9e7501 100644 --- a/components/panasonic_heatpump/README.md +++ b/components/panasonic_heatpump/README.md @@ -624,7 +624,7 @@ sensor: unit_of_measurement: °C lambda: |- // get the requried byte - int byte = my_heatpump->getResponseByte(46); + int byte = my_heatpump->get_response_byte(46); // a valid byte range is 0x00-0xFF // do not update if the byte is invalid if (byte < 0) return {}; @@ -638,7 +638,7 @@ text_sensor: update_interval: 3s lambda: |- // get the requried byte - int byte = my_heatpump->getResponseByte(9); + int byte = my_heatpump->get_response_byte(9); // a valid byte range is 0x00-0xFF // do not update if the byte is invalid if (byte < 0) return {}; diff --git a/components/panasonic_heatpump/climate/panasonic_heatpump_climate.cpp b/components/panasonic_heatpump/climate/panasonic_heatpump_climate.cpp index 963e56c..6482831 100644 --- a/components/panasonic_heatpump/climate/panasonic_heatpump_climate.cpp +++ b/components/panasonic_heatpump/climate/panasonic_heatpump_climate.cpp @@ -31,7 +31,7 @@ climate::ClimateTraits PanasonicHeatpumpClimate::traits() { void PanasonicHeatpumpClimate::control(const climate::ClimateCall& call) { if (call.get_mode().has_value()) { - int byte6 = this->parent_->getResponseByte(6); + int byte6 = this->parent_->get_response_byte(6); if (byte6 >= 0) { climate::ClimateMode new_mode = *call.get_mode(); uint8_t newByte6 = this->setClimateMode(new_mode, (uint8_t)byte6); diff --git a/components/panasonic_heatpump/panasonic_heatpump.cpp b/components/panasonic_heatpump/panasonic_heatpump.cpp index 82133f0..a77dcd8 100644 --- a/components/panasonic_heatpump/panasonic_heatpump.cpp +++ b/components/panasonic_heatpump/panasonic_heatpump.cpp @@ -40,7 +40,6 @@ void PanasonicHeatpumpComponent::setup() { ); } - // Disable any self-initiated traffic if uart_client_timeout_ is used as "disable" criterion. if (this->uart_client_ != nullptr && this->uart_client_timeout_ < 100) { ESP_LOGI(TAG, "Self polling disabled (uart_client_timeout_ < 100ms). Not sending initial request."); return; @@ -48,11 +47,12 @@ void PanasonicHeatpumpComponent::setup() { } void PanasonicHeatpumpComponent::update() { - // Hard disable: never poll if uart_client_timeout_ < 100ms and a client exists - if (this->uart_client_ != nullptr && this->uart_client_timeout_ < 100) { + // Do not send polling requests if a uart client (CZ-TAW1) is configured and timeout is set too low. + if (this->uart_client_ != nullptr && this->uart_client_timeout_ < 100) return; - } + // If a uart client (CZ-TAW1) is configured, check if the last request from the client is too long ago. + // If so, send polling request to heatpump again. if (this->uart_client_ != nullptr && !this->uart_client_timeout_exceeded_) { if (millis() - this->last_client_request_time_ > uart_client_timeout_) this->uart_client_timeout_exceeded_ = true; @@ -61,14 +61,13 @@ void PanasonicHeatpumpComponent::update() { } ESP_LOGD(TAG, "Queue polling request"); - this->queue_request(message_build(PanasonicCommand::PollingMessage)); + this->queue_request(build_message(PanasonicCommand::PollingMessage)); } void PanasonicHeatpumpComponent::loop() { switch (this->loop_state_) { - case LoopState::PROCESS_RESPONSE: { - auto current_response = this->process_response(); - switch (current_response) { + case LoopState::READ_RESPONSE: { + switch (this->read_response()) { case ResponseType::STANDARD: this->loop_state_ = LoopState::PUBLISH_SENSOR; break; @@ -139,7 +138,7 @@ void PanasonicHeatpumpComponent::loop() { this->send_request(); // fallthrough default: - this->loop_state_ = LoopState::PROCESS_RESPONSE; + this->loop_state_ = LoopState::READ_RESPONSE; break; }; } @@ -150,11 +149,11 @@ void PanasonicHeatpumpComponent::uart_task(void* pvParameters) { rx_buffer.reserve(256); while (true) { - // We process the data from the primary interface + // Process the data from the UART interface connected to the heatpump if (self->receive_from_uart(self->parent_, rx_buffer)) { auto* message = new std::vector(rx_buffer); if (xQueueSend(self->response_queue_handle_, &message, 0) != pdPASS) { - ESP_LOGW(TAG, "Response queue full, dropping heatpump packet"); + ESP_LOGW(TAG, "Response queue full or unavailable, dropping message"); delete message; } // ... and pass on a copy to CZ-TAW1 @@ -173,11 +172,11 @@ void PanasonicHeatpumpComponent::uart_client_task(void* pvParameters) { rx_buffer.reserve(256); while (true) { - // We process the data from the client interface + // Process the data from the UART interface connected to the client (CZ-TAW1) if (self->receive_from_uart(self->uart_client_, rx_buffer)) { auto* message = new std::vector(rx_buffer); if (xQueueSend(self->request_queue_handle_, &message, 0) != pdPASS) { - ESP_LOGW(TAG, "Request queue full, dropping CZ-TAW packet"); + ESP_LOGW(TAG, "Request queue full or unavailable, dropping message"); delete message; } self->last_client_request_time_ = millis(); @@ -188,13 +187,15 @@ void PanasonicHeatpumpComponent::uart_client_task(void* pvParameters) { } } -// used for both uart interfaces +// Used for both uart interfaces bool PanasonicHeatpumpComponent::receive_from_uart(uart::UARTComponent* uartComp, std::vector& buffer) { uint8_t start_byte; - // We are in a separate thread, waiting to receive a byte + + // Wait for the start byte to be available while (!uartComp->available()) vTaskDelay(pdMS_TO_TICKS(5)); + // Read the first byte if (!uartComp->read_byte(&start_byte)) return false; @@ -203,27 +204,24 @@ bool PanasonicHeatpumpComponent::receive_from_uart(uart::UARTComponent* uartComp return false; } - // packet starts, clear buffer + // Prepare buffer for header reading, header is 4 bytes long and first byte is already read. + // Message may be up to 256 bytes long, so reserve enough space to avoid dynamic resizing during reading. buffer.clear(); - buffer.reserve(256); // reserve space for the whole packet - // read whole header + buffer.reserve(256); buffer.resize(HEADER_SIZE); - // insert start byte buffer[0] = start_byte; - // read the rest of the header + // Read the rest of the header if (uartComp->available() < HEADER_SIZE - 1) vTaskDelay(pdMS_TO_TICKS(5)); - auto succeed = uartComp->read_array(&buffer[1], HEADER_SIZE - 1); - if (!(succeed && is_valid_header(buffer))) { - ESP_LOGD(TAG, "Wrong Packet Header..."); - // timeout, start over - return false; - } - // got header ... read the rest of the packet - size_t total_expected = get_packet_size(buffer); + // Verify header (start byte, message type and length) + if (!verify_message_header(buffer, succeed)) + return false; + + // Read the rest of the message according to the length specified in the header + size_t total_expected = buffer[1] + 3; size_t remaining = total_expected - buffer.size(); while (remaining > 0) { @@ -233,33 +231,97 @@ bool PanasonicHeatpumpComponent::receive_from_uart(uart::UARTComponent* uartComp if (uartComp->available() < to_read) vTaskDelay(pdMS_TO_TICKS(10)); if (!uartComp->read_array(&buffer[current_size], to_read)) { - // timeout + ESP_LOGW(TAG, "Timeout while reading message body"); return false; } remaining -= to_read; } - // packet is complete, verify checksum - uint8_t checksum = 0; - for (const auto b : buffer) - checksum += b; - - if (checksum != 0) { - ESP_LOGW(TAG, "Invalid message: wrong checksum"); + // Verify checksum (should be 0 if all bytes are summed up) + if (!verify_message_checksum(buffer)) { return false; } - // packet complete + + // message is complete return true; } -bool PanasonicHeatpumpComponent::is_valid_header(const std::vector& frame) { - return frame.size() >= HEADER_SIZE // is it a complete header? - && (frame[2] == 0x01 || frame[2] == 0x10) // 3. byte shall be 0x01 or 0x10 - && (frame[3] == 0x01 || frame[3] == 0x10 || frame[3] == 0x21); // 4. byte shall be 0x01, 0x10 or 0x21 +bool PanasonicHeatpumpComponent::verify_message_header(const std::vector& message, bool reading_succeeded) { + if (!reading_succeeded) { + ESP_LOGW(TAG, "Timeout while reading message header"); + return false; + } + + if (message.size() < HEADER_SIZE) { + ESP_LOGW(TAG, "Message too short to contain valid header"); + return false; + } + + if ((message[2] != 0x01 && message[2] != 0x10) || // 3. byte shall be 0x01 or 0x10 + (message[3] != 0x01 && message[3] != 0x10 && message[3] != 0x21)) { // 4. byte shall be 0x01, 0x10 or 0x21 + ESP_LOGW(TAG, "Invalid message header: 0x%s. Drop message.", + PanasonicHelpers::byte_array_to_hex_string(message, ',').c_str()); + return false; + } + + return true; } -uint8_t PanasonicHeatpumpComponent::get_packet_size(const std::vector& frame) { - return frame[1] + 3; // three more than stated in the header +bool PanasonicHeatpumpComponent::verify_message_checksum(const std::vector& message) { + uint8_t checksum = 0; + for (const auto b : message) + checksum += b; + + if (checksum != 0) { + ESP_LOGW(TAG, "Invalid message checksum: 0x%02X. Last byte: 0x%02X", checksum, message.back()); + return false; + } + return true; +} + +ResponseType PanasonicHeatpumpComponent::read_response() { + // Get message from queue + std::vector* message{nullptr}; + if (xQueueReceive(this->response_queue_handle_, &message, 0) != pdPASS || message == nullptr) { + return ResponseType::UNKNOWN; // nothing queued + } + PanasonicHelpers::write_uart_log(UART_LOG_RX, *message, ',', this->log_uart_msg_); + + if (!this->check_response_length(*message)) { + delete message; + return ResponseType::UNKNOWN; + } + + // Get response type and save the response + auto responseType = ResponseType::UNKNOWN; + if (message->at(3) == 0x10) { + responseType = ResponseType::STANDARD; + this->heatpump_default_message_ = std::move(*message); + + // Is an extra request required? + if (message->at(199) > 0x02) { + ESP_LOGD(TAG, "Queue extra polling request"); + this->queue_request(build_message(PanasonicCommand::PollingExtraMessage)); + } + } else if (message->at(3) == 0x21) { + responseType = ResponseType::EXTRA; + this->heatpump_extra_message_ = std::move(*message); + } + + delete message; + return responseType; +} + +bool PanasonicHeatpumpComponent::check_response_length(const std::vector& message) { + // Read response message: + // format: 0x71 [payload_length] 0x01 [0x10 || 0x21] [[TOP0 - TOP114] ...] 0x00 [checksum] + // payload_length: payload_length + 3 = packet_length + // checksum: if (sum(all bytes) & 0xFF == 0) ==> valid packet + if (message.size() == RESPONSE_MSG_SIZE) + return true; + + ESP_LOGW(TAG, "Invalid response message length: recieved %d - expected %d", message.size(), RESPONSE_MSG_SIZE); + return false; } void PanasonicHeatpumpComponent::send_request() { @@ -268,78 +330,21 @@ void PanasonicHeatpumpComponent::send_request() { return; } - std::vector* cmd_ptr{nullptr}; - if (xQueueReceive(this->request_queue_handle_, &cmd_ptr, 0) != pdPASS || cmd_ptr == nullptr) { + // Get message from queue + std::vector* message{nullptr}; + if (xQueueReceive(this->request_queue_handle_, &message, 0) != pdPASS || message == nullptr) { return; // nothing queued } - PanasonicHelpers::write_uart_log(UART_LOG_TX, *cmd_ptr, ',', this->log_uart_msg_); + PanasonicHelpers::write_uart_log(UART_LOG_TX, *message, ',', this->log_uart_msg_); // Send vector content over UART (robust API usage) - this->write_array(cmd_ptr->data(), cmd_ptr->size()); - delete cmd_ptr; + this->write_array(message->data(), message->size()); + delete message; request_send_time_ = millis(); } -void PanasonicHeatpumpComponent::queue_request(const std::vector& message) { - auto* cmd = new std::vector(message); - - // Check request_queue_handle_, function is called before setup() initializes it! - if (this->request_queue_handle_ == nullptr || xQueueSend(this->request_queue_handle_, &cmd, 0) != pdPASS) { - ESP_LOGW(TAG, "Request queue unavailable or full, dropping packet"); - delete cmd; - } -} - -ResponseType PanasonicHeatpumpComponent::process_response() { - // Check if it is a new response and dequeue it for loop processing - std::vector* response_ptr{nullptr}; - - if (xQueueReceive(this->response_queue_handle_, &response_ptr, 0) != pdPASS || response_ptr == nullptr) { - // no response to process, try to send next request - return ResponseType::UNKNOWN; - } - PanasonicHelpers::write_uart_log(UART_LOG_RX, *response_ptr, ',', this->log_uart_msg_); - - auto current_response = this->check_response(*response_ptr); - if (current_response == ResponseType::STANDARD) { - this->heatpump_default_message_ = std::move(*response_ptr); - } else if (current_response == ResponseType::EXTRA) { - this->heatpump_extra_message_ = std::move(*response_ptr); - } - - delete response_ptr; - return current_response; -} - -ResponseType PanasonicHeatpumpComponent::check_response(const std::vector& data) { - // Read response message: - // format: 0x71 [payload_length] 0x01 [0x10 || 0x21] [[TOP0 - TOP114] ...] 0x00 [checksum] - // payload_length: payload_length + 3 = packet_length - // checksum: if (sum(all bytes) & 0xFF == 0) ==> valid packet - if (data.size() != RESPONSE_MSG_SIZE) { - ESP_LOGW(TAG, "Invalid response message length: recieved %d - expected %d", data.size(), RESPONSE_MSG_SIZE); - return ResponseType::UNKNOWN; - } - - // Get response type and save the response - auto responseType = ResponseType::UNKNOWN; - if (data[3] == 0x10) { - responseType = ResponseType::STANDARD; - - // is an extra request required? - if (data[199] > 0x02) { - ESP_LOGD(TAG, "Queue extra polling request"); - this->queue_request(message_build(PanasonicCommand::PollingExtraMessage)); - } - - } else if (data[3] == 0x21) { - responseType = ResponseType::EXTRA; - } - return responseType; -} - void PanasonicHeatpumpComponent::set_command_high_nibble(const uint8_t value, const uint8_t index) { - this->command_message_ = message_build(PanasonicCommand::CommandMessage); + this->command_message_ = build_message(PanasonicCommand::CommandMessage); uint8_t lowNibble = this->heatpump_default_message_[index] & 0b1111; uint8_t highNibble = value << 4; @@ -354,7 +359,7 @@ void PanasonicHeatpumpComponent::set_command_high_nibble(const uint8_t value, co } void PanasonicHeatpumpComponent::set_command_low_nibble(const uint8_t value, const uint8_t index) { - this->command_message_ = message_build(PanasonicCommand::CommandMessage); + this->command_message_ = build_message(PanasonicCommand::CommandMessage); uint8_t highNibble = this->heatpump_default_message_[index] & 0b11110000; uint8_t lowNibble = value & 0b1111; @@ -369,7 +374,7 @@ void PanasonicHeatpumpComponent::set_command_low_nibble(const uint8_t value, con } void PanasonicHeatpumpComponent::set_command_byte(const uint8_t value, const uint8_t index) { - this->command_message_ = message_build(PanasonicCommand::CommandMessage); + this->command_message_ = build_message(PanasonicCommand::CommandMessage); // set command byte this->command_message_[index] = value; @@ -382,7 +387,7 @@ void PanasonicHeatpumpComponent::set_command_byte(const uint8_t value, const uin } void PanasonicHeatpumpComponent::set_command_curve(const uint8_t value, const uint8_t index) { - this->command_message_ = message_build(PanasonicCommand::CommandMessage); + this->command_message_ = build_message(PanasonicCommand::CommandMessage); // Set zone 1 curve bytes if (index == 75 || index == 76 || index == 77 || index == 78 || index == 86 || index == 87 || index == 88 || @@ -419,15 +424,25 @@ void PanasonicHeatpumpComponent::set_command_curve(const uint8_t value, const ui this->queue_request(this->command_message_); } +void PanasonicHeatpumpComponent::queue_request(const std::vector& message) { + auto* cmd = new std::vector(message); + + // Check request_queue_handle_, function is called before setup() initializes it! + if (this->request_queue_handle_ == nullptr || xQueueSend(this->request_queue_handle_, &cmd, 0) != pdPASS) { + ESP_LOGW(TAG, "Request queue full or unavailable, dropping message"); + delete cmd; + } +} + // This function can be used in esphome lambda to get a specific byte -int PanasonicHeatpumpComponent::getResponseByte(const int index) { +int PanasonicHeatpumpComponent::get_response_byte(const int index) { if (this->heatpump_default_message_.size() > index) return this->heatpump_default_message_[index]; return -1; } // This function can be used in esphome lambda to get a specific byte -int PanasonicHeatpumpComponent::getExtraResponseByte(const int index) { +int PanasonicHeatpumpComponent::get_extra_response_byte(const int index) { if (this->heatpump_extra_message_.size() > index) return this->heatpump_extra_message_[index]; return -1; diff --git a/components/panasonic_heatpump/panasonic_heatpump.h b/components/panasonic_heatpump/panasonic_heatpump.h index 014bfad..8f57438 100644 --- a/components/panasonic_heatpump/panasonic_heatpump.h +++ b/components/panasonic_heatpump/panasonic_heatpump.h @@ -18,7 +18,7 @@ namespace esphome { namespace panasonic_heatpump { enum LoopState : uint8_t { - PROCESS_RESPONSE, + READ_RESPONSE, PUBLISH_SENSOR, PUBLISH_BINARY_SENSOR, PUBLISH_TEXT_SENSOR, @@ -80,8 +80,8 @@ class PanasonicHeatpumpComponent : public PollingComponent, public uart::UARTDev this->log_uart_msg_ = active; } // functions to use in esphome lambda - int getResponseByte(const int index); - int getExtraResponseByte(const int index); + int get_response_byte(const int index); + int get_extra_response_byte(const int index); // command functions void set_command_high_nibble(const uint8_t value, const uint8_t index); void set_command_low_nibble(const uint8_t value, const uint8_t index); @@ -123,9 +123,9 @@ class PanasonicHeatpumpComponent : public PollingComponent, public uart::UARTDev // options variables bool log_uart_msg_{false}; uint32_t last_client_request_time_{0}; - const uint32_t REQUEST_SEND_INTERVAL{250}; // 250ms = 0.25s - uint32_t request_send_time_{5000}; // transmit first request 5000ms = 5s after startup - uint32_t uart_client_timeout_{10000}; // 10000ms = 10s + uint32_t uart_client_timeout_{10000}; // 10 sec + uint32_t request_send_time_{5000}; // 5 sec --> default is 5 sec so first request is not sent too fast after startup + static const uint32_t REQUEST_SEND_INTERVAL{250}; // 250 ms static const size_t HEADER_SIZE = 4; // uart message variables, process in main loop @@ -158,13 +158,13 @@ class PanasonicHeatpumpComponent : public PollingComponent, public uart::UARTDev void send_request(); void queue_request(const std::vector& message); - ResponseType process_response(); - ResponseType check_response(const std::vector& data); - static bool is_valid_header(const std::vector& frame); - static uint8_t get_packet_size(const std::vector& frame); + ResponseType read_response(); + static bool check_response_length(const std::vector& message); + static bool verify_message_header(const std::vector& message, bool reading_succeeded); + static bool verify_message_checksum(const std::vector& message); template - static std::vector message_build(const uint8_t (&msg)[N]) { + static std::vector build_message(const uint8_t (&msg)[N]) { return std::vector(msg, msg + N); } }; diff --git a/components/panasonic_heatpump/water_heater/panasonic_heatpump_water_heater.cpp b/components/panasonic_heatpump/water_heater/panasonic_heatpump_water_heater.cpp index 7073d0d..755de6f 100644 --- a/components/panasonic_heatpump/water_heater/panasonic_heatpump_water_heater.cpp +++ b/components/panasonic_heatpump/water_heater/panasonic_heatpump_water_heater.cpp @@ -23,7 +23,7 @@ water_heater::WaterHeaterTraits PanasonicHeatpumpWaterHeater::traits() { void PanasonicHeatpumpWaterHeater::control(const water_heater::WaterHeaterCall& call) { if (call.get_mode().has_value()) { - int byte6 = this->parent_->getResponseByte(6); + int byte6 = this->parent_->get_response_byte(6); if (byte6 >= 0) { water_heater::WaterHeaterMode new_mode = *call.get_mode(); uint8_t newByte6 = this->setWaterHeaterMode(new_mode, (uint8_t)byte6); From a3123071d099ccc285a62dc27605f22777450a45 Mon Sep 17 00:00:00 2001 From: ElVit Date: Sat, 21 Mar 2026 14:36:07 +0100 Subject: [PATCH 05/11] Fixed error in read_response --- .../panasonic_heatpump/panasonic_heatpump.cpp | 35 ++++++++++++------- .../panasonic_heatpump/panasonic_heatpump.h | 2 +- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/components/panasonic_heatpump/panasonic_heatpump.cpp b/components/panasonic_heatpump/panasonic_heatpump.cpp index a77dcd8..16fc2d6 100644 --- a/components/panasonic_heatpump/panasonic_heatpump.cpp +++ b/components/panasonic_heatpump/panasonic_heatpump.cpp @@ -201,29 +201,33 @@ bool PanasonicHeatpumpComponent::receive_from_uart(uart::UARTComponent* uartComp // Message shall start with 0x31, 0x71 or 0xF1, if not skip this byte if (start_byte != 0x31 && start_byte != 0x71 && start_byte != 0xF1) { + ESP_LOGW(TAG, "Invalid start byte: 0x%x", start_byte); return false; } - // Prepare buffer for header reading, header is 4 bytes long and first byte is already read. - // Message may be up to 256 bytes long, so reserve enough space to avoid dynamic resizing during reading. + // Prepare buffer for header reading. + // Header is 4 bytes long and first byte is already read. + // Message may be up to 256 bytes long, + // so reserve enough space to avoid dynamic resizing during reading. buffer.clear(); buffer.reserve(256); buffer.resize(HEADER_SIZE); buffer[0] = start_byte; - // Read the rest of the header + // Wait for header if (uartComp->available() < HEADER_SIZE - 1) vTaskDelay(pdMS_TO_TICKS(5)); + // Write header to buffer auto succeed = uartComp->read_array(&buffer[1], HEADER_SIZE - 1); // Verify header (start byte, message type and length) if (!verify_message_header(buffer, succeed)) return false; - // Read the rest of the message according to the length specified in the header + // Calculate total message length size_t total_expected = buffer[1] + 3; size_t remaining = total_expected - buffer.size(); - + // Write rest of the message to buffer while (remaining > 0) { size_t current_size = buffer.size(); size_t to_read = std::min((size_t)8, remaining); @@ -237,12 +241,12 @@ bool PanasonicHeatpumpComponent::receive_from_uart(uart::UARTComponent* uartComp remaining -= to_read; } - // Verify checksum (should be 0 if all bytes are summed up) + // Verify checksum if (!verify_message_checksum(buffer)) { return false; } - // message is complete + // Message is complete return true; } @@ -272,6 +276,8 @@ bool PanasonicHeatpumpComponent::verify_message_checksum(const std::vector* message{nullptr}; if (xQueueReceive(this->response_queue_handle_, &message, 0) != pdPASS || message == nullptr) { - return ResponseType::UNKNOWN; // nothing queued + return ResponseType::UNKNOWN; } PanasonicHelpers::write_uart_log(UART_LOG_RX, *message, ',', this->log_uart_msg_); @@ -294,18 +299,22 @@ ResponseType PanasonicHeatpumpComponent::read_response() { // Get response type and save the response auto responseType = ResponseType::UNKNOWN; - if (message->at(3) == 0x10) { + const uint8_t type = (*message)[3]; + if (type == 0x10) { responseType = ResponseType::STANDARD; this->heatpump_default_message_ = std::move(*message); // Is an extra request required? - if (message->at(199) > 0x02) { + if (this->heatpump_default_message_.size() > 199 && this->heatpump_default_message_[199] > 0x02) { ESP_LOGD(TAG, "Queue extra polling request"); this->queue_request(build_message(PanasonicCommand::PollingExtraMessage)); } - } else if (message->at(3) == 0x21) { + } else if (type == 0x21) { responseType = ResponseType::EXTRA; this->heatpump_extra_message_ = std::move(*message); + } else { + ESP_LOGW(TAG, "Unknown response type in byte 3: 0x%02X", type); + responseType = ResponseType::UNKNOWN; } delete message; @@ -320,7 +329,7 @@ bool PanasonicHeatpumpComponent::check_response_length(const std::vector Date: Sat, 21 Mar 2026 17:29:09 +0100 Subject: [PATCH 06/11] Fix clang-format findings --- components/panasonic_heatpump/panasonic_heatpump.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/panasonic_heatpump/panasonic_heatpump.h b/components/panasonic_heatpump/panasonic_heatpump.h index f73ae6a..bb61bb4 100644 --- a/components/panasonic_heatpump/panasonic_heatpump.h +++ b/components/panasonic_heatpump/panasonic_heatpump.h @@ -123,8 +123,8 @@ class PanasonicHeatpumpComponent : public PollingComponent, public uart::UARTDev // options variables bool log_uart_msg_{false}; uint32_t last_client_request_time_{0}; - uint32_t uart_client_timeout_{10000}; // 10 sec - uint32_t request_send_time_{5000}; // 5 sec --> default is 5 sec so first request is not sent too fast after startup + uint32_t uart_client_timeout_{10000}; // 10 sec + uint32_t request_send_time_{5000}; // 5 sec --> default is 5 sec so first request is not sent too fast after startup static const uint32_t REQUEST_SEND_INTERVAL{250}; // 250 ms static const size_t HEADER_SIZE = 4; From cea24e01c63d3eaa5c9436b3fdc41a6fe328df95 Mon Sep 17 00:00:00 2001 From: ElVit Date: Sun, 22 Mar 2026 22:45:17 +0100 Subject: [PATCH 07/11] Update README.md --- components/panasonic_heatpump/README.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/components/panasonic_heatpump/README.md b/components/panasonic_heatpump/README.md index 6107082..c2673fc 100644 --- a/components/panasonic_heatpump/README.md +++ b/components/panasonic_heatpump/README.md @@ -7,10 +7,25 @@ * ESPHome compatible microcontroller (e.g. ESP32, ESP32-S2, ESP32-C3, ...) * ADUM1201 Dual Channel Digital Magnetic Isolator (to convert 5V UART signal from the heatpump to 3.3V UART signal of the ESP controller) -* CN-CNT cable/connectors to Heatpump/CZ-TAW1 (see [Heishamon](https://github.com/Egyras/HeishaMon) github site for more information) +* CN-CNT cable/connectors to Heatpump/CZ-TAW1 (see [Heishamon](https://github.com/heishamon/HeishaMon) github site for more information) * For example: [S05B-XASK-1 JST Connector](https://a.aliexpress.com/_EvkmGVo) * For example: [XAP-05V-1 5Pin Cable with Female to Female Connector](https://a.aliexpress.com/_ExPT82E) +> [!IMPORTANT] +> The support for ESP8266 and the aruino framework is deprecated for this component. +> Since v0.0.8 this component uses threads to process the UART communication. +> But if you still want to use an ESP8266 controller or the arduino framework, +> please use the branch "heatpump/esp8266". + +```yaml +external_components: + - source: + type: git + url: https://github.com/ElVit/esphome_components + ref: 'heatpump/esp8266' + components: [ panasonic_heatpump ] +``` + ### Wiring ![wiring_adum1201.png](../../prototypes/panasonic_heatpump/wiring_adum1201.png) @@ -629,7 +644,7 @@ water_heater: ## Custom Entities (For Advanced Users) -If you review the [ProtocolByteDecrypt.md](https://github.com/Egyras/HeishaMon/blob/master/ProtocolByteDecrypt.md) file you will find also some TOPs and SETs which are not implemented yet in heishamon. +If you review the [ProtocolByteDecrypt.md](https://github.com/heishamon/HeishaMon/blob/main/ProtocolByteDecrypt.md) file you will find also some TOPs and SETs which are not implemented yet in heishamon. They are usually marked as TOP (without a number). The nice part of ESPHome is that it is so highly customizeable. So if you want some additional TOP or SET entities you can easily create your own. @@ -688,5 +703,5 @@ After a power on the heatpump should respond to the requests. ## Sources -:heart: A big THANKS to [Egyras](https://github.com/Egyras) and the work done on the repository [HeishaMon](https://github.com/Egyras/HeishaMon) for decoding the panasonic uart protocol and providing information to build hardware based on an ESP Chip. +:heart: A big THANKS to [Egyras](https://github.com/Egyras), [IgorYbema](https://github.com/IgorYbema) and the work done on the repository [HeishaMon](https://github.com/heishamon/HeishaMon) for decoding the panasonic uart protocol and providing information to build hardware based on an ESP Chip. :heart: Thanks to the whole home assistant community for sharing their knowlege and helping me to create this ESPHome component! From 4ef92f76dc8c3f16fc5acf1dabd2f80f991c9c81 Mon Sep 17 00:00:00 2001 From: ElVit Date: Sun, 22 Mar 2026 22:47:29 +0100 Subject: [PATCH 08/11] Updated README.md --- components/panasonic_heatpump/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/components/panasonic_heatpump/README.md b/components/panasonic_heatpump/README.md index c2673fc..61c1e02 100644 --- a/components/panasonic_heatpump/README.md +++ b/components/panasonic_heatpump/README.md @@ -16,15 +16,15 @@ > Since v0.0.8 this component uses threads to process the UART communication. > But if you still want to use an ESP8266 controller or the arduino framework, > please use the branch "heatpump/esp8266". - -```yaml -external_components: - - source: - type: git - url: https://github.com/ElVit/esphome_components - ref: 'heatpump/esp8266' - components: [ panasonic_heatpump ] -``` +> +> ```yaml +> external_components: +> - source: +> type: git +> url: https://github.com/ElVit/esphome_components +> ref: 'heatpump/esp8266' +> components: [ panasonic_heatpump ] +> ``` ### Wiring From 9c1b4a352138e11b174635cfb3a04562f6a02743 Mon Sep 17 00:00:00 2001 From: ElVit Date: Mon, 23 Mar 2026 20:00:23 +0100 Subject: [PATCH 09/11] Update README.md --- components/panasonic_heatpump/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/panasonic_heatpump/README.md b/components/panasonic_heatpump/README.md index 61c1e02..abd74f8 100644 --- a/components/panasonic_heatpump/README.md +++ b/components/panasonic_heatpump/README.md @@ -12,7 +12,7 @@ * For example: [XAP-05V-1 5Pin Cable with Female to Female Connector](https://a.aliexpress.com/_ExPT82E) > [!IMPORTANT] -> The support for ESP8266 and the aruino framework is deprecated for this component. +> The support for ESP8266 and the arduino framework is deprecated for this component. > Since v0.0.8 this component uses threads to process the UART communication. > But if you still want to use an ESP8266 controller or the arduino framework, > please use the branch "heatpump/esp8266". From 9a65c91b448ebb4bc93ee6c8a3e0743977a3cf85 Mon Sep 17 00:00:00 2001 From: ElVit Date: Mon, 23 Mar 2026 20:01:39 +0100 Subject: [PATCH 10/11] Update README.md --- components/panasonic_heatpump/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/panasonic_heatpump/README.md b/components/panasonic_heatpump/README.md index abd74f8..19937b5 100644 --- a/components/panasonic_heatpump/README.md +++ b/components/panasonic_heatpump/README.md @@ -12,10 +12,10 @@ * For example: [XAP-05V-1 5Pin Cable with Female to Female Connector](https://a.aliexpress.com/_ExPT82E) > [!IMPORTANT] -> The support for ESP8266 and the arduino framework is deprecated for this component. -> Since v0.0.8 this component uses threads to process the UART communication. -> But if you still want to use an ESP8266 controller or the arduino framework, -> please use the branch "heatpump/esp8266". +> The support for ESP8266 and the arduino framework is deprecated for this component. +> Since v0.0.8 this component uses threads to process the UART communication. +> But if you still want to use an ESP8266 controller or the arduino framework, +> please use the branch "heatpump/esp8266". > > ```yaml > external_components: From 4f82851ae10ded4a17ce46a11c94ad20cd6c05a0 Mon Sep 17 00:00:00 2001 From: ElVit Date: Thu, 26 Mar 2026 19:56:57 +0100 Subject: [PATCH 11/11] Renamed branch "esp8266" to "arduino" --- components/panasonic_heatpump/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/panasonic_heatpump/README.md b/components/panasonic_heatpump/README.md index b9dbf22..8056e0b 100644 --- a/components/panasonic_heatpump/README.md +++ b/components/panasonic_heatpump/README.md @@ -15,14 +15,14 @@ > The support for ESP8266 and the arduino framework is deprecated for this component. > Since v0.0.8 this component uses threads to process the UART communication. > But if you still want to use an ESP8266 controller or the arduino framework, -> please use the branch "heatpump/esp8266". +> please use the branch "heatpump/arduino". > > ```yaml > external_components: > - source: > type: git > url: https://github.com/ElVit/esphome_components -> ref: 'heatpump/esp8266' +> ref: 'heatpump/arduino' > components: [ panasonic_heatpump ] > ```