Added support for extra query (fixes #11)

This commit is contained in:
ElVit 2025-07-16 16:08:09 +02:00
parent 505d643bf0
commit 3753b3d475
7 changed files with 199 additions and 44 deletions

View File

@ -24,6 +24,16 @@ namespace esphome
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x12
};
const uint8_t PanasonicCommand::PollingExtraMessage[DATA_MESSAGE_SIZE] = {
0x71, 0x6C, 0x01, 0x21, 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, 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,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01
};
const uint8_t PanasonicCommand::CommandMessage[DATA_MESSAGE_SIZE] = {
0xF1, 0x6C, 0x01, 0x10, 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,

View File

@ -42,6 +42,7 @@ namespace esphome
static const uint8_t InitialRequest[INIT_REQUEST_SIZE];
static const uint8_t InitialResponse[INIT_RESPONSE_SIZE];
static const uint8_t PollingMessage[DATA_MESSAGE_SIZE];
static const uint8_t PollingExtraMessage[DATA_MESSAGE_SIZE];
static const uint8_t CommandMessage[DATA_MESSAGE_SIZE];
};
} // namespace panasonic_heatpump

View File

@ -25,8 +25,8 @@ namespace esphome
void PanasonicHeatpumpComponent::update()
{
if (this->uart_client_ == nullptr)
this->next_request_ = RequestType::POLLING;
if (this->uart_client_ != nullptr) return;
this->next_request_ = this->send_extra_request_ ? RequestType::POLLING_EXTRA : RequestType::POLLING;
}
void PanasonicHeatpumpComponent::loop()
@ -34,98 +34,91 @@ namespace esphome
switch (this->loop_state_)
{
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::PUBLISH_SENSOR : LoopState::SEND_REQUEST;
this->current_response_ = this->check_response(this->heatpump_message_);
switch (this->current_response_)
{
case ResponseType::UNKNOWN:
this->loop_state_ = LoopState::SEND_REQUEST;
break;
case ResponseType::DEFAULT:
this->loop_state_ = LoopState::PUBLISH_SENSOR;
break;
case ResponseType::EXTRA:
this->loop_state_ = LoopState::PUBLISH_EXTRA_SENSOR;
break;
};
break;
}
case LoopState::PUBLISH_SENSOR:
{
for (auto *entity : this->sensors_)
{
entity->publish_new_state(this->heatpump_message_);
}
this->loop_state_ = LoopState::PUBLISH_BINARY_SENSOR;
break;
}
case LoopState::PUBLISH_BINARY_SENSOR:
{
for (auto *entity : this->binary_sensors_)
{
entity->publish_new_state(this->heatpump_message_);
}
this->loop_state_ = LoopState::PUBLISH_TEXT_SENSOR;
break;
}
case LoopState::PUBLISH_TEXT_SENSOR:
{
for (auto *entity : this->text_sensors_)
{
entity->publish_new_state(this->heatpump_message_);
}
this->loop_state_ = LoopState::PUBLISH_NUMBER;
break;
}
case LoopState::PUBLISH_NUMBER:
{
for (auto *entity : this->numbers_)
{
entity->publish_new_state(this->heatpump_message_);
}
this->loop_state_ = LoopState::PUBLISH_SELECT;
break;
}
case LoopState::PUBLISH_SELECT:
{
for (auto *entity : this->selects_)
{
entity->publish_new_state(this->heatpump_message_);
}
this->loop_state_ = LoopState::PUBLISH_SWITCH;
break;
}
case LoopState::PUBLISH_SWITCH:
{
for (auto *entity : this->switches_)
{
entity->publish_new_state(this->heatpump_message_);
}
this->loop_state_ = LoopState::PUBLISH_CLIMATE;
break;
}
case LoopState::PUBLISH_CLIMATE:
{
for (auto *entity : this->climates_)
{
entity->publish_new_state(this->heatpump_message_);
}
this->loop_state_ = LoopState::SEND_REQUEST;
break;
}
case LoopState::PUBLISH_EXTRA_SENSOR:
for (auto *entity : this->extra_sensors_)
{
entity->publish_new_state(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:
{
this->loop_state_ = LoopState::READ_RESPONSE;
break;
}
};
}
@ -205,6 +198,13 @@ namespace esphome
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::PollingMessage, DATA_MESSAGE_SIZE);
this->flush();
break;
}
};
this->next_request_ = RequestType::NONE;
@ -264,41 +264,62 @@ namespace esphome
return -1;
}
bool PanasonicHeatpumpComponent::check_response(const std::vector<uint8_t>& data)
ResponseType PanasonicHeatpumpComponent::check_response(const std::vector<uint8_t>& data)
{
// Read response message:
// format: 0x71 [payload_length] 0x01 0x10 [[TOP0 - TOP114] ...] 0x00 [checksum]
// 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 false;
if (data[0] != 0x71) return false;
if (data[3] != 0x10) return false;
if (data.empty()) return ResponseType::UNKNOWN;
if (data[0] != 0x71) 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 false;
return ResponseType::UNKNOWN;
}
// Verify checksum
uint8_t checksum = 0;
for (int i = 0; i < data.size(); i++)
{
checksum += data[i];
}
// checksum = checksum & 0xFF;
// 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 false;
return ResponseType::UNKNOWN;
}
// Get response type
auto responseType = ResponseType::UNKNOWN;
if (data[3] == 0x10) responseType = ResponseType::DEFAULT;
if (data[3] == 0x21) responseType = ResponseType::EXTRA;
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::UNKNOWN;
}
// Check if extra request shall be send
if (responseType == ResponseType::DEFAULT)
{
this->send_extra_request_ = data[199] > 0x02 ? true : false;
}
else
{
this->send_extra_request_ = false;
}
// Check if the current response is a new response
if (this->last_response_count_ == this->current_response_count_) return false;
if (this->last_response_count_ == this->current_response_count_) return ResponseType::UNKNOWN;
this->last_response_count_ = this->current_response_count_;
return true;
return responseType;
}
void PanasonicHeatpumpComponent::set_command_high_nibble(const uint8_t value, const uint8_t index)

View File

@ -27,17 +27,26 @@ namespace esphome
PUBLISH_SELECT,
PUBLISH_SWITCH,
PUBLISH_CLIMATE,
PUBLISH_EXTRA_SENSOR,
SEND_REQUEST,
READ_REQUEST,
RESTART_LOOP
RESTART_LOOP,
};
enum RequestType : uint8_t
{
NONE,
COMMAND,
INITIAL,
POLLING,
COMMAND,
NONE
POLLING_EXTRA,
};
enum ResponseType : uint8_t
{
UNKNOWN,
DEFAULT,
EXTRA,
};
class PanasonicHeatpumpEntity
@ -79,6 +88,7 @@ namespace esphome
void add_sensor(PanasonicHeatpumpEntity *sensor) { sensors_.push_back(sensor); }
void add_switch(PanasonicHeatpumpEntity *switch_) { switches_.push_back(switch_); }
void add_text_sensor(PanasonicHeatpumpEntity *text_sensor) { text_sensors_.push_back(text_sensor); }
void add_extra_sensor(PanasonicHeatpumpEntity *sensor) { extra_sensors_.push_back(sensor); }
protected:
// options variables
@ -95,8 +105,10 @@ namespace esphome
uint8_t last_response_count_ { 0 };
bool response_receiving_ { false };
bool request_receiving_ { false };
RequestType next_request_ { RequestType::INITIAL };
bool send_extra_request_ { 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_;
@ -105,12 +117,13 @@ namespace esphome
std::vector<PanasonicHeatpumpEntity *> sensors_;
std::vector<PanasonicHeatpumpEntity *> switches_;
std::vector<PanasonicHeatpumpEntity *> text_sensors_;
std::vector<PanasonicHeatpumpEntity *> extra_sensors_;
// uart message functions
void read_response();
void send_request(RequestType requestType);
void read_request();
bool check_response(const std::vector<uint8_t>& data);
ResponseType check_response(const std::vector<uint8_t>& data);
};
} // namespace panasonic_heatpump
} // namespace esphome

View File

@ -128,6 +128,13 @@ CONF_TOP136 = "top136" # Bivalent Advanced Start Delay
CONF_TOP137 = "top137" # Bivalent Advanced Stop Delay
CONF_TOP138 = "top138" # Bivalent Advanced DHW Delay
CONF_XTOP0 = "xtop0" # Heat Power Consumption Extra
CONF_XTOP1 = "xtop1" # Cool Power Consumption Extra
CONF_XTOP2 = "xtop2" # DHW Power Consumption Extra
CONF_XTOP3 = "xtop3" # Heat Power Production Extra
CONF_XTOP4 = "xtop4" # Cool Power Production Extra
CONF_XTOP5 = "xtop5" # DHW Power Production Extra
TYPES = [
CONF_TOP1,
CONF_TOP5,
@ -226,6 +233,15 @@ TYPES = [
CONF_TOP138,
]
EXTRA_TYPES = [
CONF_XTOP0,
CONF_XTOP1,
CONF_XTOP2,
CONF_XTOP3,
CONF_XTOP4,
CONF_XTOP5,
]
PanasonicHeatpumpSensor = panasonic_heatpump_ns.class_("PanasonicHeatpumpSensor", sensor.Sensor, cg.Component)
CONFIG_SCHEMA = cv.Schema(
@ -894,6 +910,49 @@ CONFIG_SCHEMA = cv.Schema(
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement = UNIT_MINUTE,
),
cv.Optional(CONF_XTOP0): sensor.sensor_schema(
PanasonicHeatpumpSensor,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement = UNIT_WATT,
),
cv.Optional(CONF_XTOP1): sensor.sensor_schema(
PanasonicHeatpumpSensor,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement = UNIT_WATT,
),
cv.Optional(CONF_XTOP2): sensor.sensor_schema(
PanasonicHeatpumpSensor,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement = UNIT_WATT,
),
cv.Optional(CONF_XTOP3): sensor.sensor_schema(
PanasonicHeatpumpSensor,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement = UNIT_WATT,
),
cv.Optional(CONF_XTOP4): sensor.sensor_schema(
PanasonicHeatpumpSensor,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement = UNIT_WATT,
),
cv.Optional(CONF_XTOP5): sensor.sensor_schema(
PanasonicHeatpumpSensor,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement = UNIT_WATT,
),
}
).extend(cv.COMPONENT_SCHEMA)
@ -906,3 +965,10 @@ async def to_code(config):
cg.add(var.set_parent(parent))
cg.add(var.set_id(index))
cg.add(parent.add_sensor(var))
for index, key in enumerate(EXTRA_TYPES):
if child_config := config.get(key):
var = await sensor.new_sensor(child_config)
await cg.register_component(var, child_config)
cg.add(var.set_parent(parent))
cg.add(var.set_id(index))
cg.add(parent.add_extra_sensor(var))

View File

@ -590,6 +590,43 @@ namespace esphome
if (this->has_state() && this->get_state() == new_state) return;
break;
}
case SensorIds::CONF_XTOP0:
{
new_state = PanasonicDecode::getWordMinus1(data, 14);
if (this->has_state() && this->get_state() == new_state) return;
break;
}
case SensorIds::CONF_XTOP1:
{
new_state = PanasonicDecode::getWordMinus1(data, 16);
if (this->has_state() && this->get_state() == new_state) return;
break;
}
case SensorIds::CONF_XTOP2:
{
new_state = PanasonicDecode::getWordMinus1(data, 18);
if (this->has_state() && this->get_state() == new_state) return;
break;
}
case SensorIds::CONF_XTOP3:
{
new_state = PanasonicDecode::getWordMinus1(data, 20);
if (this->has_state() && this->get_state() == new_state) return;
break;
}
case SensorIds::CONF_XTOP4:
{
new_state = PanasonicDecode::getWordMinus1(data, 22);
if (this->has_state() && this->get_state() == new_state) return;
break;
}
case SensorIds::CONF_XTOP5:
{
new_state = PanasonicDecode::getWordMinus1(data, 24);
if (this->has_state() && this->get_state() == new_state) return;
break;
}
default: return;
};

View File

@ -106,6 +106,13 @@ namespace esphome
CONF_TOP136,
CONF_TOP137,
CONF_TOP138,
CONF_XTOP0,
CONF_XTOP1,
CONF_XTOP2,
CONF_XTOP3,
CONF_XTOP4,
CONF_XTOP5,
};
class PanasonicHeatpumpSensor : public sensor::Sensor, public Component,