diff --git a/rdtech-controller-c3.yaml b/rdtech-controller-c3.yaml index f2b258e..d82ac51 100644 --- a/rdtech-controller-c3.yaml +++ b/rdtech-controller-c3.yaml @@ -110,32 +110,68 @@ modbus_controller: setup_priority: -10 update_interval: 5s +# Persisted timestamp of the last RTC sync to the PSU. Stored in flash so +# we don't burn 6 Modbus writes on every reconnect. +globals: + - id: last_psu_time_sync + type: time_t + restore_value: yes + initial_value: '0' + time: - # Get the time from HA, so we can use it for uptime + # Get the time from HA, so we can use it for uptime and (rate-limited) + # mirror it to the PSU's RTC. - platform: homeassistant id: time_homeassistant timezone: "${time_timezone}" on_time_sync: - - logger.log: Time has been set and is valid! + - logger.log: "HA time sync received" - component.update: sensor_uptime_timestamp - - number.set: - id: date_year - value: !lambda return id(time_homeassistant).now().year; - - number.set: - id: date_month - value: !lambda return id(time_homeassistant).now().month; - - number.set: - id: date_day - value: !lambda return id(time_homeassistant).now().day_of_month; - - number.set: - id: date_hour - value: !lambda return id(time_homeassistant).now().hour; - - number.set: - id: date_minute - value: !lambda return id(time_homeassistant).now().minute; - - number.set: - id: date_second - value: !lambda return id(time_homeassistant).now().second; + # Sync the PSU clock at most once per 24h, and only write the date/time + # registers that actually differ. Avoids the original behaviour of + # firing six Modbus writes on every HA reconnect, which contended with + # sensor polling and wore the PSU's flash. + - if: + condition: + lambda: |- + const time_t now_ts = id(time_homeassistant).utcnow().timestamp; + if (now_ts - id(last_psu_time_sync) < 86400) return false; + // Skip if the date_* numbers haven't been polled yet. This + // can happen when on_time_sync arrives before the first + // Modbus cycle completes; comparing against NaN would force + // a write and then mark last_psu_time_sync, suppressing real + // drift checks for 24h. The next on_time_sync (15 min later + // by default) will retry with fresh values. + if (std::isnan(id(date_year).state)) return false; + auto now = id(time_homeassistant).now(); + const float psu_secs = id(date_hour).state * 3600.0f + + id(date_minute).state * 60.0f + + id(date_second).state; + const float ha_secs = now.hour * 3600.0f + + now.minute * 60.0f + + now.second; + return id(date_year).state != now.year + || id(date_month).state != now.month + || id(date_day).state != now.day_of_month + || std::abs(psu_secs - ha_secs) > 30.0f; + then: + - lambda: |- + auto now = id(time_homeassistant).now(); + auto write_if_diff = [](esphome::number::Number *n, float v) { + if (n->state != v) { + auto call = n->make_call(); + call.set_value(v); + call.perform(); + } + }; + write_if_diff(id(date_year), now.year); + write_if_diff(id(date_month), now.month); + write_if_diff(id(date_day), now.day_of_month); + write_if_diff(id(date_hour), now.hour); + write_if_diff(id(date_minute), now.minute); + write_if_diff(id(date_second), now.second); + id(last_psu_time_sync) = id(time_homeassistant).utcnow().timestamp; + ESP_LOGI("rdtech", "PSU clock synced from HA (delta-only)"); sensor: - platform: modbus_controller diff --git a/rdtech-controller.yaml b/rdtech-controller.yaml index d87db69..b068b04 100644 --- a/rdtech-controller.yaml +++ b/rdtech-controller.yaml @@ -108,32 +108,68 @@ modbus_controller: setup_priority: -10 update_interval: 5s +# Persisted timestamp of the last RTC sync to the PSU. Stored in flash so +# we don't burn 6 Modbus writes on every reconnect. +globals: + - id: last_psu_time_sync + type: time_t + restore_value: yes + initial_value: '0' + time: - # Get the time from HA, so we can use it for uptime + # Get the time from HA, so we can use it for uptime and (rate-limited) + # mirror it to the PSU's RTC. - platform: homeassistant id: time_homeassistant timezone: "${time_timezone}" on_time_sync: - - logger.log: Time has been set and is valid! + - logger.log: "HA time sync received" - component.update: sensor_uptime_timestamp - - number.set: - id: date_year - value: !lambda return id(time_homeassistant).now().year; - - number.set: - id: date_month - value: !lambda return id(time_homeassistant).now().month; - - number.set: - id: date_day - value: !lambda return id(time_homeassistant).now().day_of_month; - - number.set: - id: date_hour - value: !lambda return id(time_homeassistant).now().hour; - - number.set: - id: date_minute - value: !lambda return id(time_homeassistant).now().minute; - - number.set: - id: date_second - value: !lambda return id(time_homeassistant).now().second; + # Sync the PSU clock at most once per 24h, and only write the date/time + # registers that actually differ. Avoids the original behaviour of + # firing six Modbus writes on every HA reconnect, which contended with + # sensor polling and wore the PSU's flash. + - if: + condition: + lambda: |- + const time_t now_ts = id(time_homeassistant).utcnow().timestamp; + if (now_ts - id(last_psu_time_sync) < 86400) return false; + // Skip if the date_* numbers haven't been polled yet. This + // can happen when on_time_sync arrives before the first + // Modbus cycle completes; comparing against NaN would force + // a write and then mark last_psu_time_sync, suppressing real + // drift checks for 24h. The next on_time_sync (15 min later + // by default) will retry with fresh values. + if (std::isnan(id(date_year).state)) return false; + auto now = id(time_homeassistant).now(); + const float psu_secs = id(date_hour).state * 3600.0f + + id(date_minute).state * 60.0f + + id(date_second).state; + const float ha_secs = now.hour * 3600.0f + + now.minute * 60.0f + + now.second; + return id(date_year).state != now.year + || id(date_month).state != now.month + || id(date_day).state != now.day_of_month + || std::abs(psu_secs - ha_secs) > 30.0f; + then: + - lambda: |- + auto now = id(time_homeassistant).now(); + auto write_if_diff = [](esphome::number::Number *n, float v) { + if (n->state != v) { + auto call = n->make_call(); + call.set_value(v); + call.perform(); + } + }; + write_if_diff(id(date_year), now.year); + write_if_diff(id(date_month), now.month); + write_if_diff(id(date_day), now.day_of_month); + write_if_diff(id(date_hour), now.hour); + write_if_diff(id(date_minute), now.minute); + write_if_diff(id(date_second), now.second); + id(last_psu_time_sync) = id(time_homeassistant).utcnow().timestamp; + ESP_LOGI("rdtech", "PSU clock synced from HA (delta-only)"); sensor: - platform: modbus_controller