Creating Automations with YAML (Aqara magic dice)

Last week I received Aqara magic dice in post and have been experimenting a bit with it.

It was tad harder to setup than I expected (for some reason did not see ‘side’ property in condition parameters) and as such I wish to share here my automation and also inquire as ‘how it could be improved’ by those who understand HA automation templates / platforms better than I do.

As I did not understand too much of different ways of how to go about doing this I ended up with using ‘mqtt’ platform which seems to offer rather low level access/reactions to something zigbee2mqtt is providing to Home Assistant.

After digging around docs/this community and testing and trying to understand logs I finally got conditionals working after bit (or a lot) of experimenting.

alias: dice controls
description: ""
trigger:
  - platform: mqtt
    topic: zigbee2mqtt/mydice1
condition:
  - condition: template
    value_template: |-
      {{ 
      trigger.payload_json.action == 'slide' 
      or trigger.payload_json.action == 'tap' 
      or trigger.payload_json.action == 'rotate_left' 
      or trigger.payload_json.action == 'rotate_right'
      }}
action:
  - service_template: |-
      {% if trigger.payload_json.action == 'tap' 
         or trigger.payload_json.action == 'rotate_left' 
         or trigger.payload_json.action == 'rotate_right'
      %}  light.turn_on
      {% else %} script.dummy
      {% endif %}
    data_template:
      entity_id: |-
        {% if trigger.payload_json.side == 5 %} light.room1  
        {% elif trigger.payload_json.side == 4 %} light.tradfri_bulb_22
        {% elif trigger.payload_json.side == 3 %} light.room3  
        {% elif trigger.payload_json.side == 2 %} light.room2
        {% elif trigger.payload_json.side == 0 %} light.room4
        {% endif %}
      brightness_step_pct: |-
        {% if trigger.payload_json.action == 'rotate_left' %} -10
        {% elif trigger.payload_json.action == 'rotate_right' %} 10
        {% else %} 50
        {% endif %}
  - service_template: |-
      {% if trigger.payload_json.action == 'slide' %} light.turn_off
      {% else %} script.dummy
      {% endif %}
    data_template:
      entity_id: |-
        {% if trigger.payload_json.side == 5 %} light.room1
        {% elif trigger.payload_json.side == 4 %} light.tradfri_bulb_22
        {% elif trigger.payload_json.side == 3 %} light.room3
        {% elif trigger.payload_json.side == 2 %} light.room2
        {% elif trigger.payload_json.side == 0 %} light.room4
        {% endif %}
mode: single

This automation works for me as is, but from a coders point of view is tad ugly so I was wondering “If you’d actually understand how Home Assistant works how’d you go about achieving something similar?”

I think I saw somewhere a way to set ‘variable’ in trigger phase so perhaps I could assign ‘entity_id’ in one centralized location and just use it later.

Also I had to split turn_on and turn_off to 2 different locations as brightness parameters seem to break calling ‘turn_off’ service (I got ““extra keys not allowed @ data[‘brightness_step_pct’]”” if I tried to turn_off entities within same service template.

Saw ‘script.dummy’ solution somewhere in this community and used that (with small mod of using parallel)

alias: dummy
sequence:
  - delay:
      milliseconds: 1
mode: parallel
max: 50

Also as related note.

Have ppl been experiencing problems with the dice?

I was tossing mine up and down a bit (to get ‘falling’ or like action) and managed to “freeze” the cube. It nolonger sent anything into zigbee2mqtt (and as such nothing come to HA)

after removing the battery and putting it back it started to work again.

Just a suggestion, when using the community forum for research, try to avoid employing examples that are more than a year old (maybe even just six months). Whatever ancient resource you used has led to composing an automation containing syntax that was deprecated well over a year ago and archaic techniques (like script.dummy) that are no longer needed.

Here’s an example using currently available scripting techniques.

alias: dice controls
description: ""
trigger:
  - platform: mqtt
    topic: zigbee2mqtt/mydice1
condition:
  - "{{ trigger.payload_json.action in ['slide', 'tap', 'rotate_left', 'rotate_right'] }}"
action:
  - variables:
      lights:
        5: light.room1  
        4: light.tradfri_bulb_22
        3: light.room3  
        2: light.room2
        0: light.room4
      light: "{{ lights.get(trigger.payload_json.side, 0 }}"
      bp:
        rotate_left: -10
        rotate_right: 10
  - if: "{{ trigger.payload_json.action != 'slide' }}"
    then:
      - service: light.turn_on
        target:
          entity_id: "{{ light }}"
        data:
          brightness_step_pct: "{{ bp.get(trigger.payload_json.side, 50 }}"
    else:
      - service: light.turn_off
        target:
          entity_id: "{{ light }}"
mode: single
1 Like

Thank you for pointing me towards more up to date approach.
After small modifications I managed to get following to work

What came as surprise was

  • dictionaries specified in YAML end up having ‘str’ datatype as key
  • side from payload_json.side was ‘integer’ → cast to ‘string’ needed to be done
alias: dice controls
description: ""
trigger:
  - platform: mqtt
    topic: zigbee2mqtt/mydice1
condition:
  - condition: template
    value_template: |-
      {{ 
      trigger.payload_json.action in [
        'slide', 'tap', 'fall', 'shake', 'rotate_left', 'rotate_right'
        ]
      }}
action:
  - variables:
      lights:
        "0": light.room0
        "1": light.room1
        "2": light.room2
        "3": light.room3
        "4": light.tradfri_bulb_22
        "5": light.room5
      all_lights: "{{ lights.values() | list }}"
      bp:
        rotate_left: -10
        rotate_right: 25
      light: "{{ lights.get( (trigger.payload_json.side) | string) }}"
  - choose:
      - conditions: |-
          {{ trigger.payload_json.action in 
             ['tap', 'rotate_left', 'rotate_right'] }}
        sequence:
          service: light.turn_on
          data_template:
            entity_id: "{{ light }}"
            brightness_step_pct: "{{ bp.get(trigger.payload_json.action, 50) }}"
      - conditions: "{{ trigger.payload_json.action == 'slide' }}"
        sequence:
          service: light.turn_off
          target:
            entity_id: "{{ light }}"
      - conditions: "{{ trigger.payload_json.action == 'shake' }}"
        sequence:
          service: light.turn_on
          data:
            brightness_pct: 100
          target:
            entity_id: "{{ all_lights }}"
      - conditions: "{{ trigger.payload_json.action == 'fall' }}"
        sequence:
          service: light.turn_off
          target:
            entity_id: "{{ all_lights }}"
mode: single

You’re welcome but your 'small modifications ’ introduced unnecessary elements as well as deprecated conventions. If appears it’s much more challenging to forego the practices you learned from archaic examples.

  1. A YAML dictionary’s keys can be any of several types including integers. If you used the Automation Editor to compose the automation, one of its annoying quirks is to wrap integer keys to make them strings. It’s not due to a YAML limitation.

  2. Be aware that Home Assistant employs native typing which means it infers a template value’s type based on its appearance. For example, if it looks like a number, the type of the template’s result will be a number (int or float). In this case, native typing wasn’t involved (because the string filter was applied to the first argument of the get method) but it’s something you should keep in mind.

Do you want me to revise your second example or are you already creating an enhanced version of it?

Thank you for the response.

I was surprised at the int->str change as I’ve used yaml dicts with integers in python before.
I did not realize it was the editor that changed the dict keys into str should’ve looked at automations.yaml more carefully.
Casting of the value would’ve been needless if I would’ve realized to edit .yaml file instead of using the editor.

If you are willing to give me more examples of best practices I am keen to learn and possibly it could serve as convention compass for someone else as well.
As far functionality goes I think cube now does more or less all the things I could come up with it.
(whenever it actually senses stuff and sends it and does not lock up)

I noticed that there were some samples which used the cube in a way it had ‘states’ one for light control and other for AV control and mode could’ve been switched by shaking or dropping the cube.
Stateful cube would add more use possibilities.
Would such functionality still be implemented with a ‘global’ cube state which would be updated by different automations and then that state would be used in the Contition clause in the beginning of the automation?

If that is recommended path I’ll give it a go in next stage of tweakings :slight_smile:

Here’s a revised version of the automation. The lights variable is now simply a list instead of dict (thereby avoiding the complication caused by the Automation Editor’s quirk).

alias: dice controls
description: ""
trigger:
  - platform: mqtt
    topic: zigbee2mqtt/mydice1
condition:
  - "{{ action in actions }}"
action:
  - choose:
      - conditions: "{{ action in ['slide', 'fall'] }}"
        sequence:
          - service: light.turn_off
            target:
              entity_id: "{{ light if action == 'slide' else lights }}"
      - conditions: "{{ action == 'shake' }}"
        sequence:
          - service: light.turn_on
            target:
              entity_id: "{{ lights }}"
            data:
              brightness_pct: 100
    default:
      - service: light.turn_on
        target:
          entity_id: "{{ light }}"
        data:
          brightness_step_pct: "{{ bp.get(action, 50) }}"
variables:
  actions:
    - slide
    - tap
    - fall
    - shake
    - rotate_left
    - rotate_right
  action: "{{ trigger.payload_json.action }}"
  lights:
    - light.room0
    - light.room1
    - light.room2
    - light.room3
    - light.tradfri_bulb_22
    - light.room5
  light: "{{ lights[trigger.payload_json.side] | default(lights[0]) }}"
  bp:
    rotate_left: -10
    rotate_right: 25
mode: single

NOTE

If you’re interested, there’s an existing Blueprint for handling all of the cube’s actions:

2 Likes

I also have a version of this for the new version of the cube, the PRO which has different abilities and a version for ZHA the same.

Thank you.

Some useful patterns/learning here for me were

  • use variables for mapings and ‘payload_json’ renaming to make YAML more readable.
  • lose all _template (as was mentioned in one of your posts)
  • sometimes might have to use ‘| default’ ‘|list’ , … but most often it seems that those can be avoided.
  • full python inside “{{ }}”
  • using the ‘|-’ of YAML multiline can make some conditions more readable.

Other things

  • I did not seem to get “action” to work in the top ‘condition’ part.
    Are the variables only visible for ‘actions’ branch?

You’re welcome!

Please consider marking my post above with the Solution tag. It will automatically place a check-mark next to the topic’s title which signals to other users that this topic has been resolved. This helps users find answers to similar questions.

For more information about the Solution tag, refer to guideline 21 in the FAQ.

1 Like