#:########################################################################################:#
# Appliances Status Monitoring Package                                                    #
#:########################################################################################:#
#
# FILE:
#   appliances_status_monitoring.yaml
#
# VERSION:
#   V1.5 2026-07-11
#
# PURPOSE:
#   Appliance status monitoring package for multiple appliances.
#
#   This package defines a central appliance registry and one automation that:
#
#     1. Publishes MQTT Discovery configuration for each appliance monitor
#        sensor.
#     2. Samples appliance power sensors once per minute.
#     3. Calls script.appliance_monitor_process_sample.
#     4. Passes per-appliance completion announcement settings to the processor.
#
# REQUIRED PACKAGES:
#   appliance_monitor_processor.yaml V1.3 or newer
#   view_road_announcement_router.yaml V1.8 or newer
#
# MQTT:
#   MQTT is used as retained state storage for appliance monitor sensors.
#   Sensor entities are created using MQTT Discovery.
#
# ADDING AN APPLIANCE:
#   Add one entry to the am_appliances registry below.
#
# ANNOUNCEMENT ROUTER CHANNELS:
#   The following channel IDs are accepted by the current announcement router:
#
#     pushover_zorruno
#     pushover_kim
#     sony_tv_lounge
#     lounge_google_home_voice
#     google_home_voice
#     lounge_touchscreen_voice
#     mqtt_view_rd_feed
#     led_matrix_1
#     led_matrix_2
#
#   Notes:
#     - google_home_voice is an alias for lounge_google_home_voice.
#     - lounge_touchscreen_voice is currently a placeholder route.
#     - mqtt_view_rd_feed requires mqtt_feed.
#     - led_matrix_1 and led_matrix_2 use short_announcement and
#       matrix_indicator.
#
# VERSION HISTORY:
#   V1.6 2026-07-24
#     - Added startup delay guard to prevent false completion announcements.
#     - Added power_available field for the processor availability check.
#
#   V1.5 2026-07-11
#     - Added full View Road announcement router parameter support.
#     - Added per-appliance announcement_enabled.
#     - Added per-appliance announcement_payload.
#     - Added per-appliance mqtt_feed for mqtt_view_rd_feed.
#     - Added safe defaults for short announcement and matrix indicator.
#     - Documented all current announcement router channel names.
#     - Existing per-appliance channel selections retained unchanged.
#
#   V1.4 2026-06-09
#     - Added min_cycle_notify_seconds support.
#     - Default is 300 seconds, so cycles shorter than 5 minutes are recorded
#       but do not send finished notifications.
#     - Per-appliance overrides can be set in the am_appliances list.
#
#   V1.3:
#     - Removed the is_number gate before calling the processor.
#     - The processor is always called once per appliance per sample.
#     - Power is safely converted to 0 if unavailable or non-numeric.
#
#:########################################################################################:#

automation:
  - id: appliances_status_monitoring_process_samples
    alias: "Appliances Status Monitoring - Process Samples"
    description: >-
      Publishes MQTT Discovery, samples appliance power sensors, processes
      appliance status and cycle data, and passes completion announcement
      settings to the shared processor.
    mode: queued
    max: 20

    trigger:
      - platform: homeassistant
        event: start
        id: ha_start

      - platform: time_pattern
        minutes: "/1"
        id: periodic_sample

    variables:
      #:####################################################################################:#
      # MQTT LOCAL CONTROLS                                                                  #
      #:####################################################################################:#

      mqtt_device_name: "appliance_monitor"
      mqtt_local_status_topic: !secret mqtt_status_main_topic
      mqtt_discovery_prefix: "homeassistant"

      #:####################################################################################:#
      # PROCESSOR DEFAULTS                                                                   #
      #:####################################################################################:#

      am_default_debug_flow: false
      am_default_resolution: 2
      am_default_standby_power: 2
      am_default_operating_power_min: 12
      am_default_min_cycle_notify_seconds: 300

      #:####################################################################################:#
      # ANNOUNCEMENT DEFAULTS                                                                #
      #:####################################################################################:#
      #
      # These defaults apply when an appliance does not define its own values.
      #
      # A conservative default channel is retained to prevent additional
      # notifications suddenly being enabled for every appliance.
      #
      # To make all implemented channels the default, this could instead be:
      #
      # am_default_announcement_channels: >-
      #   pushover_zorruno,pushover_kim,sony_tv_lounge,
      #   lounge_google_home_voice,mqtt_view_rd_feed,
      #   led_matrix_1,led_matrix_2
      #
      # Available channels:
      #   pushover_zorruno
      #   pushover_kim
      #   sony_tv_lounge
      #   lounge_google_home_voice
      #   google_home_voice
      #   lounge_touchscreen_voice
      #   mqtt_view_rd_feed
      #   led_matrix_1
      #   led_matrix_2

      am_default_announcement_enabled: true
      am_default_announcement_channels: "pushover_zorruno"
      am_default_announcement_payload: ""

      #:####################################################################################:#
      # OPTIONAL PER-APPLIANCE OVERRIDES                                                     #
      #:####################################################################################:#
      #
      # debug_flow: true
      #   Writes state-processing details to the Home Assistant logbook.
      #
      # resolution: 4
      #   Number of one-minute samples used for the rolling average.
      #
      # standby_power: 3
      #   Below this value, the appliance shows Off.
      #   Between standby_power and operating_power_min, it shows Standby.
      #
      # operating_power_min: 20
      #   Minimum rolling-average power required for Operating.
      #
      # min_cycle_notify_seconds: 600
      #   Minimum cycle duration before a completion announcement is allowed.
      #
      # announcement_enabled: false
      #   Disables cycle-completion announcements for this appliance.
      #
      # announcement_channels: >-
      #   pushover_zorruno,pushover_kim,sony_tv_lounge,
      #   lounge_google_home_voice,mqtt_view_rd_feed,
      #   led_matrix_1,led_matrix_2
      #   Comma-separated View Road announcement router channels.
      #
      # title: "Washing Machine Notification"
      #   Optional announcement title.
      #
      # short_announcement: "Washing complete"
      #   Used by TV, voice and LED matrix channels.
      #
      # long_announcement: "The washing machine has finished."
      #   Used by Pushover and other detailed channels.
      #   If omitted, the processor creates a detailed cycle summary.
      #
      # announcement_payload: ""
      #   Optional payload passed to the announcement router.
      #
      # matrix_indicator: "wash,1,15,10,6,5,1,0"
      #   LED matrix indicator data.
      #
      # mqtt_feed: "washing_machine"
      #   Activity-feed suffix used by mqtt_view_rd_feed.
      #   If omitted, appliance_id is used.
      #
      #:####################################################################################:#

      #:####################################################################################:#
      # APPLIANCE REGISTRY                                                                  #
      #:####################################################################################:#

      am_appliances:
        #:##################################################################################:#
        # Washing Machine                                                                   #
        #:##################################################################################:#

        - appliance_id: washing_machine
          appliance_name: "Washing Machine"
          appliance_action: "washing"
          power_entity: "sensor.esp_laundrywashpow_load_power"
          icon: "mdi:washing-machine"

          resolution: 5
          standby_power: 3
          operating_power_min: 10

          announcement_enabled: true
          announcement_channels: "pushover_zorruno,pushover_kim,sony_tv_lounge,led_matrix_1"
          short_announcement: "/s/sWashing complete"
          # long_announcement:    # Generated by the processor
          announcement_payload: ""
          matrix_indicator: "wash,1,15,10,6,5,1,0"
          mqtt_feed: "Washer_complete"

        #:##################################################################################:#
        # Dryer                                                                             #
        #:##################################################################################:#

        - appliance_id: dryer
          appliance_name: "Dryer"
          appliance_action: "drying"
          power_entity: "sensor.esp_laundrydrypow_load_power"
          icon: "mdi:tumble-dryer"

          resolution: 4
          standby_power: 3
          operating_power_min: 10

          announcement_enabled: true
          announcement_channels: "pushover_zorruno,pushover_kim,sony_tv_lounge,led_matrix_1"
          short_announcement: "/s/sDryer complete"
          # long_announcement:    # Generated by the processor
          announcement_payload: ""
          matrix_indicator: "dry,1,15,10,6,5,1,0"
          mqtt_feed: "Dryer_complete"

        #:##################################################################################:#
        # Main Dishwasher                                                                   #
        #:##################################################################################:#

        - appliance_id: main_dishwasher
          appliance_name: "Main Dishwasher"
          appliance_action: "dishwashing"
          power_entity: "sensor.esp_maindishwasherpower_power"
          icon: "mdi:dishwasher"

          resolution: 5
          standby_power: 3
          operating_power_min: 30

          announcement_enabled: true
          announcement_channels: "pushover_zorruno,pushover_kim,sony_tv_lounge,led_matrix_1"
          short_announcement: "/sDishwasher complete"
          # long_announcement:    # Generated by the processor
          announcement_payload: ""
          matrix_indicator: "dish,1,15,10,6,5,1,0"
          mqtt_feed: "Dishwasher_complete"

        #:##################################################################################:#
        # Downstairs Dishwasher                                                             #
        #:##################################################################################:#

        - appliance_id: downstairs_dishwasher
          appliance_name: "Downstairs Dishwasher"
          appliance_action: "dishwashing"
          power_entity: "sensor.esp_downstdishwasher_power"
          icon: "mdi:dishwasher"

          resolution: 5
          standby_power: 3
          operating_power_min: 30

          announcement_enabled: true
          announcement_channels: "pushover_zorruno"
          short_announcement: "/sDownstairs Dishwasher complete"
          # long_announcement:    # Generated by the processor
          announcement_payload: ""
          matrix_indicator: "dish2,1,15,10,6,5,1,0"
          mqtt_feed: "Downstairs_dishwasher_complete"

        #:##################################################################################:#
        # EV Charger 32A                                                                    #
        #:##################################################################################:#

        - appliance_id: evcharger_32a
          appliance_name: "EV Charger (32A)"
          appliance_action: "charging"
          power_entity: "sensor.main_house_3_phase_power_monitor_32a_wallcharger_power"
          monitor_sensor: "sensor.appliance_monitor_ev_charger_32a_monitor"
          icon: "mdi:ev-plug-type2"

          resolution: 6
          standby_power: 3
          operating_power_min: 100

          announcement_enabled: true
          announcement_channels: "pushover_zorruno,pushover_kim"
          short_announcement: "/s/s32A EV Charging complete"
          # long_announcement:    # Generated by the processor
          announcement_payload: ""
          matrix_indicator: "ev32a,1,15,10,6,5,1,0"
          mqtt_feed: "Atto_Charger_complete"

        #:##################################################################################:#
        # EV Charger 16A                                                                    #
        #:##################################################################################:#

        - appliance_id: evcharger_16a
          appliance_name: "EV Charger (16A)"
          appliance_action: "charging"
          power_entity: "sensor.garage_db_control_16a_wallcharger_power"
          monitor_sensor: "sensor.appliance_monitor_ev_charger_16a_monitor"
          icon: "mdi:ev-plug-type1"

          resolution: 6
          standby_power: 3
          operating_power_min: 100

          announcement_enabled: true
          announcement_channels: "pushover_zorruno"
          short_announcement: "/s/s16A EV Charging complete"
          # long_announcement:    # Generated by the processor
          announcement_payload: ""
          matrix_indicator: "ev16a,1,15,10,6,5,1,0"
          mqtt_feed: "Leaf_Charger_complete"

        #:##################################################################################:#
        # Ryobi Charger Left                                                                #
        #:##################################################################################:#

        - appliance_id: ryobicharger_left
          appliance_name: "Ryobi Charger Left"
          appliance_action: "charging"
          power_entity: "sensor.ryobi_charger_left_power_monitor_x17pp_power"
          icon: "mdi:battery-charging-medium"

          resolution: 4
          standby_power: 1
          operating_power_min: 20

          announcement_enabled: true
          announcement_channels: "pushover_zorruno"
          short_announcement: "/s/sRyobi Charging Left"
          # long_announcement:    # Generated by the processor
          announcement_payload: ""
          matrix_indicator: "ryo1,1,15,10,6,5,1,0"
          mqtt_feed: "Ryobi_Charger_1_complete"

        #:##################################################################################:#
        # Ryobi Charger Right                                                               #
        #:##################################################################################:#

        - appliance_id: ryobicharger_right
          appliance_name: "Ryobi Charger Right"
          appliance_action: "charging"
          power_entity: "sensor.ryobi_charger_right_power_monitor_x16pp_power"
          icon: "mdi:battery-charging-medium"

          resolution: 4
          standby_power: 1
          operating_power_min: 20

          announcement_enabled: true
          announcement_channels: "pushover_zorruno"
          short_announcement: "/s/sRyobi Charging Right"
          # long_announcement:    # Generated by the processor
          announcement_payload: ""
          matrix_indicator: "ryo2,1,15,10,6,5,1,0"
          mqtt_feed: "Ryobi_Charger_2_complete"

        #:##################################################################################:#
        # Pool Pump                                                                         #
        #:##################################################################################:#

        - appliance_id: pool_pump
          appliance_name: "Pool Pump"
          appliance_action: "pumping"
          power_entity: "sensor.esp_poolpumppower_power"
          icon: "mdi:pool"

          resolution: 4
          standby_power: 5
          operating_power_min: 20

          announcement_enabled: true
          announcement_channels: "pushover_zorruno"
          short_announcement: "Pool Pump Off"
          # long_announcement:    # Generated by the processor
          announcement_payload: ""
          matrix_indicator: "pump,1,15,10,6,5,1,0"
          mqtt_feed: "pool_pump"

        #:##################################################################################:#
        # 3D Printer                                                                        #
        #:##################################################################################:#

        - appliance_id: 3d_printer
          appliance_name: "3D Printer"
          appliance_action: "printing"
          power_entity: "sensor.esp_3dprinterpow_load_power"
          icon: "mdi:printer-3d"

          resolution: 4
          standby_power: 2
          operating_power_min: 20

          announcement_enabled: true
          announcement_channels: "pushover_zorruno"
          short_announcement: "3D printer finished"
          # long_announcement:    # Generated by the processor
          announcement_payload: ""
          matrix_indicator: "pump,1,15,10,6,5,1,0"
          mqtt_feed: "3d_printer"

    action:
      # On startup, wait for power sensors to report real values before processing.
      - choose:
          - conditions:
              - condition: trigger
                id:
                  - ha_start
            sequence:
              - delay: "00:01:00"

      #:####################################################################################:#
      # PUBLISH / REFRESH MQTT DISCOVERY CONFIG                                              #
      #:####################################################################################:#

      - repeat:
          for_each: "{{ am_appliances }}"
          sequence:
            - variables:
                am_state_topic: >-
                  {{ mqtt_local_status_topic }}/{{ mqtt_device_name }}/{{ repeat.item.appliance_id }}/state

                am_discovery_topic: >-
                  {{ mqtt_discovery_prefix }}/sensor/{{ mqtt_device_name }}_{{ repeat.item.appliance_id }}/config

                am_unique_id: >-
                  {{ repeat.item.appliance_id }}_monitor

                am_object_id: >-
                  {{ mqtt_device_name }}_{{ repeat.item.appliance_id }}_monitor

            - action: mqtt.publish
              data:
                topic: "{{ am_discovery_topic }}"
                retain: true
                qos: 0
                payload: >-
                  {{
                    dict(
                      name=repeat.item.appliance_name ~ ' Monitor',
                      unique_id=am_unique_id,
                      object_id=am_object_id,
                      icon=repeat.item.icon | default('mdi:power-plug'),
                      state_topic=am_state_topic,
                      value_template='{{ value_json.state | default("Off") }}',
                      json_attributes_topic=am_state_topic,
                      device=dict(
                        identifiers=[mqtt_device_name],
                        name='Appliance Monitor',
                        manufacturer='View Road Home Assistant',
                        model='Shared appliance power monitor',
                        sw_version='1.5'
                      )
                    ) | to_json
                  }}

      #:####################################################################################:#
      # PROCESS SAMPLES                                                                      #
      #:####################################################################################:#
      #
      # Always process every appliance. The processor receives power_w as a safe
      # float value, so unavailable or non-numeric sensors become 0 W.

      - repeat:
          for_each: "{{ am_appliances }}"
          sequence:
            - variables:
                am_state_topic: >-
                  {{ mqtt_local_status_topic }}/{{ mqtt_device_name }}/{{ repeat.item.appliance_id }}/state

                am_monitor_sensor: >-
                  {{
                    repeat.item.monitor_sensor
                    | default(
                        'sensor.'
                        ~ mqtt_device_name
                        ~ '_'
                        ~ repeat.item.appliance_id
                        ~ '_monitor',
                        true
                      )
                  }}

                am_title: >-
                  {{
                    repeat.item.title
                    | default(
                        repeat.item.appliance_name ~ ' Notification',
                        true
                      )
                  }}

                am_announcement_enabled: >-
                  {{
                    repeat.item.announcement_enabled
                    | default(am_default_announcement_enabled)
                  }}

                am_announcement_channels: >-
                  {{
                    repeat.item.announcement_channels
                    | default(am_default_announcement_channels, true)
                  }}

                am_announcement_payload: >-
                  {{
                    repeat.item.announcement_payload
                    | default(am_default_announcement_payload, true)
                  }}

                am_mqtt_feed: >-
                  {{
                    repeat.item.mqtt_feed
                    | default(repeat.item.appliance_id, true)
                  }}

                am_short_announcement: >-
                  {{
                    repeat.item.short_announcement
                    | default(
                        (repeat.item.appliance_action | title) ~ ' complete',
                        true
                      )
                  }}

                am_matrix_indicator: >-
                  {{ repeat.item.matrix_indicator | default('', true) }}

                am_long_announcement: >-
                  {{ repeat.item.long_announcement | default('', true) }}

                am_debug_flow: >-
                  {{
                    repeat.item.debug_flow
                    | default(am_default_debug_flow)
                  }}

                am_resolution: >-
                  {{
                    repeat.item.resolution
                    | default(am_default_resolution, true)
                  }}

                am_standby_power: >-
                  {{
                    repeat.item.standby_power
                    | default(am_default_standby_power, true)
                  }}

                am_operating_power_min: >-
                  {{
                    repeat.item.operating_power_min
                    | default(am_default_operating_power_min, true)
                  }}

                am_min_cycle_notify_seconds: >-
                  {{
                    repeat.item.min_cycle_notify_seconds
                    | default(am_default_min_cycle_notify_seconds, true)
                  }}

                am_power_w: >-
                  {{ states(repeat.item.power_entity) | float(0) }}

                am_power_available: >-
                  {{ states(repeat.item.power_entity) not in ['unknown', 'unavailable'] }}

            - action: script.appliance_monitor_process_sample
              data:
                appliance_id: "{{ repeat.item.appliance_id }}"
                appliance_name: "{{ repeat.item.appliance_name }}"
                appliance_action: "{{ repeat.item.appliance_action }}"

                power_w: "{{ am_power_w }}"
                power_available: "{{ am_power_available }}"
                monitor_sensor: "{{ am_monitor_sensor }}"
                state_topic: "{{ am_state_topic }}"

                debug_flow: "{{ am_debug_flow }}"
                resolution: "{{ am_resolution }}"
                standby_power: "{{ am_standby_power }}"
                operating_power_min: "{{ am_operating_power_min }}"
                min_cycle_notify_seconds: "{{ am_min_cycle_notify_seconds }}"

                announcement_enabled: "{{ am_announcement_enabled }}"
                announcement_channels: "{{ am_announcement_channels }}"
                title: "{{ am_title }}"
                short_announcement: "{{ am_short_announcement }}"
                long_announcement: "{{ am_long_announcement }}"
                announcement_payload: "{{ am_announcement_payload }}"
                matrix_indicator: "{{ am_matrix_indicator }}"
                mqtt_feed: "{{ am_mqtt_feed }}"
