Z2M - Philips Hue Tap Dial - Custom Controls

This blueprint is evolved from @gTunes blueprint. It has since been altered by others both to keep it working and for full customization instead of binding three buttons to media controls. The only thing I’ve done is to update the template syntax to the new syntax and translated some comments to english.

The blueprint supports controlling a media device and additional custom automations with a Philips Hue Tap Dial control. It is specific to Z2M.

Features of this blueprint:

  • Simple selection of a single media device to be controlled

  • User-specifiable volume steps - if your physical device uses a volume range other than 1-100, you can specify that range and this automation will scale incremental steps appropriately

  • The controller generates three rotation speed events : step, slow, and fast. This blueprint allows you to specify how many volume increments should be applied for each speed. You may, for example, set “step” to 1, slow to “3”, and fast to “5”. Because some volume ranges can result in rounding or truncation issues downstream of the automation, the blueprint allows these steps to be set as decimal values such as “1.5”

  • Both Short press and long press of all 4 buttons are configurable. Do whatever you like with them.

Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.

blueprint:
  # Media control via Philips Hue Tap Dial - FULL CUSTOM BUTTONS
  #
  # Works only with Home Assistant and Zigbee2MQTT
  #
  # Version History:
   #   - v 0.1 dec 2023 : Initial version from Matt Rusiniak
   #   - v 0.2 nov 2025 : full custom buttons (short & long)
   #   - v 0.3 jan 2026 : translations, plus added automation domain
   #   - v 0.4 jan 2026 : updated trigger to new syntax
  # Adapted form earlier work by:
  #      - https://gist.github.com/f0ff886f/58642a648e2e50a32c7d028f40e9cd6e Matt Rusiniak
  #
  # Based on earlier work by:
  #      - https://community.home-assistant.io/t/z2m-ikea-symfonisk-gen2-e2123-media-control-v1-53/559523 Shawsky
  #      - https://gist.github.com/erkr/a437ebcb98a2b5ba2deebabd02f5ffae Eric Kreuwels
  #      - https://gist.github.com/alexwmaustin/2c25cfa1a0ade1ab9fc1ef0940289672 Alex Austin
  #      - https://gist.github.com/LempK/0aee4ad2b3b5646665d0dc987e354dab 

  name: Philips Hue Tap Dial Media Control (Full Custom Buttons) - Z2M
  description:
    Control a media_player with the Hue Tap Dial wheel via Zigbee2MQTT
    (volume with the dial) and define fully customized actions
    for the 4 buttons, in short and long press.
  domain: automation
  input:
    remote:
      name: Remote
      description: The Philips Hue Tap Dial to use
      selector:
        device:
          filter:
            integration: mqtt
            manufacturer: Philips
            model: Hue Tap dial switch
          multiple: false

    base_topic:
      name: Zigbee2MQTT Base mqtt topic
      description: The base topic configured in Zigbee2MQTT. If you haven't changed this, leave the default here ("zigbee2mqtt")
      default: zigbee2mqtt

    media_player:
      name: Media Player
      description: The media player to control with this automation
      selector:
        entity:
          domain: media_player
          multiple: false

    volume_steps:
      name: Volume number of steps
      description: Controls the volume scale. Set this to the number of steps supported by your playback device
      default: 100
      selector:
        number:
          min: 5
          max: 100
          step: 1
          unit_of_measurement: "Num"
          mode: slider

    step_rotation_increments:
      name: '"Step" rotation volume increments'
      description: Number of volume increments for a "step" rotation event
      default: 1.0
      selector:
        number:
          min: 0.1
          max: 5.0
          step: 0.1
          unit_of_measurement: "Num"
          mode: slider

    slow_rotation_increments:
      name: '"Slow" rotation volume increments'
      description: Number of volume increments for a "slow" rotation event
      default: 2
      selector:
        number:
          min: 0.1
          max: 5.0
          step: 0.1
          unit_of_measurement: "Num"
          mode: slider

    fast_rotation_increments:
      name: '"Fast" rotation volume increments'
      description: Number of volume increments for a "fast" rotation event
      default: 3
      selector:
        number:
          min: 0.1
          max: 5.0
          step: 0.1
          unit_of_measurement: "Num"
          mode: slider

    button_1_short:
      name: Button 1 (Short)
      description: Action to run on a short press of Button 1
      default: []
      selector:
        action: {}

    button_1_hold:
      name: Button 1 (Long)
      description: Action to run on a long press of Button 1
      default: []
      selector:
        action: {}

    button_2_short:
      name: Button 2 (Short)
      description: Action to run on a short press of Button 2
      default: []
      selector:
        action: {}

    button_2_hold:
      name: Button 2 (Long)
      description: Action to run on a long press of Button 2
      default: []
      selector:
        action: {}

    button_3_short:
      name: Button 3 (Short)
      description: Action to run on a short press of Button 3
      default: []
      selector:
        action: {}

    button_3_hold:
      name: Button 3 (Long)
      description: Action to run on a long press of Button 3
      default: []
      selector:
        action: {}

    button_4_short:
      name: Button 4 (Short)
      description: Action to run on a short press of Button 4
      default: []
      selector:
        action: {}

    button_4_hold:
      name: Button 4 (Long)
      description: Action to run on a long press of Button 4
      default: []
      selector:
        action: {}

mode: restart
max_exceeded: silent

trigger_variables:
  base_topic: !input base_topic
  controller: !input remote

triggers:
  - trigger: mqtt
    topic: "{{ base_topic }}/+/action"

action:
  - variables:
      controllertopic: "{{ base_topic }}/{{ device_attr(controller, 'name') }}/action"
      player: !input media_player
      steps: !input volume_steps
      stepsize: "{{ 1.0 / steps }}"
      stepmultiplier: {
          "dial_rotate_right_step": !input step_rotation_increments,
          "dial_rotate_left_step": !input step_rotation_increments,
          "dial_rotate_right_slow": !input slow_rotation_increments,
          "dial_rotate_left_slow": !input slow_rotation_increments,
          "dial_rotate_right_fast": !input fast_rotation_increments,
          "dial_rotate_left_fast": !input fast_rotation_increments
      }

  - choose:
      - conditions:
          - "{{ trigger.payload != '' }}"
          - "{{ trigger.topic == controllertopic }}"
        sequence:
          - choose:
              # ----- VOLUME (Wheel) -----
              # Rotate Right (Volume Up)
              - conditions: "{{ trigger.payload in ['dial_rotate_right_step', 'dial_rotate_right_slow', 'dial_rotate_right_fast'] }}"
                sequence:
                  - service: media_player.volume_set
                    target:
                      entity_id: "{{ player }}"
                    data:
                      volume_level: >-
                        {% set volume = state_attr(player, "volume_level") + (stepsize * stepmultiplier[trigger.payload]) %}
                        {{ 1.0 if volume > 1.0 else volume }}
              # Rotate Left (Volume Down)
              - conditions: "{{ trigger.payload in ['dial_rotate_left_step', 'dial_rotate_left_slow', 'dial_rotate_left_fast'] }}"
                sequence:
                  - service: media_player.volume_set
                    target:
                      entity_id: "{{ player }}"
                    data:
                      volume_level: >-
                        {% set volume = state_attr(player, "volume_level") - (stepsize * stepmultiplier[trigger.payload]) %}
                        {{ 0.0 if volume < 0.0 else volume }}
              # ----- BUTTTONS : SHORT PRESS -----
              - conditions: "{{ trigger.payload == 'button_1_press_release' }}"
                sequence: !input button_1_short

              - conditions: "{{ trigger.payload == 'button_2_press_release' }}"
                sequence: !input button_2_short

              - conditions: "{{ trigger.payload == 'button_3_press_release' }}"
                sequence: !input button_3_short

              - conditions: "{{ trigger.payload == 'button_4_press_release' }}"
                sequence: !input button_4_short

              # ----- BUTTONS : LONG PRESS -----
              - conditions: "{{ trigger.payload == 'button_1_hold' }}"
                sequence: !input button_1_hold

              - conditions: "{{ trigger.payload == 'button_2_hold' }}"
                sequence: !input button_2_hold

              - conditions: "{{ trigger.payload == 'button_3_hold' }}"
                sequence: !input button_3_hold

              - conditions: "{{ trigger.payload == 'button_4_hold' }}"
                sequence: !input button_4_hold

Setup:

Select your device, adjust your MQTT base topic if necessary, the choose the media_player to control.

Choose volume steps, increments, etc. and configure the custom sequences as desired.

Thanks to the creators of previous blueprints from which this is derived. They’re credited in the blueprint.