# yaml-language-server: $schema=null

###############################################################################
# PACKAGE: 16A EV Charger Control
###############################################################################
#
# FILE:
#   packages/16a_ev_charging_controls.yaml
#
# VERSION:
#   V1.2 2026-07-20
#     - Added structured mode, timing, and restart behavior documentation.
#
#   V1.1 2026-05-15
#     - Reworked 1hr topup logic to use a hard deadline and watchdog.
#     - Removed dependency on timer.finished event for relay shutoff.
#     - Offpeak until charged now continues past offpeak end until charging stops.
#     - 1hr topup now rolls into Normal Offpeak if the hour ends during
#       the offpeak window, without switching the relay off and back on.
#
#   V1.0 2026-05-11
#     - Initial package for 16A EV charger offpeak and charge-until-full control.
#
# PURPOSE:
#   Controls the Leaf 16A charger relay using scheduled offpeak operation and
#   user-selectable immediate or charge-until-full modes.
#
# DEPENDENCIES:
#   - switch.garage_db_control_16a_wallcharger_operation controls charger power.
#   - binary_sensor.garage_db_control_16a_wallcharger_charging reports whether
#     the vehicle is actively charging.
#
# CHARGE MODES:
#   - Normal (Offpeak) turns the relay on and off at the configured offpeak
#     start and end times. Selecting this mode takes no immediate action.
#   - Offpeak until charged starts at the offpeak time, or immediately if
#     selected during offpeak, and may continue beyond offpeak end until the
#     vehicle stops charging.
#   - 1hr topup now turns the relay on immediately and records a hard deadline
#     one hour ahead.
#   - Charge now until full turns the relay on immediately and keeps it on until
#     the vehicle stops charging.
#   - Stop charging now turns the relay off immediately and returns the selector
#     to Normal (Offpeak).
#
# TIMING AND COMPLETION:
#   - Charge-until-full modes allow five minutes for charging to start. This
#     also handles a vehicle that is already full.
#   - Charging is considered complete after the charging sensor remains off for
#     five minutes.
#   - The one-hour topup deadline is stored in input_datetime and checked every
#     minute and when Home Assistant starts.
#   - If a topup ends during offpeak, the relay remains on and the mode returns
#     to Normal (Offpeak), avoiding an off/on relay cycle.
#   - Equal offpeak start and end times are interpreted as an all-day window.
#
# OPERATION NOTES:
#   - Normal offpeak start and end use exact-time triggers.
#   - There is no startup catch-up if Home Assistant is offline at either time.
#
# PACKAGE SETTINGS:
#   Car display name:
#     - Leaf
#
#   Charger relay output:
#     - switch.garage_db_control_16a_wallcharger_operation
#
#   Vehicle charging sensor:
#     - binary_sensor.garage_db_control_16a_wallcharger_charging
#
#   Default offpeak period:
#     - Start: 21:00:00
#     - End:   00:00:00
#
###############################################################################

input_datetime:
  ev_16a_offpeak_start:
    name: 16A EV Offpeak Start
    has_date: false
    has_time: true
    initial: "21:00:00"

  ev_16a_offpeak_end:
    name: 16A EV Offpeak End
    has_date: false
    has_time: true
    initial: "00:00:00"

  ev_16a_1hr_topup_until:
    name: 16A EV 1hr topup until
    has_date: true
    has_time: true
    initial: "2000-01-01 00:00:00"

input_select:
  ev_16a_charge_mode:
    name: Leaf EV charge mode
    icon: mdi:ev-station
    options:
      - "Normal (Offpeak)"
      - "Offpeak until charged"
      - "1hr topup now"
      - "Charge now until full"
      - "Stop charging now"
    initial: "Normal (Offpeak)"

automation:
  #############################################################################
  # OFFPEAK START
  #############################################################################

  - id: ev_16a_charger_offpeak_start
    alias: "16A EV Charger - Offpeak Start"
    mode: single

    triggers:
      - trigger: time
        at: input_datetime.ev_16a_offpeak_start

    conditions:
      - condition: template
        value_template: >
          {{ states('input_select.ev_16a_charge_mode') in
             ['Normal (Offpeak)', 'Offpeak until charged'] }}

    actions:
      - action: switch.turn_on
        target:
          entity_id: switch.garage_db_control_16a_wallcharger_operation

      - choose:
          - conditions:
              - condition: state
                entity_id: input_select.ev_16a_charge_mode
                state: "Offpeak until charged"
            sequence:
              # If the car never starts charging, or is already full, turn off
              # after a 5 minute grace period.
              - delay: "00:05:00"

              - condition: state
                entity_id: input_select.ev_16a_charge_mode
                state: "Offpeak until charged"

              - condition: state
                entity_id: binary_sensor.garage_db_control_16a_wallcharger_charging
                state: "off"

              - action: switch.turn_off
                target:
                  entity_id: switch.garage_db_control_16a_wallcharger_operation

              - action: input_select.select_option
                target:
                  entity_id: input_select.ev_16a_charge_mode
                data:
                  option: "Normal (Offpeak)"

  #############################################################################
  # OFFPEAK END
  #############################################################################

  - id: ev_16a_charger_offpeak_end
    alias: "16A EV Charger - Offpeak End"
    mode: single

    triggers:
      - trigger: time
        at: input_datetime.ev_16a_offpeak_end

    conditions:
      - condition: state
        entity_id: input_select.ev_16a_charge_mode
        state: "Normal (Offpeak)"

    actions:
      - action: switch.turn_off
        target:
          entity_id: switch.garage_db_control_16a_wallcharger_operation

  #############################################################################
  # USER CHARGE MODE SELECTIONS
  #############################################################################

  - id: ev_16a_charger_charge_mode_selected
    alias: "16A EV Charger - Charge Mode Selected"
    mode: restart

    triggers:
      - trigger: state
        entity_id: input_select.ev_16a_charge_mode

    actions:
      - variables:
          selected_mode: "{{ trigger.to_state.state }}"
          is_offpeak_now: >
            {% set start = states('input_datetime.ev_16a_offpeak_start') %}
            {% set end = states('input_datetime.ev_16a_offpeak_end') %}
            {% set now_time = now().strftime('%H:%M:%S') %}
            {% if start == end %}
              {{ true }}
            {% elif start < end %}
              {{ start <= now_time < end }}
            {% else %}
              {{ now_time >= start or now_time < end }}
            {% endif %}

      - choose:
          #####################################################################
          # NORMAL OFFPEAK
          #####################################################################

          - conditions:
              - condition: template
                value_template: "{{ selected_mode == 'Normal (Offpeak)' }}"
            sequence:
              # No immediate action.
              # Normal mode only acts when the offpeak start/end triggers fire.
              - stop: "Normal offpeak mode selected."

          #####################################################################
          # OFFPEAK UNTIL CHARGED
          #####################################################################

          - conditions:
              - condition: template
                value_template: "{{ selected_mode == 'Offpeak until charged' }}"
            sequence:
              - if:
                  - condition: template
                    value_template: "{{ is_offpeak_now }}"
                then:
                  - action: switch.turn_on
                    target:
                      entity_id: switch.garage_db_control_16a_wallcharger_operation

                  # If the car never starts charging, or is already full, turn
                  # off after a 5 minute grace period.
                  - delay: "00:05:00"

                  - condition: state
                    entity_id: input_select.ev_16a_charge_mode
                    state: "Offpeak until charged"

                  - condition: state
                    entity_id: binary_sensor.garage_db_control_16a_wallcharger_charging
                    state: "off"

                  - action: switch.turn_off
                    target:
                      entity_id: switch.garage_db_control_16a_wallcharger_operation

                  - action: input_select.select_option
                    target:
                      entity_id: input_select.ev_16a_charge_mode
                    data:
                      option: "Normal (Offpeak)"

          #####################################################################
          # 1 HOUR TOPUP NOW
          #####################################################################

          - conditions:
              - condition: template
                value_template: "{{ selected_mode == '1hr topup now' }}"
            sequence:
              - action: input_datetime.set_datetime
                target:
                  entity_id: input_datetime.ev_16a_1hr_topup_until
                data:
                  datetime: >
                    {{ (as_timestamp(now()) + 3600)
                       | timestamp_custom('%Y-%m-%d %H:%M:%S', true) }}

              - action: switch.turn_on
                target:
                  entity_id: switch.garage_db_control_16a_wallcharger_operation

          #####################################################################
          # CHARGE NOW UNTIL FULL
          #####################################################################

          - conditions:
              - condition: template
                value_template: "{{ selected_mode == 'Charge now until full' }}"
            sequence:
              - action: switch.turn_on
                target:
                  entity_id: switch.garage_db_control_16a_wallcharger_operation

              # If the car never starts charging, or is already full, turn off
              # after a 5 minute grace period.
              - delay: "00:05:00"

              - condition: state
                entity_id: input_select.ev_16a_charge_mode
                state: "Charge now until full"

              - condition: state
                entity_id: binary_sensor.garage_db_control_16a_wallcharger_charging
                state: "off"

              - action: switch.turn_off
                target:
                  entity_id: switch.garage_db_control_16a_wallcharger_operation

              - action: input_select.select_option
                target:
                  entity_id: input_select.ev_16a_charge_mode
                data:
                  option: "Normal (Offpeak)"

          #####################################################################
          # STOP CHARGING NOW
          #####################################################################

          - conditions:
              - condition: template
                value_template: "{{ selected_mode == 'Stop charging now' }}"
            sequence:
              - action: switch.turn_off
                target:
                  entity_id: switch.garage_db_control_16a_wallcharger_operation

              - action: input_select.select_option
                target:
                  entity_id: input_select.ev_16a_charge_mode
                data:
                  option: "Normal (Offpeak)"

  #############################################################################
  # 1 HOUR TOPUP WATCHDOG
  #############################################################################

  - id: ev_16a_charger_1hr_topup_watchdog
    alias: "16A EV Charger - 1hr Topup Watchdog"
    mode: single

    triggers:
      - trigger: time_pattern
        minutes: "/1"

      - trigger: homeassistant
        event: start

    conditions:
      - condition: state
        entity_id: input_select.ev_16a_charge_mode
        state: "1hr topup now"

      - condition: template
        value_template: >
          {{ as_timestamp(now()) >=
             as_timestamp(states('input_datetime.ev_16a_1hr_topup_until')) }}

    actions:
      - variables:
          is_offpeak_now: >
            {% set start = states('input_datetime.ev_16a_offpeak_start') %}
            {% set end = states('input_datetime.ev_16a_offpeak_end') %}
            {% set now_time = now().strftime('%H:%M:%S') %}
            {% if start == end %}
              {{ true }}
            {% elif start < end %}
              {{ start <= now_time < end }}
            {% else %}
              {{ now_time >= start or now_time < end }}
            {% endif %}

      - action: input_select.select_option
        target:
          entity_id: input_select.ev_16a_charge_mode
        data:
          option: "Normal (Offpeak)"

      - choose:
          # If the 1 hour topup finishes during offpeak, do not flick the relay.
          # Leave it on and let the Normal Offpeak end automation turn it off.
          - conditions:
              - condition: template
                value_template: "{{ is_offpeak_now }}"
            sequence:
              - if:
                  - condition: state
                    entity_id: switch.garage_db_control_16a_wallcharger_operation
                    state: "off"
                then:
                  - action: switch.turn_on
                    target:
                      entity_id: switch.garage_db_control_16a_wallcharger_operation

        default:
          - if:
              - condition: state
                entity_id: switch.garage_db_control_16a_wallcharger_operation
                state: "on"
            then:
              - action: switch.turn_off
                target:
                  entity_id: switch.garage_db_control_16a_wallcharger_operation

  #############################################################################
  # CHARGING COMPLETE MONITOR
  #############################################################################

  - id: ev_16a_charger_stop_when_charging_complete
    alias: "16A EV Charger - Stop When Charging Complete"
    mode: single

    triggers:
      - trigger: state
        entity_id: binary_sensor.garage_db_control_16a_wallcharger_charging
        to: "off"
        for: "00:05:00"

    conditions:
      - condition: template
        value_template: >
          {{ states('input_select.ev_16a_charge_mode') in
             ['Offpeak until charged', 'Charge now until full'] }}

    actions:
      - action: switch.turn_off
        target:
          entity_id: switch.garage_db_control_16a_wallcharger_operation

      - action: input_select.select_option
        target:
          entity_id: input_select.ev_16a_charge_mode
        data:
          option: "Normal (Offpeak)"
