Used code from oxyde42-patch-1
This commit is contained in:
parent
9a6883b7e7
commit
8224dd24b8
|
|
@ -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<uint8_t>& data,
|
||||
const char separator) {
|
||||
PanasonicHelpers::log_uart_hex(direction, &data[0], data.size(), separator);
|
||||
void PanasonicHelpers::write_uart_log(UartLogDirection direction, const std::vector<uint8_t>& 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) {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ enum UartLogDirection : uint8_t {
|
|||
|
||||
class PanasonicHelpers {
|
||||
public:
|
||||
static void log_uart_hex(UartLogDirection direction, const std::vector<uint8_t>& 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<uint8_t>& 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<uint8_t>& data, const char separator);
|
||||
static std::string byte_array_to_hex_string(const uint8_t* data, const size_t length, const char separator);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<uint8_t>*));
|
||||
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<uint8_t>*));
|
||||
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<PanasonicHeatpumpComponent*>(pvParameters);
|
||||
std::vector<uint8_t> 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<uint8_t>(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<PanasonicHeatpumpComponent*>(pvParameters);
|
||||
std::vector<uint8_t> 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<uint8_t>(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<uint8_t>& 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<uint8_t>& 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<uint8_t>& 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<uint8_t>* 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<uint8_t>& message) {
|
||||
auto* cmd = new std::vector<uint8_t>(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<uint8_t>* 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<uint8_t>& data) {
|
||||
|
|
@ -279,62 +316,31 @@ ResponseType PanasonicHeatpumpComponent::check_response(const std::vector<uint8_
|
|||
// 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.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
|
||||
|
|
|
|||
|
|
@ -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<uint8_t> heatpump_default_message_;
|
||||
std::vector<uint8_t> heatpump_extra_message_;
|
||||
std::vector<uint8_t> response_message_;
|
||||
std::vector<uint8_t> request_message_;
|
||||
std::vector<uint8_t> 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<PanasonicHeatpumpEntity*> binary_sensors_;
|
||||
std::vector<PanasonicHeatpumpEntity*> climates_;
|
||||
|
|
@ -156,10 +152,21 @@ class PanasonicHeatpumpComponent : public PollingComponent, public uart::UARTDev
|
|||
std::vector<PanasonicHeatpumpEntity*> 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<uint8_t>& buffer);
|
||||
|
||||
void send_request();
|
||||
void queue_request(const std::vector<uint8_t>& message);
|
||||
ResponseType process_response();
|
||||
ResponseType check_response(const std::vector<uint8_t>& data);
|
||||
static bool is_valid_header(const std::vector<uint8_t>& frame);
|
||||
static uint8_t get_packet_size(const std::vector<uint8_t>& frame);
|
||||
|
||||
template <size_t N>
|
||||
static std::vector<uint8_t> message_build(const uint8_t (&msg)[N]) {
|
||||
return std::vector<uint8_t>(msg, msg + N);
|
||||
}
|
||||
};
|
||||
} // namespace panasonic_heatpump
|
||||
} // namespace esphome
|
||||
|
|
|
|||
Loading…
Reference in New Issue