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