Image of Xiaomi Magiccube and Homeassi Logostant

Xiaomi Magic Cube in Homeassistant einbinden

Vor kurzem habe ich mein Homeassistant von meinem Raspberry Pi 3 auf einen Intel NUC migriert und zeitgleich die Möglichkeit genutzt meine Konfiguration aus einem Python-Virtualenvironment zu befreien und in eine Hass.io-VM zu übertragen. Im selben Atemzug habe ich mir einen Conbee II zugelegt, um in Zukunft – ohne proprietäre Hubs – Zigbee Geräte steuern zu können und nicht länger nur auf deren Kompatibilität zu meiner vorhandenen Hue-Bridge angewiesen zu sein. Ein Conbee II eröffnet einem zugleich das Tor zur Welt in das Smart Home Ökosystem von Xiaomi/Aqara, welches eine Vielzahl günstiger Zigbee Sensoren bereitstellt.

Ein solches Device aus dem Hause Xiaomi ist der Magic Cube – ein kleines responsives Eingabegerät in Form eines Würfels, der eine Vielzahl von Bewegungen erkennt: Schütteln, Fallen, Rotieren, 90° Flip, 180° Flip, Schieben auf einer Seite, etc. Das kleine Device erschien mir perfekt als Musiksteuerungseinheit. Auf jede Seite eine Playlist, schütteln für Stop, rechts drehen für einen Track weiter und linksdrehen für einen Track zurück.

Der Würfel lässt sich ohne Probleme über das DeConz Add-On für Hass.io pairen und kann danach umgehend verwendet werden – jedoch leider nicht so, wie ich es mir vorgestellt hatte. In Hass tauchen nach dem Kopplungsvorgang lediglich zwei Batteriesensoren auf, die dem Cube zugeordnet sind. Es stellte sich heraus, dass das Gerät „auf dem Eventbus feuert“ und dies auch nur mittels uninterpretierter 4-Stelligen Zahlencodes, die einer eindeutigen Eingabeaktion zugeordnet sind. Um diese Codes auszulesen, muss in Homeassistant der Menüpunkt Developer Options > Events angeklickt werden und in dem unteren Eingabefeld deconz_event abonniert werden. Dreht, bewegt oder schüttelt man jetzt den Cube, erscheint dort ein zugehöriger Eventstream.
Um den Würfel sinnvoll nutzen zu können, sind solche uninterpretierten Zahlencodes nicht förderlich. Gleichzeitig gibt es schlichtweg zu viele dieser Codes um einen ellenlangen Template-Sensor zu erstellen. Templating auf dem Event Bus ist zusätzlich umständlich, nur bedingt sinnvoll und macht das Vorhaben nicht einfacher.

AppDaemon to the rescue!

Mit Appdaemon hatte ich zuvor keinerlei Annäherungsversuche gewagt. Für den Magic Cube schien es jedoch als das ideale Werkzeug die eingehenden Bus-Daten zu interpretieren. Appdaemon hat jedoch eine ziemlich steile Lernkurve, die mich als nicht sonderlich fähigen Hobby-Programmierer schnell abgeschreckt hat. Nach ein wenig Recherche fand ich dieses Skript, dass mir als eine gute Basis für mein Vorhaben erschien, jedoch nicht gänzlich meinen Vorstellungen entsprach. Unangepasst hat es lediglich zwei Dinge getan:

  • Einen Sensor angelegt, der die aktuell nach oben gerichtete Würfelseite ausgibt
  • Die 4-stelligen Zahlencodes interpretiert und diese auf einem frei-wählbaren Event erneut auf den Event-Bus geschickt

Nachteilg waren darüber hinaus, dass das Skript zwar die Mglichkeit bot direkt mehrere Cubes einzubinden, diese jedoch anschließend alle die gleichen Aktionen in Hass triggern würden. Auch wurde lediglich eine Rotation registriert, nicht jedoch in welche Richtung diese erfolgte. Ich habe das Skript daraufhin angepasst und zusätzlich zu den zuvor geschriebenen Funktionen folgendes erreicht:

  • Die interpretierten Zahlencodes werden nicht nur zurück auf den Eventbus geschickt, sondern es wird ein zusätzlicher Sensor erzeugt, der einen sprechenden Namen der Aktion ausgibt, z.B. flip90, shake_air, moove, flip180, etc. Sprechende Namen sind eine perfekte Ausgangsbasis für Automationen mit Templates.
  • Die Rotationsrichtung wird unterschieden in rotate_left und rotate_right.

Die von mir weiterentwicklete Appdaemon App im Detail:
Mein Ziel war es mehrere Magic Cubes unabhängig voneinander auslesen zu können. Eingegeben werden muss lediglich die DECONZ_ID, die Homeassistant als Entity zugewiesen bekommen hat. Andere Variablen des Skripts lauten:

  • SENSOR_ID: Der Name des Sensors, der erzeugt wird um die aktuell nach oben gerichtete Seite darzustellen
  • ACTION_ID: Der Sensor der einen sprechenden Namen für die letzte Aktion des Würfels ausgibt
  • EVENT_ID: Das Thema mit dem die letzte Aktion auf dem Eventbus ausgegeben wird und dort abonniert werden kann
# magiccube_kueche.py

# Based on https://github.com/iago1460/fast-deploy/blob/master/config_template/appdaemon/apps/cube.py

import appdaemon.plugins.hass.hassapi as hass
from enum import Enum


DECONZ_IDS = ('zauberwurfel_kueche')
SENSOR_ID = 'sensor.magiccube_kueche_faceup'
ACTION_ID = 'sensor.magiccube_kueche_action'
EVENT_ID = 'magiccube.kueche_action'


class Action(str, Enum):
    FLIP90 = 'flip90'
    FLIP180 = 'flip180'
    MOVE = 'move'  # = slide
    TAP_TWICE = 'tap_twice'
    SHAKE_AIR = 'shake_air'
    # SWING = 'swing'  ??
    ALERT = 'alert'  # = wake?
    FREE_FALL = 'free_fall'
    ROTATE_LEFT = "rotate_left"
    ROTATE_RIGHT = "rotate_right"


class CubeControl(hass.Hass):
    """
    Recreating xiaomi aqara binary sensor platform for cube
    https://www.home-assistant.io/components/binary_sensor.xiaomi_aqara/
    """
    def initialize(self):
        self.listen_event(self.handle_event, "deconz_event")

    def handle_event(self, event_name, data, kwargs):
        if data['id'] in DECONZ_IDS:
            if data['event'] in [1000, 2000, 3000, 4000, 5000, 6000]:
                to_side = data['event'] // 1000
                self.set_state(SENSOR_ID, state=to_side)
                self.set_state(ACTION_ID, state=Action.MOVE)
                self.fire_event(EVENT_ID, entity_id=SENSOR_ID, action_type=Action.MOVE)
            elif data['event'] in [1001, 2002, 3003, 4004, 5005, 6006]:
                to_side = data['event'] % 1000
                self.set_state(SENSOR_ID, state=to_side)
                self.set_state(ACTION_ID, state=Action.TAP_TWICE)
                self.fire_event(EVENT_ID, entity_id=SENSOR_ID, action_type=Action.TAP_TWICE)
            elif data['event'] in [1006, 2005, 3004, 4003, 5002, 6001]:
                from_side = data['event'] % 1000
                to_side = data['event'] // 1000
                self.set_state(SENSOR_ID, state=to_side, attributes={'from_side': from_side})
                self.set_state(ACTION_ID, state=Action.FLIP180)
                self.fire_event(EVENT_ID, entity_id=SENSOR_ID, action_type=Action.FLIP180)
            elif data['event'] in [1002, 1003, 1004, 1005, 2001, 2003, 2004, 2006, 3001, 3002, 3005, 3006, 4001, 4002, 4005, 4006, 5001, 5003, 5004, 5006, 6002, 6003, 6004, 6005]:
                from_side = data['event'] % 1000
                to_side = data['event'] // 1000
                self.set_state(SENSOR_ID, state=to_side, attributes={'from_side': from_side})
                self.set_state(ACTION_ID, state=Action.FLIP90)
                self.fire_event(EVENT_ID, entity_id=SENSOR_ID, action_type=Action.FLIP90)
            elif data['event'] == 7007:
                self.set_state(ACTION_ID, state=Action.SHAKE_AIR)
                self.fire_event(EVENT_ID, entity_id=SENSOR_ID, action_type=Action.SHAKE_AIR)
            elif data['event'] == 7008:
                self.set_state(ACTION_ID, state=Action.FREE_FALL)
                self.fire_event(EVENT_ID, entity_id=SENSOR_ID, action_type=Action.FREE_FALL)
            elif data['event'] == 7000:
                self.set_state(ACTION_ID, state=Action.ALERT)
                self.fire_event(EVENT_ID, entity_id=SENSOR_ID, action_type=Action.ALERT)
            else:
                degrees = data['event'] / 100
                if degrees < 0:
                    self.set_state(ACTION_ID, state=Action.ROTATE_LEFT, attributes={'direction': degrees})
                    self.fire_event(EVENT_ID, entity_id=SENSOR_ID, action_type=Action.ROTATE_LEFT, action_value=degrees)
                else:
                    self.set_state(ACTION_ID, state=Action.ROTATE_RIGHT, attributes={'direction': degrees})
                    self.fire_event(EVENT_ID, entity_id=SENSOR_ID, action_type=Action.ROTATE_RIGHT, action_value=degrees)

Dieser Code wird im Ordner /config/appdaemon/apps im Config-Pfad von Homeassistant unter dem beispielhaften Namen magiccube_kueche.py abgelegt. Zusätzlich muss nun noch die Datei apps.yaml im Pfad /config/appdaemon angepasst und folgender Eintrag hinzugefügt werden:

# apps.yaml

cube_control_kueche:
  module: magiccube_kueche
  class: CubeControl

Damit ist Homeassistant für den Magic Cube gewappnet und kann nach einem Neustart des Appdaemon Plugins die Daten des Würfels empfangen.
Ich habe hierfür mit Lovelace eine picture-elements-card angelegt, die neben der nach oben gerichteten Seite auch noch den sprechenden Namen der letzten Aktion und den Batteriestand des Devices anzeigt.

Aufgemerkt: Bis auf die Batterieangabe werden die anderen beiden Sensoren erst angezeigt, wenn der Würfel einmal eine Aktion ausgeführt hat.

Der zugehörige Code sieht folgendermaßen aus:

type: picture-elements
image: /local/magiccube.png
elements:
  - entity: sensor.magiccube_kueche_faceup
    style:
      font-size: 600%
      left: 72%
      top: 32%
    type: state-label
  - entity: sensor.magiccube_kueche_action
    style:
      font-size: 180%
      left: 72%
      top: 58%
    type: state-label
  - entity: sensor.switch_13_battery_level
    style:
      font-size: 180%
      left: 72%
      top: 78%
    type: state-label

Das Hintergrundbild mit dem Würfel ist tranparent als PNG-Datei vorhanden und eignet sich daher auch hervaorragend für das Zusammenspiel mit wechselnden Themes in der Lovelace UI. Einfach rechtklick drauf machen, beim Herunterladen umbenennen in magiccube.png und im Ordner /config/www/ ablegen:

Magic Cube zur Steuerung von Spotify nutzen

Nachdem die Lovelace Card eingebunden und der Magic Cube korrekt erkannt wird, kommt nun der spaßige Part. Jede Seite des Würfels wird mit einer Playlist belegt. Dreht man den Würfel auf diese Seite, startet diese auf dem media_player der Wahl. Dreht man den Würfel nach rechts, wird der nächste Track abgespielt. Dreht man ihn nach links, spielt der vorherige Track. Wird der Würfel geschüttelt, schaltet sich der Media_Player aus.
Im folgenden Beispiel erfolgt das mit Spotify als Media_Player. Als Abspielgerät kommt mein Bose Soundtouch 10 in der Küche zum Einsatz. Damit die dargestellten Automationen nicht zu lang werden, habe ich im Folgenden nur die Würfelseiten 1 und 6 dargestellt und exemplarisch 3 Aktionen (Linksdreh, Rechtsdreh und Schütteln) dargestellt:

- alias: Magic Cube Kueche Musiccontrol
  initial_state: True
  trigger:
    - platform: state
      entity_id: sensor.magiccube_kueche_faceup
  action:
    - service: media_player.turn_on
      entity_id: media_player.spotify
    - service: media_player.select_source
      data:
        entity_id: media_player.spotify
        source: Kueche
    - service: media_player.play_media
      data_template:
        media_content_type: playlist
        entity_id: media_player.spotify
        media_content_id: >
          {% if is_state("sensor.magiccube_kueche_faceup", "1") %}
            spotify:playlist:37i9dQZF1DX1xPh0VVE326
          {% elif is_state("sensor.magiccube_kueche_faceup", "6") %}
            spotify:playlist:37i9dQZF1DZ06evO02TC95
          {% endif %}

- alias: Magic Cube Kueche Actioncontrol
  initial_state: True
  trigger:
    - platform: state
      entity_id: sensor.magiccube_kueche_action
  action:
    - service_template: >
        {% if is_state("sensor.magiccube_kueche_action", "shake_air") %}
          media_player.turn_off
        {% elif is_state("sensor.magiccube_kueche_action", "rotate_right") %}
          media_player.media_next_track
        {% elif is_state("sensor.magiccube_kueche_action", "rotate_left") %}
          media_player.media_previous_track
        {% endif %}
      data:
        entity_id: media_player.soundtouch_kuche

Weitere Magic Cubes einbinden

Möchte man zustzlich weitere Cubes einbinden, so erstellt man ganz einfach für jeden neuen Würfel eine neue App im Ordner /config/appdaemon/app und bindet diese analog zu dieser Beispielapp in der apps.yaml im Ordner /config/appdaemon ein.

Viel Spaß beim Ausprobieren. Anregungen, Verbesserungen und einfallsreiche Ideen zur Nutzung des Magic Cubes sind in den Kommentaren herzlich willkommen!