15using namespace water_heater_protocol;
34 const bool ready =
false;
40 ESP_LOGI(TAG,
"Local network status changed: %02X",
status);
45 uint8_t wire[MAX_WIRE_FRAME];
49#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
72 static constexpr uint8_t VERSION_REQUEST[] = {0x00, 0x07};
73 static constexpr uint8_t STATUS_REQUEST[] = {0x4D, 0x01};
76 sizeof(VERSION_REQUEST), FrameType::FRAME_TYPE_VERSION);
79 FrameType::FRAME_TYPE_DEVICE_ID);
82 sizeof(STATUS_REQUEST), FrameType::FRAME_TYPE_STATUS);
91 ESP_LOGW(TAG,
"Heater communication lost: no valid status for %u ms",
102 ESP_LOGW(TAG,
"No matching response type=%02X in %u ms",
static_cast<uint8_t
>(this->
expected_type_),
109 ESP_LOGW(TAG,
"Control response timed out; discarding remaining commands");
127 this->
commands_[0] = {Parameter::PARAMETER_POWER, 0};
136 FrameType::FRAME_TYPE_CONFIRM);
149 FrameType::FRAME_TYPE_STATUS);
156 while (budget-- > 0 && this->
available() > 0) {
164 if ((this->
parser_.
data()[FRAME_FLAGS_OFFSET] & FRAME_FLAG_ENCRYPTED) != 0) {
165 ESP_LOGW(TAG,
"RX: XOR-encrypted frames are not supported and will be dropped");
167 ESP_LOGW(TAG,
"RX: unsupported flags or invalid checksum/CRC");
182 ESP_LOGV(TAG,
"Response matched type=%02X",
static_cast<uint8_t
>(
type));
186 case FrameType::FRAME_TYPE_GET_NETWORK_STATUS:
189 case FrameType::FRAME_TYPE_CONFIRM:
191 ESP_LOGD(TAG,
"Network status acknowledged");
195 case FrameType::FRAME_TYPE_INVALID:
196 ESP_LOGW(TAG,
"Appliance rejected request");
203 case FrameType::FRAME_TYPE_REPORT:
215 ESP_LOGD(TAG,
"Control confirmed: parameter=%02X value=%02X",
static_cast<uint8_t
>(command.
parameter), command.
value);
223 const auto type =
static_cast<FrameType>(frame[FRAME_TYPE_OFFSET]);
224#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
226 ESP_LOGV(TAG,
"RX decoded type=%02X flags=%02X: %s",
static_cast<uint8_t
>(
type), frame[FRAME_FLAGS_OFFSET],
235 if (
status.mode == HonMode::HON_MODE_UNKNOWN && (recovered || this->reported_mode_ != HonMode::HON_MODE_UNKNOWN)) {
236 const uint8_t *payload = frame + FRAME_PAYLOAD_OFFSET;
238 "Unknown heater mode: payload[38..41]=%02X %02X %02X %02X; using last recognized active mode, or Electric "
240 payload[38], payload[39], payload[40], payload[41]);
251 ESP_LOGI(TAG,
"Heater communication established");
262 case HonMode::HON_MODE_NORMAL:
265 case HonMode::HON_MODE_ECO:
268 case HonMode::HON_MODE_BPS:
271 case HonMode::HON_MODE_UNKNOWN:
288 const uint8_t percent =
status.available_water * 100 / 3;
296 char label[HonModeLabels::MAX_LENGTH + 1];
297 ESPHOME_strncpy_P(label,
298 reinterpret_cast<ESPHOME_PGM_P
>(HonModeLabels::get_progmem_str(
299 static_cast<uint8_t
>(
status.mode),
static_cast<uint8_t
>(HonMode::HON_MODE_UNKNOWN))),
307 LOG_WATER_HEATER(
"",
"Haier hOn",
this);
309 " Polling interval: %u ms\n"
310 " Communication timeout: %u ms",
331 return HonMode::HON_MODE_NORMAL;
333 return HonMode::HON_MODE_ECO;
335 return HonMode::HON_MODE_BPS;
337 return HonMode::HON_MODE_UNKNOWN;
342 using namespace water_heater;
344 const auto on =
call.get_on();
345 const bool off = (on.has_value() && !*on) || (
mode.has_value() && *
mode == WATER_HEATER_MODE_OFF);
359 ESP_LOGW(TAG,
"Control rejected: no recent appliance status");
363 ESP_LOGW(TAG,
"Control busy; wait for the previous request to finish");
375 mode.has_value() ? *
mode == WATER_HEATER_MODE_ELECTRIC : this->
reported_mode_ == HonMode::HON_MODE_NORMAL;
377 ESP_LOGW(TAG,
"Target can only be set in Normal mode");
382 auto append = [
this](
Parameter parameter, uint8_t value) {
386 if (!this->
is_on() && ((on.has_value() && *on) ||
mode.has_value()))
387 append(Parameter::PARAMETER_POWER, 1);
390 if (*mode != WATER_HEATER_MODE_ELECTRIC && this->
reported_mode_ != HonMode::HON_MODE_NORMAL)
391 append(Parameter::PARAMETER_ECO, 0);
393 case WATER_HEATER_MODE_ELECTRIC:
394 append(Parameter::PARAMETER_ECO, 0);
396 case WATER_HEATER_MODE_ECO:
397 append(Parameter::PARAMETER_ECO, 1);
399 case WATER_HEATER_MODE_PERFORMANCE:
400 append(Parameter::PARAMETER_BPS, 1);
407 append(Parameter::PARAMETER_TARGET_TEMPERATURE,
static_cast<uint8_t
>(
temperature - TARGET_TEMPERATURE_OFFSET));
411 using namespace water_heater;
415 WATER_HEATER_SUPPORTS_ON_OFF);
417 {WATER_HEATER_MODE_OFF, WATER_HEATER_MODE_ELECTRIC, WATER_HEATER_MODE_ECO, WATER_HEATER_MODE_PERFORMANCE});
BedjetMode mode
BedJet operating mode.
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void status_set_warning()
void set_interval(const char *name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a const char* name.
void status_clear_warning()
void publish_state(bool new_state)
Publish a new state to the front-end.
static constexpr uint32_t POLL_INTERVAL_MS
sensor::Sensor * secondary_sensor_
water_heater::WaterHeaterCallInternal make_call() override
void handle_frame_type_(water_heater_protocol::FrameType type)
sensor::Sensor * primary_sensor_
void check_timeouts_(uint32_t now)
static constexpr uint8_t POLL_CYCLE
void update_network_status_()
static constexpr uint32_t NETWORK_STATUS_INTERVAL_ID
static constexpr uint8_t NETWORK_NOT_READY
static constexpr uint32_t NETWORK_REFRESH_MS
binary_sensor::BinarySensor * communication_sensor_
static constexpr uint32_t STATUS_POLL_INTERVAL_ID
static constexpr uint8_t MAX_BYTES_PER_LOOP
void send_request_(uint32_t now, Awaiting kind, water_heater_protocol::FrameType type, const uint8_t *payload, size_t size, water_heater_protocol::FrameType expected)
water_heater_protocol::FrameType expected_type_
sensor::Sensor * heating_time_sensor_
static constexpr uint8_t NETWORK_READY
binary_sensor::BinarySensor * heating_sensor_
void publish_status_(const water_heater_protocol::Status &status)
void send_frame_(water_heater_protocol::FrameType type, const uint8_t *payload=nullptr, size_t size=0)
binary_sensor::BinarySensor * power_sensor_
water_heater_protocol::HonMode reported_mode_
static constexpr uint8_t MIN_TARGET_TEMPERATURE
static constexpr uint8_t MAX_TARGET_TEMPERATURE
static constexpr uint32_t RESPONSE_TIMEOUT_MS
static constexpr uint32_t NETWORK_ACK_TIMEOUT_MS
sensor::Sensor * water_sensor_
void read_frames_(uint32_t now)
text_sensor::TextSensor * mode_sensor_
sensor::Sensor * target_sensor_
water_heater::WaterHeaterMode last_active_mode_
void control(const water_heater::WaterHeaterCall &call) override
void confirm_control_(const water_heater_protocol::Status &status)
static constexpr uint32_t NETWORK_STATUS_INTERVAL_MS
void send_pending_(uint32_t now)
uint32_t communication_timeout_
static constexpr uint32_t CONTROL_STATUS_MAX_AGE_MS
static constexpr uint32_t FRAME_GAP_MS
void dump_config() override
water_heater::WaterHeaterTraits traits() override
void send_next_control_(uint32_t now)
water_heater_protocol::FrameParser parser_
std::array< water_heater_protocol::Command, 4 > commands_
bool feed(uint8_t value)
Returns true once a valid frame is complete. A complete but invalid frame leaves complete() set.
size_t size() const
Logical bytes received, including the checksum and CRC; may exceed MAX_STORED_FRAME.
const uint8_t * data() const
The stored head of the frame, at most MAX_STORED_FRAME bytes; valid until the next feed or reset.
size_t stored_size() const
void publish_state(float state)
Publish a new state to the front-end.
void publish_state(const std::string &state)
bool read_byte(uint8_t *data)
void write_array(const uint8_t *data, size_t len)
virtual void publish_state()
void set_state_flag_(uint32_t flag, bool value)
Set or clear a state flag. Should only be called from control().
bool is_on() const
Check if the water heater is on.
float target_temperature_
float current_temperature_
void set_supported_modes(WaterHeaterModeMask modes)
void add_feature_flags(uint32_t flags)
Get/set feature flags (see WaterHeaterFeature enum)
void set_supports_current_temperature(bool supports)
void set_min_temperature(float min_temperature)
void set_target_temperature_step(float target_temperature_step)
void set_max_temperature(float max_temperature)
bool decode_status(const uint8_t *frame, size_t size, Status &status)
Decodes a status report or a polled status; false for any other frame or an implausible one.
bool command_confirmed(const Command &command, const Status &status)
True when a status shows that the command took effect.
size_t encode_frame(FrameType type, const uint8_t *payload, size_t size, uint8_t *wire)
Writes a clear-text frame with up to MAX_PAYLOAD bytes of payload into wire, which must hold MAX_WIRE...
void encode_control(const Command &command, uint8_t *payload)
Fills the four-byte control payload for a command.
PROGMEM_STRING_TABLE(HonModeLabels, "Unknown", "Normal", "ECO", "BPS")
ESPHOME_LOG_TAG(TAG, "haier_water_heater")
ESPHOME_ALWAYS_INLINE bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
@ WATER_HEATER_MODE_ELECTRIC
@ WATER_HEATER_MODE_PERFORMANCE
@ WATER_HEATER_STATE_ON
Water heater is on (not in standby)
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Application App
Global storage of Application pointer - only one Application can exist.
uint8_t current_temperature
uint8_t secondary_temperature
uint8_t target_temperature
uint8_t estimated_heating_time