Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions esphome/components/daikin_madoka/daikin_madoka.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ static const uint16_t CMD_GET_FAN_SPEED = 0x0050;
static const uint16_t CMD_SET_FAN_SPEED = 0x4050;
static const uint16_t CMD_GET_SENSOR_INFORMATION = 0x0110;

// Status retry parameters: if the BRC1H reports a status that disagrees with
// the last HA-issued status within this window, re-send SET_SETTING_STATUS up
// to MAX_STATUS_RETRIES times before accepting the readback as truth. The
// same parameters also gate the symmetric mode-retry below.
static const uint32_t STATUS_RETRY_WINDOW_MS = 30000;
static const uint8_t MAX_STATUS_RETRIES = 2;

// FAN_ONLY is reported as either byte 0 or byte 5 depending on whether the
// pulse treats it as a transient fan flag or as the main operation mode.
// Treat both as the same outcome when comparing readback to requested mode.
static inline bool madoka_mode_byte_equiv(uint8_t a, uint8_t b) {
if (a == b) return true;
if ((a == 0 && b == 5) || (a == 5 && b == 0)) return true;
return false;
}

void DaikinMadoka::dump_config() { LOG_CLIMATE(TAG, "Daikin Madoka Climate Controller", this); }

void DaikinMadoka::setup() { this->receive_semaphore_ = xSemaphoreCreateMutex(); }
Expand Down Expand Up @@ -81,6 +97,11 @@ void DaikinMadoka::control(const ClimateCall &call) {
this->query_(CMD_SET_OPERATION_MODE, std::vector<uint8_t>{0x20, 0x01, (uint8_t) mode_out}, 600);
}
this->query_(CMD_SET_SETTING_STATUS, std::vector<uint8_t>{0x20, 0x01, (uint8_t) status_out}, 200);
this->last_set_status_byte_ = static_cast<uint8_t>(status_out);
this->last_set_mode_byte_ = mode_out; // 255 keeps mode-retry disabled for OFF (no SET_OPERATION_MODE was sent)
this->last_set_ms_ = millis();
this->status_retry_count_ = 0;
this->mode_retry_count_ = 0;
}
std::vector<uint8_t> temp_setpoint_args;
if (call.get_target_temperature_high().has_value()) {
Expand Down Expand Up @@ -321,6 +342,25 @@ void DaikinMadoka::parse_cb_(std::vector<uint8_t> msg) {
}
i += len;
}
// Status retry guard — see header comment on last_set_status_byte_.
if (this->last_set_ms_ > 0 &&
(millis() - this->last_set_ms_) < STATUS_RETRY_WINDOW_MS &&
((uint8_t) (this->cur_status_.status ? 1 : 0)) != this->last_set_status_byte_ &&
this->status_retry_count_ < MAX_STATUS_RETRIES) {
this->status_retry_count_++;
ESP_LOGW(TAG,
"[%s] SETTING_STATUS readback mismatch (got=%d, expected=%d), retry %d/%d",
this->get_name().c_str(),
(int) (this->cur_status_.status ? 1 : 0),
(int) this->last_set_status_byte_,
(int) this->status_retry_count_,
(int) MAX_STATUS_RETRIES);
this->query_(CMD_SET_SETTING_STATUS,
std::vector<uint8_t>{0x20, 0x01, this->last_set_status_byte_}, 200);
// Don't let the rest of parse_cb_ act on the stale readback — pretend the
// pulse acknowledged what we asked for; the next poll round will confirm.
this->cur_status_.status = (this->last_set_status_byte_ != 0);
}
break;
case CMD_GET_OPERATION_MODE:
while (i < message_size) {
Expand All @@ -332,6 +372,30 @@ void DaikinMadoka::parse_cb_(std::vector<uint8_t> msg) {
}
i += len;
}
// Mode retry guard — symmetric to the SETTING_STATUS retry above. If the
// last HA-issued SET_OPERATION_MODE was within the retry window and the
// pulse-reported byte still doesn't match (BLE drop or pulse hadn't
// applied yet), re-issue SET_OPERATION_MODE and pretend the readback
// already matched, so the switch below doesn't snap climate::mode back
// to the stale value. last_set_mode_byte_ == 255 means we haven't issued
// a mode-setting command (OFF doesn't go via OPERATION_MODE) — guard off.
if (this->last_set_ms_ > 0 &&
(millis() - this->last_set_ms_) < STATUS_RETRY_WINDOW_MS &&
this->last_set_mode_byte_ != 255 &&
!madoka_mode_byte_equiv(this->cur_status_.mode, this->last_set_mode_byte_) &&
this->mode_retry_count_ < MAX_STATUS_RETRIES) {
this->mode_retry_count_++;
ESP_LOGW(TAG,
"[%s] OPERATION_MODE readback mismatch (got=%d, expected=%d), retry %d/%d",
this->get_name().c_str(),
(int) this->cur_status_.mode,
(int) this->last_set_mode_byte_,
(int) this->mode_retry_count_,
(int) MAX_STATUS_RETRIES);
this->query_(CMD_SET_OPERATION_MODE,
std::vector<uint8_t>{0x20, 0x01, this->last_set_mode_byte_}, 600);
this->cur_status_.mode = this->last_set_mode_byte_;
}
break;
default:
break;
Expand Down
18 changes: 18 additions & 0 deletions esphome/components/daikin_madoka/daikin_madoka.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ class DaikinMadoka : public climate::Climate, public esphome::ble_client::BLECli
uint16_t wwr_handle_;
SemaphoreHandle_t receive_semaphore_ = nullptr;
Status cur_status_;
// Status retry guard. The BRC1H sometimes drops the SET_SETTING_STATUS chunk
// silently (typically when one ESP juggles two BRC1H pairs at once over BLE).
// The unit then stays physically off even though we just told it to turn on,
// and the next poll reports status=0 and flips climate::mode to OFF in HA.
// Remember the requested status for a short window and re-issue
// SET_SETTING_STATUS if the readback disagrees, up to a small cap.
uint8_t last_set_status_byte_{0};
uint32_t last_set_ms_{0};
uint8_t status_retry_count_{0};
// Mode retry guard, symmetric to the SETTING_STATUS retry above. Same drop
// scenario, this time for SET_OPERATION_MODE: chunk silently lost on BLE →
// next poll reports the old pulse-side mode → parse_cb_ flips climate::mode
// in HA back to that stale value. Remembering the raw byte we requested
// (0..5; 255 = unset, e.g. OFF which doesn't go via OPERATION_MODE) lets us
// re-issue SET_OPERATION_MODE up to MAX_STATUS_RETRIES times before
// accepting the readback as truth.
uint8_t last_set_mode_byte_{255};
uint8_t mode_retry_count_{0};

std::vector<std::vector<uint8_t>> split_payload_(std::vector<uint8_t> msg);
std::vector<uint8_t> prepare_message_(uint16_t cmd, std::vector<uint8_t> args);
Expand Down
Loading