⚽ My World Cup Match Day automation with live goal announcements!⚽

With the World Cup in full swing, I felt motivated to implement a major upgrade to my match-day automation setup. I wanted something more dynamic than just lighting changes, so I’ve now added live audio announcements for goals to the mix!

To pull this off, I’m leveraging the Aqara Hub M3 alongside my existing Home Assistant configuration. Here is how I got it working:

  1. Custom Sound Effects: I downloaded a great cheering crowd sound effect from Pixabay (specifically this one) and uploaded it directly to the Aqara M3 Hub.
  2. Matter Integration: I created a scene within the Aqara app to play that specific cheering sound, and then exposed that scene via Matter directly into Home Assistant.
  3. API Upgrade: I updated my API to expose live goal events as they happen.

If you want to replicate this setup or hook into the live data, you can scrape my API using the popular Multiscrape Home Assistant integration.

1. Multiscrape Configuration

Add this to your configuration (adjusting for your specific entity names):

multiscrape:
  - resource: "https://wc.markus.workers.dev/"
    scan_interval: 60
    sensor:
      - name: "FIFA World Cup 2026"
        unique_id: "fifa_world_cup_2026"
        select: "h1"
        attributes:
          - name: Team 1
            select: "h1"
            value_template: >
              {% set raw_teams = value.split(' (')[0] if ' (' in value else value %}
              {{ raw_teams.split(' vs. ')[0].strip() if ' vs. ' in raw_teams else 'Unknown' }}
          - name: Team 2
            select: "h1"
            value_template: >
              {% set raw_teams = value.split(' (')[0] if ' (' in value else value %}
              {{ raw_teams.split(' vs. ')[1].strip() if ' vs. ' in raw_teams else 'Unknown' }}
          - name: Ongoing
            select: "body"
            extract: content
            value_template: "{{ '<p>' in value }}"
            on_error:
              value: none
          - name: Score
            select: "p"
            value_template: "{{ value if ':' in value else None }}"
            on_error:
              value: none
          - name: Goals Team 1
            select: "p"
            value_template: "{{ value.split(':')[0] if ':' in value else None }}"
            on_error:
              value: none
          - name: Goals Team 2
            select: "p"
            value_template: "{{ value.split(':')[1] if ':' in value else None }}"
            on_error:
              value: none

2. Home Assistant Automation

Then, use this automation code to trigger the Aqara M3 scene whenever a goal is detected by the scraper:

alias: FIFA World Cup Lights
description: >-
  Controls living room lights and announcements based on World Cup match states
  and scores.
triggers:
  - minutes: "0"
    trigger: time_pattern
    id: time_check
  - minutes: "30"
    trigger: time_pattern
    id: time_check
  - trigger: state
    entity_id: sensor.fifa_world_cup_2026
    attribute: goals_team_1
    id: goal_team1
  - trigger: state
    entity_id: sensor.fifa_world_cup_2026
    attribute: goals_team_2
    id: goal_team2
conditions:
  - condition: template
    value_template: >-
      {{ states('sensor.fifa_world_cup_2026') not in ['unknown', 'unavailable',
      ''] 
         and 'vs.' in states('sensor.fifa_world_cup_2026') }}
actions:
  - variables:
      match_text: "{{ states('sensor.fifa_world_cup_2026') }}"
      teams_part: "{{ match_text.split(' (')[0] }}"
      team1_name: "{{ state_attr('sensor.fifa_world_cup_2026', 'team_1') }}"
      team2_name: "{{ state_attr('sensor.fifa_world_cup_2026', 'team_2') }}"
      score: "{{ state_attr('sensor.fifa_world_cup_2026', 'score') }}"
      goals_t1: "{{ state_attr('sensor.fifa_world_cup_2026', 'goals_team_1') | int(0) }}"
      goals_t2: "{{ state_attr('sensor.fifa_world_cup_2026', 'goals_team_2') | int(0) }}"
      leading_team: >-
        {% if goals_t1 > goals_t2 %}{{ team1_name }} {% elif goals_t2 > goals_t1
        %}{{ team2_name }} {% else %}Tie{% endif %}
  - alias: If team scored goal
    if:
      - condition: template
        value_template: >-
          {{ trigger.id in ['goal_team1', 'goal_team2'] and trigger.from_state
          is not none }}
    then:
      - variables:
          scoring_team: "{{ team1_name if trigger.id == 'goal_team1' else team2_name }}"
          other_team: "{{ team2_name if trigger.id == 'goal_team1' else team1_name }}"
          target_light: >-
            {{ 'light.wohnzimmerlampe' if trigger.id == 'goal_team1' else
            'light.aqara_led_strip_t2' }}
          initial_state: "{{ states(target_light) }}"
          initial_bright: "{{ state_attr(target_light, 'brightness') }}"
          initial_rgb: "{{ state_attr(target_light, 'rgb_color') }}"
      - parallel:
          - if:
              - condition: template
                value_template: "{{ goals_t1 >= 1 or goals_t2 >= 1 }}"
            then:
              - action: switch.turn_on
                metadata: {}
                target:
                  entity_id: switch.play_crowd_cheers_sound
                data: {}
            alias: Play crowd cheers
          - if:
              - condition: template
                value_template: "{{ goals_t1 >= 1 or goals_t2 >= 1 }}"
            then:
              - action: script.announcement_on_maxi
                data:
                  message: >-
                    Gooooaaaaal! {{ scoring_team }} scored.  {% if leading_team
                    == 'Tie' %}
                      It now stands {{ score }} between both teams.
                    {% else %}
                      It now stands {{ score }} for {{ leading_team }} against {{ other_team }}.
                    {% endif %}
                  volume: 0.5
            alias: Goal announcement
          - alias: Animate light
            sequence:
              - action: ai_task.generate_data
                data:
                  entity_id: ai_task.gemini_flash_latest_version
                  task_name: Get flag colors
                  instructions: >-
                    Provide a JSON list of the dominant distinct colors found in
                    the national flag of {{ scoring_team }}.  Provide at least 2
                    or 3 colors. Return ONLY valid JSON in this exact format: {
                    "colors": ["#HEX1", "#HEX2", "#HEX3"] }
                response_variable: flag_colors_response
              - variables:
                  flag_colors: "{{ (flag_colors_response.data | from_json).colors }}"
              - repeat:
                  for_each: "{{ flag_colors * 5 }}"
                  sequence:
                    - action: light.turn_on
                      target:
                        entity_id: "{{ target_light }}"
                      data:
                        brightness_pct: 100
                        rgb_color:
                          - "{{ repeat.item[1:3] | int(base=16) }}"
                          - "{{ repeat.item[3:5] | int(base=16) }}"
                          - "{{ repeat.item[5:7] | int(base=16) }}"
                    - delay:
                        hours: 0
                        minutes: 0
                        seconds: 0
                        milliseconds: 600
              - choose:
                  - conditions:
                      - condition: template
                        value_template: "{{ initial_state == 'on' }}"
                    sequence:
                      - action: light.turn_on
                        target:
                          entity_id: "{{ target_light }}"
                        data:
                          brightness: "{{ initial_bright | int(255) }}"
                          rgb_color: >-
                            {{ initial_rgb if initial_rgb is not none else [255,
                            255, 255] }}
                default:
                  - action: light.turn_off
                    target:
                      entity_id: "{{ target_light }}"
                    data: {}
  - if:
      - condition: template
        value_template: "{{ trigger.id == 'time_check' }}"
    then:
      - variables:
          timestamp_str: "{{ match_text.split('(')[1].split(')')[0] }}"
          time_diff: "{{ (as_datetime(timestamp_str) - now()).total_seconds() / 60 }}"
          is_live: "{{ time_diff <= 0 and time_diff >= -125 }}"
          starting_soon: "{{ time_diff > 0 and time_diff <= 60 }}"
          is_exactly_starting: "{{ time_diff >= -15 and time_diff <= 15 }}"
      - condition: template
        value_template: "{{ is_exactly_starting or starting_soon }}"
      - action: ai_task.generate_data
        data:
          entity_id: ai_task.gemini_flash_latest_version
          task_name: Get light colors
          instructions: >-
            There's a FIFA world cup 2026 football match between {{ team1_name
            }} and {{ team2_name }}.  I have two RGB lamps which I want to
            represent those two teams. Choose the two colors  that best
            represent these countries but make sure they are different enough to
            be able  to differentiate them. Return the result as valid JSON in
            the following form and nothing else: {
              "team1": { "name": "{{ team1_name }}", "color": "#<hex color code>" },
              "team2": { "name": "{{ team2_name }}", "color": "#<hex color code>" }
            }
        response_variable: gemini_response
      - variables:
          colors: "{{ gemini_response.data | from_json }}"
          hex_team1: "{{ colors.team1.color }}"
          hex_team2: "{{ colors.team2.color }}"
          brightness_pct: "{{ 100 if is_live else 50 }}"
      - action: light.turn_on
        target:
          entity_id: light.wohnzimmerlampe
        data:
          brightness_pct: "{{ brightness_pct | int }}"
          rgb_color:
            - "{{ hex_team1[1:3] | int(base=16) }}"
            - "{{ hex_team1[3:5] | int(base=16) }}"
            - "{{ hex_team1[5:7] | int(base=16) }}"
      - action: light.turn_on
        target:
          entity_id: light.aqara_led_strip_t2
        data:
          brightness_pct: "{{ brightness_pct | int }}"
          rgb_color:
            - "{{ hex_team2[1:3] | int(base=16) }}"
            - "{{ hex_team2[3:5] | int(base=16) }}"
            - "{{ hex_team2[5:7] | int(base=16) }}"
      - if:
          - condition: template
            value_template: "{{ is_exactly_starting }}"
        then:
          - action: script.announcement_on_maxi
            data:
              message: Match {{ team1_name }} vs. {{ team2_name }} begins
              volume: 0.5
    alias: If mood lights trigger
mode: single

It’s quite amazing hearing the room erupt into a stadium cheer shortly after a goal is scored. It completely changes the match-day experience. Let me know if you have any questions or need help tweaking the configs for your own setup!

2 Likes