#:########################################################################################:#
# TITLE: COMMON SNTP TIME SOURCE
# zorruno.com common ESPHome include
#:########################################################################################:#
# FILE:
# common/sntp_common.yaml
#:########################################################################################:#
# VERSIONS:
# V1.1 2026-05-23 Fixed offline fallback clock so it increments from assumed noon boot time
# V1.0 Initial common SNTP time source and diagnostic sensors
#:########################################################################################:#
# PURPOSE:
# - Provides an SNTP time source for ESPHome devices.
# - Stores the current time as minutes since midnight in current_mins.
# - Provides a simple fallback clock if SNTP is not available.
# - Publishes diagnostic text sensors for last restart, current/internal time, and sync status.
#:########################################################################################:#
# OPERATION NOTES:
# - SNTP time is stored in id(sntp_time).
# - Current minutes since midnight are stored in id(current_mins).
# - If SNTP is valid, current_mins follows the real SNTP time.
# - If SNTP is invalid, current_mins increments once per minute from the assumed boot time.
# - The fallback assumes the device was powered on at 12:00 noon.
# - If accurate offline time is needed, power-cycle the device at 12:00 noon.
#:########################################################################################:#

#:########################################################################################:#
# SUBSTITUTIONS: Specific SNTP variable substitutions
# If NOT using a secrets file, replace these values directly in quotes.
#:########################################################################################:#
substitutions:
  timezone: "Pacific/Auckland"
  sntp_update_interval: 6h

  # Network time servers
  # https://www.ntppool.org/zone/@
  sntp_server_1: !secret ntp_server_1
  sntp_server_2: !secret ntp_server_2
  sntp_server_3: !secret ntp_server_3

#:########################################################################################:#
# GLOBALS: Internal clock fallback support
#:########################################################################################:#
globals:
  - id: current_mins
    type: int
    restore_value: false
    initial_value: "720" # 720 minutes from midnight is 12:00 noon.

#:########################################################################################:#
# INTERVAL: Keep current_mins updated
#:########################################################################################:#
interval:
  - interval: 60s
    then:
      - lambda: |-
          // If SNTP is valid, store real minutes since midnight.
          auto now = id(sntp_time).now();
          if (now.is_valid()) {
            id(current_mins) = (now.hour * 60) + now.minute;
          } else {
            // Fallback: increment from the assumed startup time.
            // Initial value is 720, so offline boot assumes 12:00 noon.
            id(current_mins) = (id(current_mins) + 1) % 1440;
          }

#:########################################################################################:#
# TIME: Real time clock source for ESPHome
# https://esphome.io/components/time/index.html
# https://esphome.io/components/time/sntp
#:########################################################################################:#
time:
  - platform: sntp
    id: sntp_time
    timezone: "${timezone}"
    update_interval: ${sntp_update_interval}
    servers:
      - "${sntp_server_1}"
      - "${sntp_server_2}"
      - "${sntp_server_3}"
    on_time_sync:
      then:
        - logger.log: "Synchronised SNTP clock"

        - text_sensor.template.publish:
            id: time_sync
            state: "SNTP clock synced"

        # Update last restart time once only, after the first successful SNTP sync.
        - if:
            condition:
              lambda: |-
                return id(device_last_restart).state.empty();
            then:
              - text_sensor.template.publish:
                  id: device_last_restart
                  state: !lambda |-
                    auto now = id(sntp_time).now();
                    return now.strftime("%a %d %b %Y - %I:%M:%S %p");

#:########################################################################################:#
# TEXT SENSOR COMPONENT:
# https://esphome.io/components/text_sensor/
#:########################################################################################:#
text_sensor:
  # Shows when the device was last restarted, based on the first successful SNTP sync.
  - platform: template
    name: "Time: Last Restart"
    id: device_last_restart
    icon: mdi:clock
    entity_category: diagnostic
    update_interval: never

  # Shows the current SNTP time if valid, otherwise the internal fallback time.
  - platform: template
    name: "Time: Internal Time"
    id: time_text
    icon: mdi:clock-outline
    update_interval: 1min
    entity_category: diagnostic
    lambda: |-
      if (id(sntp_time).now().is_valid()) {
        auto t = id(sntp_time).now().strftime("%H:%M:%S - %d-%m-%Y");
        return { t };
      }

      int mins = id(current_mins);
      if (mins < 0) mins = 0;
      if (mins >= 1440) mins = mins % 1440;

      int hour = mins / 60;
      int minute = mins % 60;

      char buff[32];
      snprintf(buff, sizeof(buff), "%02d:%02d (fallback)", hour, minute);
      return { std::string(buff) };

  # Shows whether the device is using SNTP or the fallback clock.
  - platform: template
    name: "Time: Sync Status"
    id: time_sync
    icon: mdi:clock-check-outline
    update_interval: 1min
    entity_category: diagnostic
    lambda: |-
      if (id(sntp_time).now().is_valid()) {
        return { std::string("SNTP clock synced") };
      }

      return { std::string("Using fallback internal clock") };