# LED Strip Media Progress Bar ( Home Assistant / Apple TV / WLED)

Recently I installed an WLED strip on my media cabinet which sits below my TV. Initially, I set up a basic automation to power the LED strip on/off following the power state of my Apple TV. One day as I was pondering my next unnecessary and over-engineered automation project, I thought:

**What if I could use the LED strip as a media progress indicator bar for content playing on my Apple TV?**

After some quick research, it seems like other users have used WLED as a progress bar for other use cases, but nothing perfectly fit my goal.

## Goals & Limitations

* The LED strip should update at a reasonable rate matching up with the content being played on my Apple TV.
    
* A known limitation of the Home Assistant Apple TV integration is that the polling/update frequency is not that good.
    
    * Our automation will need to calculate a precise progress value, assuming we may not always get timely/reliable updates from our Apple TV attributes.
        
* It seems that the Apple TV attributes in Home Assistant do not reflect application state, they only update when media is started/stopped ([see this post](https://community.home-assistant.io/t/feature-request-apple-tv-app-id-attribute-update-on-select/508712)).
    
    * If I exit a movie for example, the entity reflects a “paused” state and the attributes still reflect the movie that was exited. This does not change until new media is played.
        
    * Since I don’t want to stare at the progress bar when nothing is playing or after I exit content, I will treat the “paused” state (with a slight delay) as a trigger to go back to my default color.
        

<div data-node-type="callout">
<div data-node-type="callout-emoji">⚠</div>
<div data-node-type="callout-text">Full disclaimer, I make no claims of being an expert at writing code or automations. As such, my talented French colleague by the name of Claude will be lending a heavy hand on this project. With that said, please feel free to tell Claude how you feel about their code.</div>
</div>

## Components

1. **Apple TV Integration**: Built-in Home Assistant integration that monitors Apple TV state.
    
    1. The Apple TV integration provides several states that the automations monitor:
        
        * `playing`: Media is currently playing
            
        * `paused`: Media is paused
            
        * `idle`: Apple TV is on but not playing media
            
        * `standby`: Apple TV is in sleep mode
            
    2. Additionally, the integration provides media attributes that we will use to calculate the current percentage of the progress bar:
        
        * `media_position`: Current position in the media (in seconds)
            
        * `media_duration`: Total duration of the media (in seconds)
            
        * `media_position_updated_at`: Timestamp when the position was last updated
            
2. **WLED REST API**: Custom REST API endpoint for direct JSON control of WLED.
    

## WLED API

After a little bit of research, I decided that using the [WLED JSON API](https://kno.wled.ge/interfaces/json-api/) would be the best approach for this project. This allows us to send a POST call along with a JSON body containing the instructions for the LED strip.

For example, my LED strip contains a total of 108 addressable LEDs. If I send this very basic POST request with the following JSON body, my entire LED strip turns orange. To test this you can use an API client like Postman, Scalar, etc.

```json
{
  "seg": {
    "i": [0, 108, "FFA500"]
  }
}
```

Here are some additional examples of the JSON syntax:

1. **Single Segment Control** (used in the idle state):
    
    ```json
    {
      "on": true,
      "bri": 255,
      "seg": {
        "i": [0, 108, "FF8C00"]
      }
    }
    ```
    
2. **Multi Segment Control** (used in the progress bar state):
    
    ```json
    {
      "on": true,
      "bri": 255,
      "seg": {
        "i": [0, 42, "00FFFF", 42, 108, "FF0080"]
      }
    }
    ```
    

The `"i"` array in the WLED JSON format represents segments with their start position, end position, and color. The syntax is: `[start_position1, end_position1, "color1", start_position2, end_position2, "color2", ...]`

## Home Assistant

### Configuration.yaml

To apply this API control approach in Home Assistant, we added the following to our `configuration.yaml` file to define a REST command that will directly send JSON data to the WLED controller:

```yaml
rest_command:
  wled_progress:
    url: http://123.456.789.0/json
    method: POST
    content_type: "application/json"
    payload: "{{ progress }}"
```

This command is used by both automations to control the WLED strip in different ways. As you can see, the payload has a placeholder variable "{{ progress }}". This will allow us to leverage the same rest\_command in multiple different automations, as we can dynamically construct the JSON payload within our automation code.

### Automations.yaml

#### 1\. Apple TV Power and Paused State

This consolidated automation handles both power on/off scenario and the paused scenario based on Apple TV state. I have chose the color orange (`"FF8C00"`) as my idle color but you can change this.

```yaml
- id: 'XXXXXXXXXXXXXXXX'
  alias: Apple TV LED Power and Color Control
  description: Controls LED strip power and color based on Apple TV state
  triggers:
  - entity_id: media_player.apple_tv
    trigger: state
  - event: start
    trigger: homeassistant
  actions:
  - variables:
      apple_tv_state: '{{ states(''media_player.apple_tv'') }}'
  - choose:
    # When Apple TV is playing, we don't need to do anything as the progress bar automation handles it
    - conditions:
      - condition: state
        entity_id: media_player.apple_tv
        state: playing
      sequence:
      - delay:
          milliseconds: 100
    # When Apple TV is paused or idle, turn on LED and set to orange
    - conditions:
      - condition: or
        conditions:
        - condition: state
          entity_id: media_player.apple_tv
          state: paused
        - condition: state
          entity_id: media_player.apple_tv
          state: idle
      sequence:
      - target:
          entity_id: light.media_cabinet_led_strip
        data: {}
        action: light.turn_on
      # Wait before applying orange color
      - delay:
          seconds: 3
      - data:
          progress: "{% set json_data = {\n  \"on\": true,\n  \"bri\": 255,\n  \"seg\": {\n    \"i\": [0, 108, \"FF8C00\"]\n  }\n} %} {{ json_data|tojson }}\n"
        action: rest_command.wled_progress
    # When Apple TV is off or standby, turn off the LED strip
    - conditions:
      - condition: or
        conditions:
        - condition: state
          entity_id: media_player.apple_tv
          state: standby
        - condition: state
          entity_id: media_player.apple_tv
          state: 'off'
      sequence:
      - target:
          entity_id: light.media_cabinet_led_strip
        data: {}
        action: light.turn_off
      # Also send command to ensure WLED is completely off
      - data:
          progress: "{% set json_data = {\n  \"on\": false,\n  \"seg\": {\n    \"i\": [0, 0, \"000000\", 0, 108, \"000000\"]\n  }\n} %} {{ json_data|tojson }}\n"
        action: rest_command.wled_progress
  mode: restart
  max_exceeded: silent
```

**Key aspects:**

* Consolidates both power control and orange color setting into a single automation
    
    * Anywhere the orange color is mentioned (`"FF8C00"`) can be customized to a HEX color of your choosing
        
* Uses a state-based approach with three conditions:
    
    * When playing: Does nothing (lets the progress bar automation handle it)
        
    * When paused/idle: Turns on LED and sets to orange after a 3-second delay
        
    * When off/standby: Turns off the LED strip completely
        
* Uses the restart mode to ensure only one instance runs at a time
    
* Includes the HomeAssistant start trigger to set the correct state on system restart
    

#### 2\. Apple TV Progress Bar When Playing

This automation creates a progress bar effect on the WLED strip while media is playing on the Apple TV:

```yaml
- id: 'XXXXXXXXXXXXXXXXXXXX'
  alias: Apple TV Progress Bar When Playing
  description: Shows progress bar on WLED when Apple TV is playing
  trigger:
    # Trigger to state
  - platform: state
    entity_id: media_player.apple_tv
    to: 'playing'
    # Trigger for media_position attribute state
  - platform: state
    entity_id: media_player.apple_tv
    attribute: media_position
    # Trigger every 1 second to ensure timely updates to LED strip
  - platform: time_pattern
    seconds: '/1'
  condition:
    # Ensure Apple TV is in a playing state
  - condition: state
    entity_id: media_player.apple_tv
    state: 'playing'
  - condition: template
    value_template: '{{ state_attr("media_player.apple_tv", "media_duration") is not none and state_attr("media_player.apple_tv", "media_duration") > 0 }}'
  action:
    # Format our JSON payload to send to our rest command
  - service: rest_command.wled_progress
    data:
      progress: >
        {% set last_position = state_attr('media_player.apple_tv', 'media_position')|float(0) %}
        {% set last_updated = state_attr('media_player.apple_tv', 'media_position_updated_at') %}
        {% set duration = state_attr('media_player.apple_tv', 'media_duration')|float(1) %}
        {% set position = last_position %}
        {% if last_updated is not none %}
          {% set time_diff = (now().timestamp() - as_timestamp(last_updated))|float(0) %}
          {% set position = last_position + time_diff %}
          {% if position > duration %}
            {% set position = duration %}
          {% endif %}
        {% endif %}
        {% set progress_percent = position / duration if duration > 0 else 0 %}
        {% set led_count = 108 %}
        {% set progress_leds = (progress_percent * led_count)|int %}
        {% set json_data = {
          "on": true,
          "bri": 255,
          "seg": {
            "i": [0, progress_leds, "00FFFF", progress_leds, 108, "FF0080"]
          }
        } %}
        {{ json_data|tojson }}
  mode: restart
  max_exceeded: silent
```

**Key aspects:**

* Updates every second and on media position changes
    
* Calculates the current playback position, accounting for time elapsed since last update
    
* Calculates the percentage of playback completed and converts to LED positions
    
* Creates a two-color effect: cyan (`"00FFFF"`) for completed portion and pink (`"FF0080"`) for remaining portion
    
* Uses the WLED segment array notation to create a split-color effect
    

# Future Enhancement Ideas

Potential improvements to this setup:

1. Add color customization through Home Assistant input\_select entities
    
2. Implement brightness control based on time of day or ambient light sensors
    
3. Add additional visual effects for specific media types (movies, music, etc.)
    

# Closing

I hope you find this information useful! Please share your adaptations below and as always, feel free to ask any questions.
