Merge pull request #6 from ElVit/panasonic_dev

Added feature to change min/max values of entity SET5/6/7/8 depending on the current state of entity TOP76/81.
Improved loop time.
Added new heatpump models (taken from HeishaMon).
This commit is contained in:
ElVit 2025-06-11 15:54:07 +02:00 committed by GitHub
commit 24e7f16f9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 357 additions and 139 deletions

View File

@ -9,7 +9,10 @@ namespace esphome
0x31, 0x05, 0x10, 0x01, 0x00, 0x00, 0x00, 0xB9 0x31, 0x05, 0x10, 0x01, 0x00, 0x00, 0x00, 0xB9
}; };
const uint8_t PanasonicCommand::InitialResponse[INIT_RESPONSE_SIZE] = { const uint8_t PanasonicCommand::InitialResponse[INIT_RESPONSE_SIZE] = {
0x31, 0x30, 0x01, 0x10, 0x1E, 0x6C, 0xC8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C 0x31, 0x30, 0x01, 0x10, 0x1E, 0x6C, 0xC8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x3C
}; };
const uint8_t PanasonicCommand::PollingMessage[DATA_MESSAGE_SIZE] = { const uint8_t PanasonicCommand::PollingMessage[DATA_MESSAGE_SIZE] = {
0x71, 0x6C, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x6C, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

View File

@ -78,8 +78,11 @@ namespace esphome
{ 0xE2, 0xCF, 0x0C, 0x74, 0x09, 0x12, 0xD0, 0x0C, 0x95, 0x05 }, // 47 { 0xE2, 0xCF, 0x0C, 0x74, 0x09, 0x12, 0xD0, 0x0C, 0x95, 0x05 }, // 47
{ 0xE2, 0xD5, 0x0B, 0x34, 0x99, 0x83, 0x92, 0x0C, 0x28, 0x98 }, // 48 { 0xE2, 0xD5, 0x0B, 0x34, 0x99, 0x83, 0x92, 0x0C, 0x28, 0x98 }, // 48
{ 0xE2, 0xCF, 0x0D, 0x77, 0x09, 0x12, 0xD0, 0x0C, 0x05, 0x11 }, // 49 { 0xE2, 0xCF, 0x0D, 0x77, 0x09, 0x12, 0xD0, 0x0C, 0x05, 0x11 }, // 49
{ 0xE2, 0xCF, 0x0B, 0x44, 0x34, 0x12, 0xD0, 0x0C, 0x34, 0x35 }, // 50
{ 0xE2, 0xD5, 0x0C, 0x67, 0x00, 0x83, 0x92, 0x0C, 0x27, 0x98 }, // 51 { 0xE2, 0xD5, 0x0C, 0x67, 0x00, 0x83, 0x92, 0x0C, 0x27, 0x98 }, // 51
{ 0xE2, 0xD5, 0x0B, 0x34, 0x99, 0x83, 0x92, 0x0C, 0x27, 0x98 }, // 52 { 0xE2, 0xD5, 0x0B, 0x34, 0x99, 0x83, 0x92, 0x0C, 0x27, 0x98 }, // 52
{ 0x42, 0xD4, 0x0B, 0x83, 0x71, 0x32, 0xD2, 0x0C, 0x44, 0x55 }, // 53
{ 0xE2, 0xCF, 0x0C, 0x74, 0x09, 0x12, 0xD0, 0x0E, 0x94, 0x05 }, // 54
}; };
int PanasonicDecode::getBit1(uint8_t input) int PanasonicDecode::getBit1(uint8_t input)

View File

@ -4,7 +4,7 @@
#include <vector> #include <vector>
#define RESPONSE_MSG_SIZE 203 #define RESPONSE_MSG_SIZE 203
#define NUMBER_OF_MODELS 53 #define NUMBER_OF_MODELS 55
namespace esphome namespace esphome
@ -60,13 +60,14 @@ namespace esphome
static const constexpr char* const ZonesSensorType[] = { "4", "Water Temperature", "External Thermostat", "Internal Thermostat", "Thermistor" }; static const constexpr char* const ZonesSensorType[] = { "4", "Water Temperature", "External Thermostat", "Internal Thermostat", "Thermistor" };
static const constexpr char* const Quietmode[] = { "4", "Off", "Level 1", "Level 2", "Level 3" }; static const constexpr char* const Quietmode[] = { "4", "Off", "Level 1", "Level 2", "Level 3" };
static const constexpr char* const Powerfulmode[] = { "4", "Off", "30min", "60min", "90min" }; static const constexpr char* const Powerfulmode[] = { "4", "Off", "30min", "60min", "90min" };
static const constexpr char* const OpModeDesc[] = { "11", "Heat only", "Cool only", "Auto", "Auto(heat)", "Auto(cool)", "DHW only", "Heat+DHW", "Cool+DHW", "Auto+DHW", "Auto(heat)+DHW", "Auto(cool)+DHW" }; static const constexpr char* const OpModeDesc[] = { "11", "Heat only", "Cool only", "Auto", "Auto(heat)", "Auto(cool)", "DHW only",
"Heat+DHW", "Cool+DHW", "Auto+DHW", "Auto(heat)+DHW", "Auto(cool)+DHW" };
static const constexpr char* const ZonesState[] = { "3", "Zone 1", "Zone 2", "Zone 1 & 2" }; static const constexpr char* const ZonesState[] = { "3", "Zone 1", "Zone 2", "Zone 1 & 2" };
static const constexpr char* const ExtPadHeaterType[] = { "3", "Disabled", "Type-A", "Type-B" }; static const constexpr char* const ExtPadHeaterType[] = { "3", "Disabled", "Type-A", "Type-B" };
static const constexpr char* const Bivalent[] = { "3", "Alternative", "Parallel", "Advanced Parallel" }; static const constexpr char* const Bivalent[] = { "3", "Alternative", "Parallel", "Advanced Parallel" };
static const constexpr char* const ModelNames[] = static const constexpr char* const ModelNames[] =
{ {
"53", //string representation of number of known models (last model number + 1) "55", // string representation of number of known models (last model number + 1)
"WH-MDC05H3E5", // 0 "WH-MDC05H3E5", // 0
"WH-MDC07H3E5", // 1 "WH-MDC07H3E5", // 1
"IDU:WH-SXC09H3E5, ODU:WH-UX09HE5", // 2 "IDU:WH-SXC09H3E5, ODU:WH-UX09HE5", // 2
@ -117,8 +118,11 @@ namespace esphome
"IDU:WH-ADC0916H9E8, ODU:WH-UX12HE8", // 47 "IDU:WH-ADC0916H9E8, ODU:WH-UX12HE8", // 47
"IDU:WH-SDC0509L3E5, ODU:WH-WDG07LE5", // 48 "IDU:WH-SDC0509L3E5, ODU:WH-WDG07LE5", // 48
"IDU:WH-SXC09H3E5, ODU:WH-UX09HE5", // 49 "IDU:WH-SXC09H3E5, ODU:WH-UX09HE5", // 49
"WH-MXC12H9E8", // 50
"IDU:WH-ADC0509L3E5AN, ODU:WH-WDG05LE5", // 51 "IDU:WH-ADC0509L3E5AN, ODU:WH-WDG05LE5", // 51
"IDU:WH-SDC0509L3E5, ODU:WH-WDG05LE5", // 52 "IDU:WH-SDC0509L3E5, ODU:WH-WDG05LE5", // 52
"IDU:WH-ADC0309J3E5C, ODU:WH-UD03JE5", // 53
"IDU:WH-ADC0916H9E8, ODU:WH-UX09HE8", // 54
}; };
}; };

View File

@ -0,0 +1,51 @@
#include "helpers.h"
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::log_uart_hex(UartLogDirection direction, const uint8_t* data, const size_t length, const char separator)
{
std::string logStr = "";
std::string msgDir = direction == UART_LOG_TX ? ">>>" : "<<<";
std::string msgType = direction == UART_LOG_TX ? "request" : "response";
switch(data[0])
{
case 0x31:
msgType = "initial_" + msgType;
break;
case 0x71:
msgType = "polling_" + msgType;
break;
case 0xF1:
msgType = "command_" + msgType;
break;
};
ESP_LOGI(TAG, "%s %s[%i]", msgDir.c_str(), msgType.c_str(), length);
delay(10);
char buffer[5];
for (size_t i = 0; i < length; i++)
{
if (i > 0) logStr += separator;
sprintf(buffer, "%02X", data[i]);
logStr += buffer;
}
for (size_t i = 0; i < logStr.length(); i += UART_LOG_CHUNK_SIZE)
{
ESP_LOGI(TAG, "%s %s", msgDir.c_str(), logStr.substr(i, UART_LOG_CHUNK_SIZE).c_str());
delay(10);
}
}
} // namespace panasonic_heatpump
} // namespace esphome

View File

@ -0,0 +1,28 @@
#pragma once
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <vector>
#include <string>
#define UART_LOG_CHUNK_SIZE 120
namespace esphome
{
namespace panasonic_heatpump
{
enum UartLogDirection
{
UART_LOG_RX,
UART_LOG_TX,
};
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);
};
}
}

View File

@ -82,10 +82,10 @@ TYPES = [
# min_value, max_value, step # min_value, max_value, step
CONF_NUMBERS = [ CONF_NUMBERS = [
[ -5, 5, 1, ], [ 0, 0, 1, ],
[ -5, 5, 1, ], [ 0, 0, 1, ],
[ -5, 5, 1, ], [ 0, 0, 1, ],
[ -5, 5, 1, ], [ 0, 0, 1, ],
[ 40, 75, 1, ], [ 40, 75, 1, ],
[ 64, 254, 1, ], [ 64, 254, 1, ],
[ -15, 35, 1, ], [ -15, 35, 1, ],

View File

@ -1,4 +1,5 @@
#include "panasonic_heatpump.h" #include "panasonic_heatpump.h"
#include "esphome/core/application.h"
namespace esphome namespace esphome
@ -10,41 +11,110 @@ namespace esphome
void PanasonicHeatpumpComponent::dump_config() void PanasonicHeatpumpComponent::dump_config()
{ {
ESP_LOGCONFIG(TAG, "Panasonic Heatpump"); ESP_LOGCONFIG(TAG, "Panasonic Heatpump");
delay(10); delay(10); // NOLINT
} }
void PanasonicHeatpumpComponent::setup() void PanasonicHeatpumpComponent::setup()
{ {
ESP_LOGCONFIG(TAG, "Setting up Panasonic Heatpump ..."); ESP_LOGCONFIG(TAG, "Setting up Panasonic Heatpump ...");
delay(10); delay(10); // NOLINT
this->check_uart_settings(9600, 1, uart::UART_CONFIG_PARITY_EVEN, 8); this->check_uart_settings(9600, 1, uart::UART_CONFIG_PARITY_EVEN, 8);
// Trigger initial request this->update();
if (this->uart_client_ == nullptr)
{
this->next_request_ = 0;
this->trigger_request_ = true;
}
else
{
this->trigger_request_ = false;
}
} }
void PanasonicHeatpumpComponent::update() void PanasonicHeatpumpComponent::update()
{ {
if (this->uart_client_ == nullptr) if (this->uart_client_ == nullptr)
this->trigger_request_ = true; this->next_request_ = RequestType::POLLING;
} }
void PanasonicHeatpumpComponent::loop() void PanasonicHeatpumpComponent::loop()
{ {
this->read_response(); switch (this->loop_state_)
this->send_request(); {
this->read_request(); case LoopState::READ_RESPONSE:
{
this->read_response();
this->loop_state_ = LoopState::CHECK_RESPONSE;
break;
}
case LoopState::CHECK_RESPONSE:
{
bool result = this->check_response(this->heatpump_message_);
this->loop_state_ = result ?
LoopState::SET_NUMBER_TRAITS : LoopState::SEND_REQUEST;
break;
}
case LoopState::SET_NUMBER_TRAITS:
{
this->set_number_traits(this->heatpump_message_);
this->loop_state_ = LoopState::PUBLISH_SENSOR;
break;
}
case LoopState::PUBLISH_SENSOR:
{
this->publish_sensor(this->heatpump_message_);
this->loop_state_ = LoopState::PUBLISH_BINARY_SENSOR;
break;
}
case LoopState::PUBLISH_BINARY_SENSOR:
{
this->publish_binary_sensor(this->heatpump_message_);
this->loop_state_ = LoopState::PUBLISH_TEXT_SENSOR;
break;
}
case LoopState::PUBLISH_TEXT_SENSOR:
{
this->publish_text_sensor(this->heatpump_message_);
this->loop_state_ = LoopState::PUBLISH_NUMBER;
break;
}
case LoopState::PUBLISH_NUMBER:
{
this->publish_number(this->heatpump_message_);
this->loop_state_ = LoopState::PUBLISH_SELECT;
break;
}
case LoopState::PUBLISH_SELECT:
{
this->publish_select(this->heatpump_message_);
this->loop_state_ = LoopState::PUBLISH_SWITCH;
break;
}
case LoopState::PUBLISH_SWITCH:
{
this->publish_switch(this->heatpump_message_);
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;
}
default:
{
// Perform reboot only if a traits (e.g. min/max value of a number entity) was changed the second time.
// The first traits change should happen before controller is connected to home assistant,
// because the default min/max value is 0.
if (this->trait_update_counter_ > 1)
{
ESP_LOGW(TAG, "Min/max values have changed. Rebooting so Home Assistant reconfigures the number component.");
delay(100); // NOLINT
App.safe_reboot();
}
// Next request will be polling this->loop_state_ = LoopState::READ_RESPONSE;
this->next_request_ = 1; break;
this->trigger_request_ = false; }
};
} }
void PanasonicHeatpumpComponent::read_response() void PanasonicHeatpumpComponent::read_response()
@ -71,7 +141,7 @@ namespace esphome
// 2. byte contains the payload size // 2. byte contains the payload size
if (this->response_message_.size() == 2) if (this->response_message_.size() == 2)
{ {
this->response_payload_length_ = byte_; this->payload_length_ = byte_;
} }
// Discard message if format is wrong // Discard message if format is wrong
if ((this->response_message_.size() == 3 || if ((this->response_message_.size() == 3 ||
@ -81,49 +151,51 @@ namespace esphome
this->response_receiving_ = false; this->response_receiving_ = false;
ESP_LOGW(TAG, "Invalid response message: %d. byte is 0x%02X but expexted is 0x01 or 0x10", ESP_LOGW(TAG, "Invalid response message: %d. byte is 0x%02X but expexted is 0x01 or 0x10",
response_message_.size(), byte_); response_message_.size(), byte_);
delay(10); delay(10); // NOLINT
continue; continue;
} }
// Check if message is complete // Check if message is complete
if (this->response_message_.size() > 2 && if (this->response_message_.size() > 2 &&
this->response_message_.size() == this->response_payload_length_ + 3) this->response_message_.size() == this->payload_length_ + 3)
{ {
this->temp_message_ = this->response_message_; this->heatpump_message_ = this->response_message_;
this->response_receiving_ = false; this->response_receiving_ = false;
this->log_uart_hex(UART_LOG_RX, this->response_message_, ','); this->current_response_count_++;
this->decode_response(this->response_message_);; if (this->log_uart_msg_) PanasonicHelpers::log_uart_hex(UART_LOG_RX, this->response_message_, ',');
} }
} }
} }
void PanasonicHeatpumpComponent::send_request() void PanasonicHeatpumpComponent::send_request(RequestType requestType)
{ {
if (this->trigger_request_ == false) return; switch (requestType)
if (this->next_request_ == 2) // command
{ {
this->log_uart_hex(UART_LOG_TX, this->command_message_, ','); case RequestType::COMMAND:
this->write_array(this->command_message_); {
this->flush(); if (this->log_uart_msg_) PanasonicHelpers::log_uart_hex(UART_LOG_TX, this->command_message_, ',');
return; 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;
}
};
if (this->uart_client_ != nullptr) return; this->next_request_ = RequestType::NONE;
if (this->next_request_ == 0) // initial
{
// Probably not necessary but CZ-TAW1 sends this query on boot
this->log_uart_hex(UART_LOG_TX, PanasonicCommand::InitialRequest, INIT_REQUEST_SIZE, ',');
this->write_array(PanasonicCommand::InitialRequest, INIT_REQUEST_SIZE);
this->flush();
}
else if (this->next_request_ == 1) // polling
{
this->log_uart_hex(UART_LOG_TX, PanasonicCommand::PollingMessage, DATA_MESSAGE_SIZE, ',');
this->write_array(PanasonicCommand::PollingMessage, DATA_MESSAGE_SIZE);
this->flush();
}
} }
void PanasonicHeatpumpComponent::read_request() void PanasonicHeatpumpComponent::read_request()
@ -149,7 +221,7 @@ namespace esphome
// 2. byte contains the payload size // 2. byte contains the payload size
if (this->request_message_.size() == 2) if (this->request_message_.size() == 2)
{ {
this->request_payload_length_ = byte_; this->payload_length_ = byte_;
} }
// Discard message if format is wrong // Discard message if format is wrong
if ((this->request_message_.size() == 3 || if ((this->request_message_.size() == 3 ||
@ -159,82 +231,41 @@ namespace esphome
this->request_receiving_ = false; this->request_receiving_ = false;
ESP_LOGW(TAG, "Invalid request message: %d. byte is 0x%02X but expexted is 0x01 or 0x10", ESP_LOGW(TAG, "Invalid request message: %d. byte is 0x%02X but expexted is 0x01 or 0x10",
request_message_.size(), byte_); request_message_.size(), byte_);
delay(10); delay(10); // NOLINT
continue; continue;
} }
// Check if message is complete // Check if message is complete
if (this->request_message_.size() > 2 && if (this->request_message_.size() > 2 &&
this->request_message_.size() == this->request_payload_length_ + 3) this->request_message_.size() == this->payload_length_ + 3)
{ {
this->request_receiving_ = false; this->request_receiving_ = false;
this->log_uart_hex(UART_LOG_TX, this->request_message_, ','); if (this->log_uart_msg_) PanasonicHelpers::log_uart_hex(UART_LOG_TX, this->request_message_, ',');
} }
} }
} }
int PanasonicHeatpumpComponent::getResponseByte(const int index) int PanasonicHeatpumpComponent::getResponseByte(const int index)
{ {
if (this->heatpump_message_.size() > index) return this->heatpump_message_[index];
if (this->response_message_.size() > index) return this->response_message_[index]; if (this->response_message_.size() > index) return this->response_message_[index];
if (this->temp_message_.size() > index) return this->temp_message_[index];
return -1; return -1;
} }
void PanasonicHeatpumpComponent::log_uart_hex(UartLogDirection direction, const std::vector<uint8_t>& data, const char separator) bool PanasonicHeatpumpComponent::check_response(const std::vector<uint8_t>& data)
{
this->log_uart_hex(direction, &data[0], data.size(), separator);
}
void PanasonicHeatpumpComponent::log_uart_hex(UartLogDirection direction, const uint8_t* data, const size_t length, const char separator)
{
if (this->log_uart_msg_ == false) return;
std::string logStr = "";
std::string msgDir = direction == UART_LOG_TX ? ">>>" : "<<<";
std::string msgType = direction == UART_LOG_TX ? "request" : "response";
switch(data[0])
{
case 0x31:
msgType = "initial_" + msgType;
break;
case 0x71:
msgType = "polling_" + msgType;
break;
case 0xF1:
msgType = "command_" + msgType;
break;
};
ESP_LOGI(TAG, "%s %s[%i]", msgDir.c_str(), msgType.c_str(), length);
delay(10);
char buffer[5];
for (size_t i = 0; i < length; i++)
{
if (i > 0) logStr += separator;
sprintf(buffer, "%02X", data[i]);
logStr += buffer;
}
for (size_t i = 0; i < logStr.length(); i += UART_LOG_CHUNK_SIZE)
{
ESP_LOGI(TAG, "%s %s", msgDir.c_str(), logStr.substr(i, UART_LOG_CHUNK_SIZE).c_str());
delay(10);
}
}
void PanasonicHeatpumpComponent::decode_response(const std::vector<uint8_t>& data)
{ {
// Read response message: // Read response message:
// format: 0x71 [payload_length] 0x01 0x10 [[TOP0 - TOP114] ...] 0x00 [checksum] // format: 0x71 [payload_length] 0x01 0x10 [[TOP0 - TOP114] ...] 0x00 [checksum]
// payload_length: payload_length + 3 = packet_length // payload_length: payload_length + 3 = packet_length
// checksum: if (sum(all bytes) & 0xFF == 0) ==> valid packet // checksum: if (sum(all bytes) & 0xFF == 0) ==> valid packet
if (data[0] != 0x71) return; if (data.empty()) return false;
if (data[0] != 0x71) return false;
if (data.size() != RESPONSE_MSG_SIZE) if (data.size() != RESPONSE_MSG_SIZE)
{ {
ESP_LOGW(TAG, "Invalid response message length: recieved %d - expected %d", data.size(), RESPONSE_MSG_SIZE); ESP_LOGW(TAG, "Invalid response message length: recieved %d - expected %d", data.size(), RESPONSE_MSG_SIZE);
delay(10); delay(10); // NOLINT
return; return false;
} }
uint8_t checksum = 0; uint8_t checksum = 0;
@ -246,21 +277,18 @@ namespace esphome
if (checksum != 0) if (checksum != 0)
{ {
ESP_LOGW(TAG, "Invalid response message: checksum = 0x%02X, last_byte = 0x%02X", checksum, data[202]); ESP_LOGW(TAG, "Invalid response message: checksum = 0x%02X, last_byte = 0x%02X", checksum, data[202]);
delay(10); delay(10); // NOLINT
return; return false;
} }
this->publish_sensor(data); if (this->last_response_count_ == this->current_response_count_) return false;
this->publish_binary_sensor(data); this->last_response_count_ = this->current_response_count_;
this->publish_text_sensor(data); return true;
this->publish_number(data);
this->publish_select(data);
this->publish_switch(data);
} }
void PanasonicHeatpumpComponent::set_command_byte(const uint8_t value, const uint8_t index) void PanasonicHeatpumpComponent::set_command_byte(const uint8_t value, const uint8_t index)
{ {
if (this->next_request_ == 1) if (this->next_request_ != RequestType::COMMAND)
{ {
// initialize the command // initialize the command
command_message_.assign(std::begin(PanasonicCommand::CommandMessage), command_message_.assign(std::begin(PanasonicCommand::CommandMessage),
@ -272,13 +300,12 @@ namespace esphome
command_message_.back() = PanasonicCommand::calcChecksum(command_message_, command_message_.size() - 1); command_message_.back() = PanasonicCommand::calcChecksum(command_message_, command_message_.size() - 1);
// command will be send on next loop // command will be send on next loop
this->next_request_ = 2; this->next_request_ = RequestType::COMMAND;
this->trigger_request_ = true;
} }
void PanasonicHeatpumpComponent::set_command_bytes(const std::vector<std::tuple<uint8_t, uint8_t>>& data) void PanasonicHeatpumpComponent::set_command_bytes(const std::vector<std::tuple<uint8_t, uint8_t>>& data)
{ {
if (this->next_request_ == 1) if (this->next_request_ != RequestType::COMMAND)
{ {
// initialize the command // initialize the command
command_message_.assign(std::begin(PanasonicCommand::CommandMessage), command_message_.assign(std::begin(PanasonicCommand::CommandMessage),
@ -295,13 +322,91 @@ namespace esphome
command_message_.back() = PanasonicCommand::calcChecksum(command_message_, command_message_.size() - 1); command_message_.back() = PanasonicCommand::calcChecksum(command_message_, command_message_.size() - 1);
// command will be send on next loop // command will be send on next loop
this->next_request_ = 2; this->next_request_ = RequestType::COMMAND;
this->trigger_request_ = true; }
void PanasonicHeatpumpComponent::set_number_traits(const std::vector<uint8_t>& data)
{
#ifdef USE_TEXT_SENSOR
#ifdef USE_NUMBER
if (data.empty()) return;
// traits can be changed anytime, but entities will only be updated in home assistant,
// if the traits was set before connecting to home assistant. If now a traits must be changed later,
// a reboot of the ESP controller is required to see the changes in home assistant.
bool change_detected = false;
std::string top76 = PanasonicDecode::getTextState(
PanasonicDecode::HeatCoolModeDesc, PanasonicDecode::getBit7and8(data[28])); // Heating Mode
std::string top81 = PanasonicDecode::getTextState(
PanasonicDecode::HeatCoolModeDesc, PanasonicDecode::getBit5and6(data[28])); // Cooling Mode
float set5_min = this->set5_number_->traits.get_min_value(); // Z1 Heat Request Temperature
float set6_min = this->set6_number_->traits.get_min_value(); // Z1 Cool Request Temperature
float set7_min = this->set7_number_->traits.get_min_value(); // Z2 Heat Request Temperature
float set8_min = this->set8_number_->traits.get_min_value(); // Z2 Cool Request Temperature
if (set5_min >= 0.0 && top76 == PanasonicDecode::HeatCoolModeDesc[1])
{
this->set5_number_->traits.set_min_value(-5.0);
this->set5_number_->traits.set_max_value(5.0);
change_detected = true;
}
if (set6_min >= 0.0 && top81 == PanasonicDecode::HeatCoolModeDesc[1])
{
this->set6_number_->traits.set_min_value(-5.0);
this->set6_number_->traits.set_max_value(5.0);
change_detected = true;
}
if (set7_min >= 0.0 && top76 == PanasonicDecode::HeatCoolModeDesc[1])
{
this->set7_number_->traits.set_min_value(-5.0);
this->set7_number_->traits.set_max_value(5.0);
change_detected = true;
}
if (set8_min >= 0.0 && top81 == PanasonicDecode::HeatCoolModeDesc[1])
{
this->set8_number_->traits.set_min_value(-5.0);
this->set8_number_->traits.set_max_value(5.0);
change_detected = true;
}
if (set5_min <= 0.0 && top76 == PanasonicDecode::HeatCoolModeDesc[2])
{
this->set5_number_->traits.set_min_value(20.0);
this->set5_number_->traits.set_max_value(60.0);
change_detected = true;
}
if (set6_min <= 0.0 && top81 == PanasonicDecode::HeatCoolModeDesc[2])
{
this->set6_number_->traits.set_min_value(20.0);
this->set6_number_->traits.set_max_value(60.0);
change_detected = true;
}
if (set7_min <= 0.0 && top76 == PanasonicDecode::HeatCoolModeDesc[2])
{
this->set7_number_->traits.set_min_value(20.0);
this->set7_number_->traits.set_max_value(60.0);
change_detected = true;
}
if (set8_min <= 0.0 && top81 == PanasonicDecode::HeatCoolModeDesc[2])
{
this->set8_number_->traits.set_min_value(20.0);
this->set8_number_->traits.set_max_value(60.0);
change_detected = true;
}
if (change_detected)
{
this->trait_update_counter_++;
}
#endif
#endif
} }
void PanasonicHeatpumpComponent::publish_sensor(const std::vector<uint8_t>& data) void PanasonicHeatpumpComponent::publish_sensor(const std::vector<uint8_t>& data)
{ {
#ifdef USE_SENSOR #ifdef USE_SENSOR
if (data.empty()) return;
if (this->top1_sensor_) this->top1_sensor_->publish_state(PanasonicDecode::getPumpFlow(data[169], data[170])); if (this->top1_sensor_) this->top1_sensor_->publish_state(PanasonicDecode::getPumpFlow(data[169], data[170]));
if (this->top5_sensor_) this->top5_sensor_->publish_state(PanasonicDecode::getByteMinus128(data[143]) + PanasonicDecode::getFractional(data[118], 0)); if (this->top5_sensor_) this->top5_sensor_->publish_state(PanasonicDecode::getByteMinus128(data[143]) + PanasonicDecode::getFractional(data[118], 0));
if (this->top6_sensor_) this->top6_sensor_->publish_state(PanasonicDecode::getByteMinus128(data[144]) + PanasonicDecode::getFractional(data[118], 3)); if (this->top6_sensor_) this->top6_sensor_->publish_state(PanasonicDecode::getByteMinus128(data[144]) + PanasonicDecode::getFractional(data[118], 3));
@ -403,6 +508,7 @@ namespace esphome
void PanasonicHeatpumpComponent::publish_binary_sensor(const std::vector<uint8_t>& data) void PanasonicHeatpumpComponent::publish_binary_sensor(const std::vector<uint8_t>& data)
{ {
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
if (data.empty()) return;
if (this->top0_binary_sensor_) this->top0_binary_sensor_->publish_state(PanasonicDecode::getBinaryState(PanasonicDecode::getBit7and8(data[4]))); if (this->top0_binary_sensor_) this->top0_binary_sensor_->publish_state(PanasonicDecode::getBinaryState(PanasonicDecode::getBit7and8(data[4])));
if (this->top2_binary_sensor_) this->top2_binary_sensor_->publish_state(PanasonicDecode::getBinaryState(PanasonicDecode::getBit1and2(data[4]))); if (this->top2_binary_sensor_) this->top2_binary_sensor_->publish_state(PanasonicDecode::getBinaryState(PanasonicDecode::getBit1and2(data[4])));
if (this->top3_binary_sensor_) this->top3_binary_sensor_->publish_state(PanasonicDecode::getBinaryState(PanasonicDecode::getBit1and2(data[7]))); if (this->top3_binary_sensor_) this->top3_binary_sensor_->publish_state(PanasonicDecode::getBinaryState(PanasonicDecode::getBit1and2(data[7])));
@ -432,6 +538,7 @@ namespace esphome
void PanasonicHeatpumpComponent::publish_text_sensor(const std::vector<uint8_t>& data) void PanasonicHeatpumpComponent::publish_text_sensor(const std::vector<uint8_t>& data)
{ {
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
if (data.empty()) return;
if (this->top4_text_sensor_) this->top4_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::OpModeDesc, PanasonicDecode::getOpMode(data[6]))); if (this->top4_text_sensor_) this->top4_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::OpModeDesc, PanasonicDecode::getOpMode(data[6])));
if (this->top17_text_sensor_) this->top17_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::Powerfulmode, PanasonicDecode::getBit6and7and8(data[7]))); if (this->top17_text_sensor_) this->top17_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::Powerfulmode, PanasonicDecode::getBit6and7and8(data[7])));
if (this->top18_text_sensor_) this->top18_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::Quietmode, PanasonicDecode::getBit3and4and5(data[7]))); if (this->top18_text_sensor_) this->top18_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::Quietmode, PanasonicDecode::getBit3and4and5(data[7])));
@ -459,6 +566,7 @@ namespace esphome
void PanasonicHeatpumpComponent::publish_number(const std::vector<uint8_t>& data) void PanasonicHeatpumpComponent::publish_number(const std::vector<uint8_t>& data)
{ {
#ifdef USE_NUMBER #ifdef USE_NUMBER
if (data.empty()) return;
if (this->set11_number_) this->set11_number_->publish_state(PanasonicDecode::getByteMinus128(data[42])); if (this->set11_number_) this->set11_number_->publish_state(PanasonicDecode::getByteMinus128(data[42]));
if (this->set20_number_) this->set20_number_->publish_state(PanasonicDecode::getByteMinus128(data[99])); if (this->set20_number_) this->set20_number_->publish_state(PanasonicDecode::getByteMinus128(data[99]));
if (this->set18_number_) this->set18_number_->publish_state(PanasonicDecode::getByteMinus128(data[84])); if (this->set18_number_) this->set18_number_->publish_state(PanasonicDecode::getByteMinus128(data[84]));
@ -498,6 +606,7 @@ namespace esphome
void PanasonicHeatpumpComponent::publish_select(const std::vector<uint8_t>& data) void PanasonicHeatpumpComponent::publish_select(const std::vector<uint8_t>& data)
{ {
#ifdef USE_SELECT #ifdef USE_SELECT
if (data.empty()) return;
if (this->set9_select_) this->set9_select_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::OpModeDesc, PanasonicDecode::getOpMode(data[6]))); if (this->set9_select_) this->set9_select_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::OpModeDesc, PanasonicDecode::getOpMode(data[6])));
if (this->set4_select_) this->set4_select_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::Powerfulmode, PanasonicDecode::getBit6and7and8(data[7]))); if (this->set4_select_) this->set4_select_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::Powerfulmode, PanasonicDecode::getBit6and7and8(data[7])));
if (this->set3_select_) this->set3_select_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::Quietmode, PanasonicDecode::getBit3and4and5(data[7]))); if (this->set3_select_) this->set3_select_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::Quietmode, PanasonicDecode::getBit3and4and5(data[7])));
@ -510,7 +619,8 @@ namespace esphome
void PanasonicHeatpumpComponent::publish_switch(const std::vector<uint8_t>& data) void PanasonicHeatpumpComponent::publish_switch(const std::vector<uint8_t>& data)
{ {
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (data.empty()) return;
if (this->set1_switch_) this->set1_switch_->publish_state(PanasonicDecode::getBinaryState(PanasonicDecode::getBit7and8(data[4]))); if (this->set1_switch_) this->set1_switch_->publish_state(PanasonicDecode::getBinaryState(PanasonicDecode::getBit7and8(data[4])));
if (this->set10_switch_) this->set10_switch_->publish_state(PanasonicDecode::getBinaryState(PanasonicDecode::getBit1and2(data[4]))); if (this->set10_switch_) this->set10_switch_->publish_state(PanasonicDecode::getBinaryState(PanasonicDecode::getBit1and2(data[4])));
if (this->set24_switch_) this->set24_switch_->publish_state(PanasonicDecode::getBinaryState(PanasonicDecode::getBit1and2(data[5]))); if (this->set24_switch_) this->set24_switch_->publish_state(PanasonicDecode::getBinaryState(PanasonicDecode::getBit1and2(data[5])));

View File

@ -24,21 +24,38 @@
#endif #endif
#include "decode.h" #include "decode.h"
#include "commands.h" #include "commands.h"
#include "helpers.h"
#include <vector> #include <vector>
#include <tuple> #include <tuple>
#include <string> #include <string>
#define UART_LOG_CHUNK_SIZE 153
namespace esphome namespace esphome
{ {
namespace panasonic_heatpump namespace panasonic_heatpump
{ {
enum UartLogDirection enum LoopState
{ {
UART_LOG_RX, READ_RESPONSE,
UART_LOG_TX, CHECK_RESPONSE,
SET_NUMBER_TRAITS,
PUBLISH_SENSOR,
PUBLISH_BINARY_SENSOR,
PUBLISH_TEXT_SENSOR,
PUBLISH_NUMBER,
PUBLISH_SELECT,
PUBLISH_SWITCH,
SEND_REQUEST,
READ_REQUEST,
RESTART_LOOP
};
enum RequestType
{
INITIAL,
POLLING,
COMMAND,
NONE
}; };
class PanasonicHeatpumpComponent : public PollingComponent, public uart::UARTDevice class PanasonicHeatpumpComponent : public PollingComponent, public uart::UARTDevice
@ -258,7 +275,7 @@ namespace esphome
PanasonicHeatpumpComponent() = default; PanasonicHeatpumpComponent() = default;
// base class functions // base class functions
float get_setup_priority() const override { return setup_priority::LATE; } float get_setup_priority() const override { return setup_priority::DATA; }
void dump_config() override; void dump_config() override;
void setup() override; void setup() override;
void update() override; void update() override;
@ -274,35 +291,35 @@ namespace esphome
uart::UARTComponent* uart_client_ { nullptr }; uart::UARTComponent* uart_client_ { nullptr };
bool log_uart_msg_ { false }; bool log_uart_msg_ { false };
// uart message variables // uart message variables
std::vector<uint8_t> temp_message_; std::vector<uint8_t> heatpump_message_;
std::vector<uint8_t> response_message_; std::vector<uint8_t> response_message_;
std::vector<uint8_t> request_message_; std::vector<uint8_t> request_message_;
std::vector<uint8_t> command_message_; std::vector<uint8_t> command_message_;
uint8_t response_payload_length_; uint8_t payload_length_;
uint8_t request_payload_length_;
uint8_t byte_; uint8_t byte_;
uint8_t current_response_count_ { 0 };
uint8_t last_response_count_ { 0 };
bool response_receiving_ { false }; bool response_receiving_ { false };
bool request_receiving_ { false }; bool request_receiving_ { false };
bool trigger_request_ { false }; RequestType next_request_ { RequestType::INITIAL };
uint8_t next_request_ { 0 }; // 0 = initial, 1 = polling, 2 = command LoopState loop_state_ { LoopState::RESTART_LOOP };
uint16_t trait_update_counter_ { 0 };
// uart message functions // uart message functions
void read_response(); void read_response();
void send_request(); void send_request(RequestType requestType);
void read_request(); void read_request();
void decode_response(const std::vector<uint8_t>& data); bool check_response(const std::vector<uint8_t>& data);
void set_command_byte(const uint8_t value, const uint8_t index); void set_command_byte(const uint8_t value, const uint8_t index);
void set_command_bytes(const std::vector<std::tuple<uint8_t, uint8_t>>& data); void set_command_bytes(const std::vector<std::tuple<uint8_t, uint8_t>>& data);
// sensor and control publish functions // sensor and control publish functions
void set_number_traits(const std::vector<uint8_t>& data);
void publish_sensor(const std::vector<uint8_t>& data); void publish_sensor(const std::vector<uint8_t>& data);
void publish_binary_sensor(const std::vector<uint8_t>& data); void publish_binary_sensor(const std::vector<uint8_t>& data);
void publish_text_sensor(const std::vector<uint8_t>& data); void publish_text_sensor(const std::vector<uint8_t>& data);
void publish_number(const std::vector<uint8_t>& data); void publish_number(const std::vector<uint8_t>& data);
void publish_select(const std::vector<uint8_t>& data); void publish_select(const std::vector<uint8_t>& data);
void publish_switch(const std::vector<uint8_t>& data); void publish_switch(const std::vector<uint8_t>& data);
// helper functions
void log_uart_hex(UartLogDirection direction, const std::vector<uint8_t>& data, const char separator);
void log_uart_hex(UartLogDirection direction, const uint8_t* data, const size_t length, const char separator);
}; };
} // namespace panasonic_heatpump } // namespace panasonic_heatpump
} // namespace esphome } // namespace esphome

View File

@ -11,6 +11,7 @@ CONF_SET2 = "set2" # Set Holiday Mode
CONF_SET3 = "set3" # Set Quiet Mode CONF_SET3 = "set3" # Set Quiet Mode
CONF_SET4 = "set4" # Set Powerful Mode CONF_SET4 = "set4" # Set Powerful Mode
CONF_SET9 = "set9" # Set Operation Mode CONF_SET9 = "set9" # Set Operation Mode
# ToDo: Split up set9 into set9_1 (Heating Mode) and set9_2 (DHW Mode)
CONF_SET17 = "set17" # Set Zones CONF_SET17 = "set17" # Set Zones
CONF_SET26 = "set26" # Set External PadHeater CONF_SET26 = "set26" # Set External PadHeater
CONF_SET35 = "set35" # Set Bivalent Mode CONF_SET35 = "set35" # Set Bivalent Mode

View File

@ -25,6 +25,7 @@ ICON_EXTERNAL_PAD_HEATER = "mdi:radiator"
CONF_TOP4 = "top4" # Operating Mode State CONF_TOP4 = "top4" # Operating Mode State
# ToDo: Split up top4 into top4_1 (Heating Mode State) and top4_2 (DHW Mode State)
CONF_TOP17 = "top17" # Powerful Mode Time CONF_TOP17 = "top17" # Powerful Mode Time
CONF_TOP18 = "top18" # Quiet Mode Level CONF_TOP18 = "top18" # Quiet Mode Level
CONF_TOP19 = "top19" # Holiday Mode State CONF_TOP19 = "top19" # Holiday Mode State