ZHA - Aqara Magic Cube (57 actions)

Blueprint to automate the Aqara Magic Cube using ZHA.
Based on Mikkelmoller’s 51-action blueprint.

This blueprint adds support for flipping the cube to a specific side regardless of the side it was on before.

Supported actions

  • Slide
  • Knock
  • Flip 90 degress
  • Flip 180 degress
  • Flip to specific side
  • Shake
  • Drop
  • Wake
  • Rotate clockwise
  • Rotate counter-clockwise

Side layout diagram

image
(Thanks to idantene for the above!)

Changelog

6/14/2021

  • Added default values for value and flip_degrees
  • Removed relative_degrees variable. Sent with rotation data, and currently not used in the blueprint.

8/14/2021

  • Fixed side-specific knocks

Blueprint

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

blueprint:
  name: Aqara Magic Cube
  description: Control anything using Aqara Magic Cube.
  domain: automation
  input:
    remote:
      name: Magic Cube
      description: Select the Aqara Magic Cube device
      selector:
        device:
          integration: zha
          manufacturer: LUMI
    flip_90:
      name: Flip 90 degrees
      description: 'Actions to run when cube flips 90 degrees.

        This cancels all specific 90 degrees functions.

        e.g From side 1 to side 2 will be the same as from side 6 to side 2'
      default: false
      selector:
        boolean: {}
    cube_flip_90:
      name: Flip cube 90 degrees
      description: Action to run when cube flips 90 degrees. This only works if 'Flip
        90 degrees' is toggled
      default: []
      selector:
        action: {}
    flip_180:
      name: Flip 180 degrees
      description: 'Actions to run when cube flips 180 degrees.

        This cancels all specific 180 degrees functions

        e.g From side 1 to side 4 will be the same as from side 5 to side 2'
      default: false
      selector:
        boolean: {}
    cube_flip_180:
      name: Flip cube 180 degrees
      description: Action to run when cube flips 180 degrees. This only works if 'Flip
        180 degrees' is toggled
      default: []
      selector:
        action: {}
    flip_any:
      name: Flip to any side
      description: 'Actions to run when cube flips to any side.

        This cares about the end side, but cancels all specific flip functions.

        e.g From side 1 to side 2 will be the same as from side 6 to side 2

        but different than side 1 to side 5'
      default: false
      selector:
        boolean: {}
    cube_flip_1:
      name: Flip cube to side 1
      description: Action to run when cube flips to side 1. This only works if 'Flip
        any' is toggled
      default: []
      selector:
        action: {}
    cube_flip_2:
      name: Flip cube to side 2
      description: Action to run when cube flips to side 2. This only works if 'Flip
        any' is toggled
      default: []
      selector:
        action: {}
    cube_flip_3:
      name: Flip cube to side 3
      description: Action to run when cube flips to side 3. This only works if 'Flip
        any' is toggled
      default: []
      selector:
        action: {}
    cube_flip_4:
      name: Flip cube to side 4
      description: Action to run when cube flips to side 4. This only works if 'Flip
        any' is toggled
      default: []
      selector:
        action: {}
    cube_flip_5:
      name: Flip cube to side 5
      description: Action to run when cube flips to side 5. This only works if 'Flip
        any' is toggled
      default: []
      selector:
        action: {}
    cube_flip_6:
      name: Flip cube to side 6
      description: Action to run when cube flips to side 6. This only works if 'Flip
        any' is toggled
      default: []
      selector:
        action: {}
    slide_any_side:
      name: Slide any side
      description: 'Actions to run when cube slides on any side.

        This cancels all specific ''slide'' functions

        e.g Slide on side 1 will be the same as slide on side 2'
      default: false
      selector:
        boolean: {}
    cube_slide_any:
      name: Slide cube on any side
      description: Action to run when cube slides on any slide. This only works if
        'Slide any side' is toggled
      default: []
      selector:
        action: {}
    knock_any_side:
      name: Knock on any side
      description: 'Actions to run when knocking cube regardless of the side.

        This cancels all specific ''knock'' functions

        e.g Knock on side 1 will be the same as knocking side 2'
      default: false
      selector:
        boolean: {}
    cube_knock_any:
      name: Knock cube on any side
      description: Action to run when knocking cube on any side. This only works if
        'Knock on any side' is toggled
      default: []
      selector:
        action: {}
    one_to_two:
      name: From side 1 to side 2
      description: Action to run when cube goes from side 1 to side 2
      default: []
      selector:
        action: {}
    one_to_three:
      name: From side 1 to side 3
      description: Action to run when cube goes from side 1 to side 3
      default: []
      selector:
        action: {}
    one_to_four:
      name: From side 1 to side 4
      description: Action to run when cube goes from side 1 to side 4
      default: []
      selector:
        action: {}
    one_to_five:
      name: From side 1 to side 5
      description: Action to run when cube goes from side 1 to side 5
      default: []
      selector:
        action: {}
    one_to_six:
      name: From side 1 to side 6
      description: Action to run when cube goes from side 1 to side 6
      default: []
      selector:
        action: {}
    two_to_one:
      name: From side 2 to side 1
      description: Action to run when cube goes from side 2 to side 1
      default: []
      selector:
        action: {}
    two_to_three:
      name: From side 2 to side 3
      description: Action to run when cube goes from side 2 to side 3
      default: []
      selector:
        action: {}
    two_to_four:
      name: From side 2 to side 4
      description: Action to run when cube goes from side 2 to side 4
      default: []
      selector:
        action: {}
    two_to_five:
      name: From side 2 to side 5
      description: Action to run when cube goes from side 2 to side 5
      default: []
      selector:
        action: {}
    two_to_six:
      name: From side 2 to side 6
      description: Action to run when cube goes from side 2 to side 6
      default: []
      selector:
        action: {}
    three_to_one:
      name: From side 3 to side 1
      description: Action to run when cube goes from side 3 to side 1
      default: []
      selector:
        action: {}
    three_to_two:
      name: From side 3 to side 2
      description: Action to run when cube goes from side 3 to side 2
      default: []
      selector:
        action: {}
    three_to_four:
      name: From side 3 to side 4
      description: Action to run when cube goes from side 3 to side 4
      default: []
      selector:
        action: {}
    three_to_five:
      name: From side 3 to side 5
      description: Action to run when cube goes from side 3 to side 5
      default: []
      selector:
        action: {}
    three_to_six:
      name: From side 3 to side 6
      description: Action to run when cube goes from side 3 to side 6
      default: []
      selector:
        action: {}
    four_to_one:
      name: From side 4 to side 1
      description: Action to run when cube goes from side 4 to side 1
      default: []
      selector:
        action: {}
    four_to_two:
      name: From side 4 to side 2
      description: Action to run when cube goes from side 4 to side 2
      default: []
      selector:
        action: {}
    four_to_three:
      name: From side 4 to side 3
      description: Action to run when cube goes from side 4 to side 3
      default: []
      selector:
        action: {}
    four_to_five:
      name: From side 4 to side 5
      description: Action to run when cube goes from side 4 to side 5
      default: []
      selector:
        action: {}
    four_to_six:
      name: From side 4 to side 6
      description: Action to run when cube goes from side 4 to side 6
      default: []
      selector:
        action: {}
    five_to_one:
      name: From side 5 to side 1
      description: Action to run when cube goes from side 5 to side 1
      default: []
      selector:
        action: {}
    five_to_two:
      name: From side 5 to side 2
      description: Action to run when cube goes from side 5 to side 2
      default: []
      selector:
        action: {}
    five_to_three:
      name: From side 5 to side 3
      description: Action to run when cube goes from side 5 to side 3
      default: []
      selector:
        action: {}
    five_to_four:
      name: From side 5 to side 4
      description: Action to run when cube goes from side 5 to side 4
      default: []
      selector:
        action: {}
    five_to_six:
      name: From side 5 to side 6
      description: Action to run when cube goes from side 5 to side 6
      default: []
      selector:
        action: {}
    six_to_one:
      name: From side 6 to side 1
      description: Action to run when cube goes from side 6 to side 1
      default: []
      selector:
        action: {}
    six_to_two:
      name: From side 6 to side 2
      description: Action to run when cube goes from side 6 to side 2
      default: []
      selector:
        action: {}
    six_to_three:
      name: From side 6 to side 3
      description: Action to run when cube goes from side 6 to side 3
      default: []
      selector:
        action: {}
    six_to_four:
      name: From side 6 to side 4
      description: Action to run when cube goes from side 6 to side 4
      default: []
      selector:
        action: {}
    six_to_five:
      name: From side 6 to side 5
      description: Action to run when cube goes from side 6 to side 5
      default: []
      selector:
        action: {}
    one_to_one:
      name: Knock - Side 1
      description: Action to run when knocking on side 1
      default: []
      selector:
        action: {}
    two_to_two:
      name: Knock - Side 2
      description: Action to run when knocking on side 2
      default: []
      selector:
        action: {}
    three_to_three:
      name: Knock - Side 3
      description: Action to run when knocking on side 3
      default: []
      selector:
        action: {}
    four_to_four:
      name: Knock - Side 4
      description: Action to run when knocking on side 4
      default: []
      selector:
        action: {}
    five_to_five:
      name: Knock - Side 5
      description: Action to run when knocking on side 5
      default: []
      selector:
        action: {}
    six_to_six:
      name: Knock - Side 6
      description: Action to run when knocking on side 6
      default: []
      selector:
        action: {}
    slide_on_one:
      name: Slide - Side 1 up
      description: Action to run when slides with Side 1 up
      default: []
      selector:
        action: {}
    slide_on_two:
      name: Slide - Side 2 up
      description: Action to run when slides with Side 2 up
      default: []
      selector:
        action: {}
    slide_on_three:
      name: Slide - Side 3 up
      description: Action to run when slides with Side 3 up
      default: []
      selector:
        action: {}
    slide_on_four:
      name: Slide - Side 4 up
      description: Action to run when slides with Side 4 up
      default: []
      selector:
        action: {}
    slide_on_five:
      name: Slide - Side 5 up
      description: Action to run when slides with Side 5 up
      default: []
      selector:
        action: {}
    slide_on_six:
      name: Slide - Side 6 up
      description: Action to run when slides with Side 6 up
      default: []
      selector:
        action: {}
    cube_wake:
      name: Wake up the cube
      description: Action to run when cube wakes up
      default: []
      selector:
        action: {}
    cube_drop:
      name: Cube drops
      description: Action to run when cube drops
      default: []
      selector:
        action: {}
    cube_shake:
      name: Shake cube
      description: Action to run when you shake the cube
      default: []
      selector:
        action: {}
    rotate_right:
      name: Rotate right
      description: Action to run when cube rotates right
      default: []
      selector:
        action: {}
    rotate_left:
      name: Rotate left
      description: Action to run when cube rotates left
      default: []
      selector:
        action: {}
  source_url: https://community.home-assistant.io/t/aqara-magic-cube-zha-51-actions/270829/1
mode: restart
max_exceeded: silent
trigger:
- platform: event
  event_type: zha_event
  event_data:
    device_id: !input 'remote'
action:
- variables:
    command: '{{ trigger.event.data.command }}'
    value: '{{ trigger.event.data.args.value | default(0) }}'
    flip_degrees: '{{ trigger.event.data.args.flip_degrees | default(0) }}'
    flip_any: !input 'flip_any'
    flip_90: !input 'flip_90'
    flip_180: !input 'flip_180'
    slide_any_side: !input 'slide_any_side'
    knock_any_side: !input 'knock_any_side'
    flip90: 64
    flip180: 128
    slide: 256
    knock: 512
    shake: 0
    drop: 3
    activated_face: "\n{% if command == \"slide\" or command == \"knock\" %}\n\n \
      \ {% if trigger.event.data.args.activated_face == 1 %} 1\n\n  {% elif trigger.event.data.args.activated_face\
      \ == 2 %} 5\n\n  {% elif trigger.event.data.args.activated_face == 3 %} 6\n\n\
      \  {% elif trigger.event.data.args.activated_face == 4 %} 4\n\n  {% elif trigger.event.data.args.activated_face\
      \ == 5 %} 2\n\n  {% elif trigger.event.data.args.activated_face == 6 %} 3\n\n\
      \  {% endif %}\n\n{% elif command == 'flip' %}\n\n  {{ trigger.event.data.args.activated_face\
      \ | int }}\n\n{% endif %}\n"
    from_face: "\n{% if command == \"flip\" and flip_degrees == 90 %}\n\n  {{ ((value\
      \ - flip90 - (trigger.event.data.args.activated_face - 1)) / 8) + 1 | int }}\n\
      \n{% endif %}\n"
    relative_degrees: "\n{% if command == \"rotate_right\" or command == \"rotate_left\" %}\n\n \
      \ {{ trigger.event.data.args.relative_degrees | float }}\n\
      \n{% endif %}\n"
- choose:
  - conditions:
    - '{{ command == ''rotate_right'' }}'
    sequence: !input 'rotate_right'
  - conditions:
    - '{{ command ==  ''rotate_left'' }}'
    sequence: !input 'rotate_left'
  - conditions:
    - '{{ command == ''checkin'' }}'
    sequence: !input 'cube_wake'
  - conditions:
    - '{{ value == shake }}'
    sequence: !input 'cube_shake'
  - conditions:
    - '{{ value == drop }}'
    sequence: !input 'cube_drop'
  - conditions:
    - '{{ command == ''knock'' and knock_any_side }}'
    sequence: !input 'cube_knock_any'
  - conditions:
    - '{{ command == ''slide'' and slide_any_side }}'
    sequence: !input 'cube_slide_any'
  - conditions:
    - '{{ command == ''flip'' and flip_any }}'
    sequence:
    - choose:
      - conditions: '{{ activated_face == 1 }}'
        sequence: !input 'cube_flip_1'
      - conditions: '{{ activated_face == 2 }}'
        sequence: !input 'cube_flip_2'
      - conditions: '{{ activated_face == 3 }}'
        sequence: !input 'cube_flip_3'
      - conditions: '{{ activated_face == 4 }}'
        sequence: !input 'cube_flip_4'
      - conditions: '{{ activated_face == 5 }}'
        sequence: !input 'cube_flip_5'
      - conditions: '{{ activated_face == 6 }}'
        sequence: !input 'cube_flip_6'
  - conditions:
    - '{{ flip_degrees == 90 and flip_90 }}'
    sequence: !input 'cube_flip_90'
  - conditions:
    - '{{ flip_degrees == 180 and flip_180 }}'
    sequence: !input 'cube_flip_180'
  - conditions:
    - '{{ flip_degrees == 90 and activated_face == 1 }}'
    sequence:
    - choose:
      - conditions: '{{ from_face == 2 }}'
        sequence: !input 'two_to_one'
      - conditions: '{{ from_face == 3 }}'
        sequence: !input 'three_to_one'
      - conditions: '{{ from_face == 5 }}'
        sequence: !input 'five_to_one'
      - conditions: '{{ from_face == 6 }}'
        sequence: !input 'six_to_one'
  - conditions:
    - '{{ flip_degrees == 90 and activated_face == 2 }}'
    sequence:
    - choose:
      - conditions: '{{ from_face == 1 }}'
        sequence: !input 'one_to_two'
      - conditions: '{{ from_face == 3 }}'
        sequence: !input 'three_to_two'
      - conditions: '{{ from_face == 4 }}'
        sequence: !input 'four_to_two'
      - conditions: '{{ from_face == 6 }}'
        sequence: !input 'six_to_two'
  - conditions:
    - '{{ flip_degrees == 90 and activated_face == 3 }}'
    sequence:
    - choose:
      - conditions: '{{ from_face == 1 }}'
        sequence: !input 'one_to_three'
      - conditions: '{{ from_face == 2 }}'
        sequence: !input 'two_to_three'
      - conditions: '{{ from_face == 4 }}'
        sequence: !input 'four_to_three'
      - conditions: '{{ from_face == 5 }}'
        sequence: !input 'five_to_three'
  - conditions:
    - '{{ flip_degrees == 90 and activated_face == 4 }}'
    sequence:
    - choose:
      - conditions: '{{ from_face == 2 }}'
        sequence: !input 'two_to_four'
      - conditions: '{{ from_face == 3 }}'
        sequence: !input 'three_to_four'
      - conditions: '{{ from_face == 5 }}'
        sequence: !input 'five_to_four'
      - conditions: '{{ from_face == 6 }}'
        sequence: !input 'six_to_four'
  - conditions:
    - '{{ flip_degrees == 90 and activated_face == 5 }}'
    sequence:
    - choose:
      - conditions: '{{ from_face == 1 }}'
        sequence: !input 'one_to_five'
      - conditions: '{{ from_face == 3 }}'
        sequence: !input 'three_to_five'
      - conditions: '{{ from_face == 4 }}'
        sequence: !input 'four_to_five'
      - conditions: '{{ from_face == 6 }}'
        sequence: !input 'six_to_five'
  - conditions:
    - '{{ flip_degrees == 90 and activated_face == 6 }}'
    sequence:
    - choose:
      - conditions: '{{ from_face == 1 }}'
        sequence: !input 'one_to_six'
      - conditions: '{{ from_face == 2 }}'
        sequence: !input 'two_to_six'
      - conditions: '{{ from_face == 4 }}'
        sequence: !input 'four_to_six'
      - conditions: '{{ from_face == 5 }}'
        sequence: !input 'five_to_six'
  - conditions:
    - '{{ value == flip180 + activated_face - 1 }}'
    sequence:
    - choose:
      - conditions: '{{ activated_face == 1 }}'
        sequence: !input 'four_to_one'
      - conditions: '{{ activated_face == 2 }}'
        sequence: !input 'five_to_two'
      - conditions: '{{ activated_face == 3 }}'
        sequence: !input 'six_to_three'
      - conditions: '{{ activated_face == 4 }}'
        sequence: !input 'one_to_four'
      - conditions: '{{ activated_face == 5 }}'
        sequence: !input 'two_to_five'
      - conditions: '{{ activated_face == 6 }}'
        sequence: !input 'three_to_six'
  - conditions:
    - '{{ command == ''knock'' and not knock_any_side }}'
    sequence:
    - choose:
      - conditions: '{{ activated_face == 1 }}'
        sequence: !input 'one_to_one'
      - conditions: '{{ activated_face == 2 }}'
        sequence: !input 'two_to_two'
      - conditions: '{{ activated_face == 3 }}'
        sequence: !input 'three_to_three'
      - conditions: '{{ activated_face == 4 }}'
        sequence: !input 'four_to_four'
      - conditions: '{{ activated_face == 5 }}'
        sequence: !input 'five_to_five'
      - conditions: '{{ activated_face == 6 }}'
        sequence: !input 'six_to_six'
  - conditions:
    - '{{ value == slide + activated_face - 1 }}'
    sequence:
    - choose:
      - conditions: '{{ activated_face == 1 }}'
        sequence: !input 'slide_on_one'
      - conditions: '{{ activated_face == 2 }}'
        sequence: !input 'slide_on_two'
      - conditions: '{{ activated_face == 3 }}'
        sequence: !input 'slide_on_three'
      - conditions: '{{ activated_face == 4 }}'
        sequence: !input 'slide_on_four'
      - conditions: '{{ activated_face == 5 }}'
        sequence: !input 'slide_on_five'
      - conditions: '{{ activated_face == 6 }}'
        sequence: !input 'slide_on_six'
14 Likes

This looks like an awesome replacement for my nodered implementation.

But, the device selector only allows the zha devices and mine are from zigbee2mqtt. Is there a reason for this? Maybe zha gives more detail for the action or different action names?

Thanks

The trigger would definitely be different, and I’m guessing the data is probably also different. So the trigger and action variables would need to be rewritten.

I just started playing with magic cube tonight so this is great timing!

I just got this device a few days ago, and I am thrilled to see this blueprint, thank you! One question I have and have not been able to find an answer to is about the wake option. What is the motion with the cube that triggers the wake action?

That’s a great question. I honestly have never gotten a wake event of any sort from it in zha_event. Theoretically there should sometimes be a checkin event. I suspect it isn’t based on a gesture, but is possibly something that it occasionally wakes itself to check in with the hub, maybe?

Great blueprint, thank you! I’m only using a few functions from it, but it works great. If nothing else your code helped me solve a problem I was having with my own blueprint!

Great blueprint!!!
I think that after the last update, though, they fixed the 2<->5 and 3<->6 knock and slide. So maybe you have to update the blueprint

This looks awesome and I want to use it so bad, I have all kinds of ideas…
But…
I think I am missing something.
I have added my Aqara Cube in ZHA successfully but it has only one entity, the battery level of the Cube.
Any push in the direction of how to get the device implemented properly so I can start using this awesome blueprint with that cool little device would be massively appreciated.

That’s the correct behaviour.
You can start using this blueprint. Just select the right device.

Yep, you’re totally right.
I should have just tried it and I may have realised that.
I really appreciate the help.
Also, this works awesome!

Thanks @brent, that looks really powerful and this blueprint convinced me to buy one cube.
One question (or two) : it does not seem that sides of the cube are identified by numbers. How to you know which one is which ?
Second one : Aqara only advertises 6 moves and they don’t take into account the side. Shall I understand that through the Zigbee messages sent by the cube we can know the side nb facing up, etc … If so, this opens up to even more actions.
tks

Thank you for this good job!

This made the cube a really useful device. I’m a newbie.

Can you please tell me if there is some way to add a separate rotation for each side to this blueprint?

Or if this is too much for you, maybe someone else from respected users can give me an answer?

This would be the pinnacle of excellence in using this device.

1 Like

great work!
just got an aqara cube can’t wait to try this out.
does anyone know is it possible to have a double tap action with this blueprint, and if so how to implement?

EDIT: nevermind - my understanding now is that ‘knock’ is a double tap action, and there is no single tap?

1 Like

I’m glad this has been useful to so many people!

The side with “Aqara” is 1.
The side opposite that is 4.
If you position the cube so that Aqara is on the top, and facing you so you can read it, the remaining sides are:
Up: 3
Left: 5
Right: 2
Down: 6

If you want to check for yourself, go to Developer Tools → Events. Start listening to zha_event, and the event data will have an activated_face variable. That’s the side that’s up.

Some of the gestures do send the activated face (flip, slide, knock). Others don’t (drop, shake, rotate).

The information sent by the cube doesn’t include the side that’s up during a rotation. It would be possible to track that in an input_number helper, but I’m not sure how to generalize that for a blueprint without hardcoding a name of a helper (maybe possible, but if so I don’t know how yet). Also, there are many things that can happen that would get that out of sync, like tossing the cube in the air. Very easy for the side that’s actually up to be different than the last reported activated side.

It seems that a single tap doesn’t send an event at all. Any number of multiple taps in quick succession (I tested 2-6) send a single knock event.

2 Likes

:pray: really useful

Love it! Could you extend this with rotation on each side (same as slide)?. This would allow me to change brightness of multiple lights and adjust volume for instance.

Nope, unfortunately not. For a more thorough explanation, see my comment a couple replies up.

Am i the only one to report that my logs fill up with messages like the one below every time an event kicks in?

WARNING (MainThread) [homeassistant.helpers.template] Template variable warning: 'dict object' has no attribute 'flip_degrees' when rendering '{{ trigger.event.data.args.flip_degrees }}'

and the same for relative_degrees if flip is used.

The culprit is the blanket access of those trigger.event.data.args keys when setting up the initial variables.

A fix would be to check do the existence of the key first, like this:

action:

  - variables:
      ...
      flip_degrees:   ' {{ trigger.event.data.args.flip_degrees | default }}'
      relative_degrees: '{{ trigger.event.data.args.relative_degrees | default }}'

[edit: had suggested another code above for some period, but reverted back to default filter]

Is there any possiblity that this fix can be applied?
(and if yes, do i need then to refresh my blueprints, somehow?)

PS: thank you for your efforts.

Thanks for finding that and for the code revisions! Currently, relative_degrees isn’t used, so I commented it out. I also provided default values for both value and flip_degrees. In my testing, there are no longer errors, and everything still works as intended. Additional feedback welcome!

Edit: I’m not sure of the correct way to update yours. Documentation says “When a user imports a blueprint, Home Assistant stores the URL from which it was imported. This will allow Home Assistant in the future to update blueprints by looking at updated blueprint code in the forum topic.” So I can’t tell if that means it will automatically update or not.