Shelly BLU Button 1

Mit ** gekennzeichnete Links auf dieser Seite sind Affiliatelinks.

Shelly BLU Button 1
Shelly BLU Button 1
  • Matthias Kleine
  • 03.05.2023
  • Hardware
  • Produkt-Review

Shelly BLU? Was sind das nun wieder für Geräte. Erst Plus, dann Pro und jetzt BLU? Wie der Name schon vermuten lässt, handelt es sich um Bluetooth-Komponenten. Also verlässt Shelly den bekannten Weg und setzt nicht mehr in jedem Fall auf WiFi / WLAN für die Kommunikation. Das war in der Vergangenheit immer wieder ein Problem. Gerade Batteriebetriebene Geräte müssen sehr komplex umgesetzt werden, damit die Batterie nicht nach wenigen Stunden platt ist. Man bedient sich also jeder Menge Tricks um die Batterielaufzeit zu verlängern. Weiterhin helfen große Akkus in vielen Produkten.

Wichtig: Leider ist der Shelly BLU Button1 mit der ersten Firmware (1.0.2) sehr energiebedürftig. Das heißt, dass die Batterien nach wenigen Tagen leer sind und ersetzt werden müssen. Abhilfe soll ein Firmware-Update schaffen, welches allerdings nur mit einer Shelly-App auf einem Android-Gerät durchgeführt werden kann. Das schmälert meine ursprüngliche Euphorie dann doch sehr. Mir ist nicht bekannt, ab welchem Bestelldatum man den Button mit der neuesten Firmware geliefert bekommt. Nach einem Update auf 1.0.5 sind die Probleme scheinbar weg. Wie man das Update durchführt, findest Du weiter unten im Blog-Beitrag

Allerdings ist das aus meiner Sicht nicht der richtige Weg. Nimmt man zum Beispiel einen Button oder Fensterkontakt, dann möchte man die Aktion so schnell wie möglich im Smart Home System auswerten und damit arbeiten. Wenn wir da von 2-3 Sekunden Verzögerung sprechen, fühlt es sich schon nicht mehr gut an. Und so lange kann es schonmal dauern, bis die WLAN-Verbindung aufgebaut wurde, der MQTT-Server kontaktiert wurde und dann erst die paar Byte an Information übertragen wird. Der technische Aufwand und die zu versendende Datenmenge stehen einfach in keinem sinnvollen Verhältnis.

Daher ist es umso schöner, dass Shelly das nun auch erkannt hat und Bluetooth-Geräte auf dem Markt bringt. Und das schöne dabei ist: Jeder Shelly Plus oder Shelly Pro kann als Gateway dienen. Sobald Du also ein paar dieser Geräte verbaut hast (optimimalerweise im ganzen Haus verteilt), sind alle Voraussetzungen erfüllt. Denn diese Geräte basieren auf einem ESP32, welcher automatisch eine Bluetooth-Schnittstelle mitbringt.

Wie genau man das Ganze einrichtet und welche Möglichkeiten sich ergeben, erfährst Du im heutigen Video.

Video

Hausbau-Kurs

Firmware-Update

Da der Shelly BLU Button 1 mit der Firmware 1.0.2 ein echter Batterie-Killer ist (hält keine 14 Tage durch), muss man ein Update machen. Dafür gibt es eine Android-App im Google Play Store. Leider aktuell wirklich nur für Android und nicht für iOS. Damit konnte ich ein Update auf die Firmware-Version 1.0.5 durchführen. Hoffentlich sind die Probleme damit nun erstmal erledigt!

BLE MQTT publish

Wichtig: Laut Hersteller soll noch im Mai 2023 ein Update verfügbar sein, welches automatisch die Bluetooth-Payloads per MQTT published. Dann wird das folgende Script also überflüssig. Jetzt habe ich es aber schon gebaut und möchte es Dir nicht vorenthalten. Eventuell kannst Du es ja als Referenz für weitere Scripts brauchen.

Das folgende Script ist nur ein Vorschlag, wie man die Events vom Shelly BLE Button 1 auf MQTT weiterleiten könnte. Dabei wird auf dem Topic shellies/ble zum Beispiel der folgende Payload gepublished (1x kurz gedrückt):

{
    "event": {
        "Button": 1,
        "Battery": 29,
        "pid": 116,
        "BTHome_version": 2,
        "encryption": false
    },
    "sender": {
        "mac": "b4:35:22:f6:48:31",
        "type": "SBBT-002C"
    },
    "receiver": "shellyplus1-a8032abd007c"
}
// v0.1
let BTHOME_SVC_ID_STR = "fcd2";

let uint8 = 0;
let int8 = 1;
let uint16 = 2;
let int16 = 3;
let uint24 = 4;
let int24 = 5;

let BTH = {};
let SHELLY_ID = undefined;

BTH[0x00] = { n: "pid", t: uint8 };
BTH[0x01] = { n: "Battery", t: uint8, u: "%" };
BTH[0x05] = { n: "Illuminance", t: uint24, f: 0.01 };
BTH[0x1a] = { n: "Door", t: uint8 };
BTH[0x20] = { n: "Moisture", t: uint8 };
BTH[0x2d] = { n: "Window", t: uint8 };
BTH[0x3a] = { n: "Button", t: uint8 };

function getByteSize(type) {
    if (type === uint8 || type === int8) return 1;
    if (type === uint16 || type === int16) return 2;
    if (type === uint24 || type === int24) return 3;
    //impossible as advertisements are much smaller;
    return 255;
}

let BTHomeDecoder = {
    utoi: function (num, bitsz) {
        let mask = 1 << (bitsz - 1);
        return num & mask ? num - (1 << bitsz) : num;
    },
    getUInt8: function (buffer) {
        return buffer.at(0);
    },
    getInt8: function (buffer) {
        return this.utoi(this.getUInt8(buffer), 8);
    },
    getUInt16LE: function (buffer) {
        return 0xffff & ((buffer.at(1) << 8) | buffer.at(0));
    },
    getInt16LE: function (buffer) {
        return this.utoi(this.getUInt16LE(buffer), 16);
    },
    getUInt24LE: function (buffer) {
        return (
            0x00ffffff & ((buffer.at(2) << 16) | (buffer.at(1) << 8) | buffer.at(0))
        );
    },
    getInt24LE: function (buffer) {
        return this.utoi(this.getUInt24LE(buffer), 24);
    },
    getBufValue: function (type, buffer) {
        if (buffer.length < getByteSize(type)) return null;
        let res = null;
        if (type === uint8) res = this.getUInt8(buffer);
        if (type === int8) res = this.getInt8(buffer);
        if (type === uint16) res = this.getUInt16LE(buffer);
        if (type === int16) res = this.getInt16LE(buffer);
        if (type === uint24) res = this.getUInt24LE(buffer);
        if (type === int24) res = this.getInt24LE(buffer);
        return res;
    },
    unpack: function (buffer) {
        //beacons might not provide BTH service data
        if (typeof buffer !== "string" || buffer.length === 0) return null;
        let result = {};
        let _dib = buffer.at(0);
        result["encryption"] = _dib & 0x1 ? true : false;
        result["BTHome_version"] = _dib >> 5;
        if (result["BTHome_version"] !== 2) return null;
        //can not handle encrypted data
        if (result["encryption"]) return result;
        buffer = buffer.slice(1);

        let _bth;
        let _value;
        while (buffer.length > 0) {
            _bth = BTH[buffer.at(0)];
            if (typeof _bth === "undefined") {
                console.log("BTH: unknown type");
                break;
            }
            buffer = buffer.slice(1);
            _value = this.getBufValue(_bth.t, buffer);
            if (_value === null) break;
            if (typeof _bth.f !== "undefined") _value = _value * _bth.f;
            result[_bth.n] = _value;
            buffer = buffer.slice(getByteSize(_bth.t));
        }
        return result;
    },
};

let lastPacketId = 0x100;

function bleScanCallback(event, result) {
    if (event !== BLE.Scanner.SCAN_RESULT) {
        return;
    }

    if (typeof result.local_name === "undefined" || typeof result.addr === "undefined" || result.local_name.indexOf("SBBT") !== 0) {
        return;
    }

    let servData = result.service_data;

    // exit if service data is null/device is encrypted
    if (servData === null || typeof servData === "undefined" || typeof servData[BTHOME_SVC_ID_STR] === "undefined") {
        console.log("Can't handle encrypted devices");
        return;
    }

    let receivedData = BTHomeDecoder.unpack(servData[BTHOME_SVC_ID_STR]);

    // exit if unpacked data is null or the device is encrypted
    if (receivedData === null || typeof receivedData === "undefined" || receivedData["encryption"]) {
        console.log("Can't handle encrypted devices");
        return;
    }

    // exit if the event is duplicated
    if (lastPacketId === receivedData.pid) {
        return;
    }

    lastPacketId = receivedData["pid"];

    let message = {
        receiver: SHELLY_ID,
        sender: {
            type: result.local_name,
            mac: result.addr
        },
        event: receivedData
    };

    console.log("Publishing " + JSON.stringify(message));

    if (MQTT.isConnected()) {
        MQTT.publish("shellies/ble", JSON.stringify(message));
    }
}

function bleScan() {
    let bleScanner = BLE.Scanner.Start({
        duration_ms: BLE.Scanner.INFINITE_SCAN,
        active: true
    });

    if (bleScanner === false) {
        console.log("Error when starting the BLE scanner");
        return;
    }

    BLE.Scanner.Subscribe(bleScanCallback);
    console.log("BLE is successfully started");
}

Shelly.call("Mqtt.GetConfig", "", function (res, err_code, err_msg, ud) {
  SHELLY_ID = res["topic_prefix"];
});

// Check for BLE config and print a message if BLE is not enabled on the device
let bleConfig = Shelly.getComponentConfig('ble');
if (bleConfig.enable) {
    bleScan();
}

Beispiel ioBroker-Script

Und hier noch ein Beispiel Blockly-Script, wie man den Payload vom vorigen Script zerlegen könnte.

<xml xmlns="https://developers.google.com/blockly/xml">
  <variables>
    <variable id="]W,lypbF#)iJs8=/+/Pm">obj</variable>
    <variable id="TyTu[i!;Vvq~MWz1::02">deviceType</variable>
    <variable id="ReQ,su)ll%UG`x^@t7%`">buttonMac</variable>
    <variable id="mJU6BZgNQ.*Gz(y[yf}]">buttonId</variable>
  </variables>
  <block type="on" id="+C^TOo2VFiVv6CB2OqX?" x="88" y="88">
    <field name="OID">0_userdata.0.ble-payload</field>
    <field name="CONDITION">any</field>
    <field name="ACK_CONDITION"></field>
    <statement name="STATEMENT">
      <block type="variables_set" id="8XF+u~_%0_zudE?C)(?i">
        <field name="VAR" id="]W,lypbF#)iJs8=/+/Pm">obj</field>
        <value name="VALUE">
          <block type="convert_json2object" id="0C}Tt4nMBEbhe~dDlzqc">
            <value name="VALUE">
              <block type="on_source" id="eJ=JLq11+]aj2IxI!GfM">
                <field name="ATTR">state.val</field>
              </block>
            </value>
          </block>
        </value>
        <next>
          <block type="variables_set" id="c_0ol+b[1CGNWm-I,;-.">
            <field name="VAR" id="TyTu[i!;Vvq~MWz1::02">deviceType</field>
            <value name="VALUE">
              <block type="get_attr" id="9OT0h,A?wtfNu%d;E(8U">
                <value name="PATH">
                  <shadow type="text" id="5GqB*TWU@;;)MNW^~d[=">
                    <field name="TEXT">sender.type</field>
                  </shadow>
                </value>
                <value name="OBJECT">
                  <block type="variables_get" id=")RVyl~L)uHDmX,8A9hiF">
                    <field name="VAR" id="]W,lypbF#)iJs8=/+/Pm">obj</field>
                  </block>
                </value>
              </block>
            </value>
            <next>
              <block type="controls_if" id="[6Wp~Pla=7L8JUsrwRt(">
                <value name="IF0">
                  <block type="logic_compare" id="s`LC(t+fcH4c82`lC7!G">
                    <field name="OP">EQ</field>
                    <value name="A">
                      <block type="text_getSubstring" id="92I/cmq;=db~=Vo.-ub;">
                        <mutation at1="false" at2="true"></mutation>
                        <field name="WHERE1">FIRST</field>
                        <field name="WHERE2">FROM_START</field>
                        <value name="STRING">
                          <block type="variables_get" id="LT9]^nMlM{ywFCiXC7~u">
                            <field name="VAR" id="TyTu[i!;Vvq~MWz1::02">deviceType</field>
                          </block>
                        </value>
                        <value name="AT2">
                          <block type="math_number" id="Ows_eKFoGpX-1POscoY(">
                            <field name="NUM">4</field>
                          </block>
                        </value>
                      </block>
                    </value>
                    <value name="B">
                      <block type="text" id="p;bfqgZyl.EMpH;t]RE_">
                        <field name="TEXT">SBBT</field>
                      </block>
                    </value>
                  </block>
                </value>
                <statement name="DO0">
                  <block type="variables_set" id="zK?3W/9vU9c}i6q%VK_s">
                    <field name="VAR" id="ReQ,su)ll%UG`x^@t7%`">buttonMac</field>
                    <value name="VALUE">
                      <block type="get_attr" id="rbx]wFFoexGh+AOL%hg~">
                        <value name="PATH">
                          <shadow type="text" id="-vzvDErW^yUXM]Av{XfT">
                            <field name="TEXT">sender.mac</field>
                          </shadow>
                        </value>
                        <value name="OBJECT">
                          <block type="variables_get" id="L}B^P`el22jO#Gmyyu1h">
                            <field name="VAR" id="]W,lypbF#)iJs8=/+/Pm">obj</field>
                          </block>
                        </value>
                      </block>
                    </value>
                    <next>
                      <block type="variables_set" id="Z3!zTp$qbc2^;S(yMfAm">
                        <field name="VAR" id="mJU6BZgNQ.*Gz(y[yf}]">buttonId</field>
                        <value name="VALUE">
                          <block type="get_attr" id="O.`8q,Q`_.ZptG6(`YyU">
                            <value name="PATH">
                              <shadow type="text" id="SRBiT_9p,@aQG+`Qa!cl">
                                <field name="TEXT">event.Button</field>
                              </shadow>
                            </value>
                            <value name="OBJECT">
                              <block type="variables_get" id="av^2Z8PK{,3ry@CS2Ji,">
                                <field name="VAR" id="]W,lypbF#)iJs8=/+/Pm">obj</field>
                              </block>
                            </value>
                          </block>
                        </value>
                        <next>
                          <block type="controls_if" id="j8^2Ap5$xLTT8%p7XFVj">
                            <mutation elseif="1"></mutation>
                            <value name="IF0">
                              <block type="logic_compare" id="BvQa^H4eW+0$,qC,Fmv#">
                                <field name="OP">EQ</field>
                                <value name="A">
                                  <block type="variables_get" id="1wVt83Fo2t{J.k~o9H(@">
                                    <field name="VAR" id="ReQ,su)ll%UG`x^@t7%`">buttonMac</field>
                                  </block>
                                </value>
                                <value name="B">
                                  <block type="text" id="gyHh,EPxB]k3-2w1K9g;">
                                    <field name="TEXT">b4:35:22:f6:48:31</field>
                                  </block>
                                </value>
                              </block>
                            </value>
                            <statement name="DO0">
                              <block type="controls_if" id=",Z)]v%_X5}@IKw#oL[Ts">
                                <mutation elseif="3"></mutation>
                                <value name="IF0">
                                  <block type="logic_compare" id="5PViJtqKwUsE77jggPA*">
                                    <field name="OP">EQ</field>
                                    <value name="A">
                                      <block type="variables_get" id="yCspep%7V,0g|qm.4bJ8">
                                        <field name="VAR" id="mJU6BZgNQ.*Gz(y[yf}]">buttonId</field>
                                      </block>
                                    </value>
                                    <value name="B">
                                      <block type="math_number" id="WV@Xcrgk)i`W9uSIp5vF">
                                        <field name="NUM">1</field>
                                      </block>
                                    </value>
                                  </block>
                                </value>
                                <statement name="DO0">
                                  <block type="comment" id="Crn~de?yX||xur4pZv()">
                                    <field name="COMMENT">1x kurz</field>
                                    <next>
                                      <block type="debug" id="/8ZVaX[4a$D*jTY=iTN7">
                                        <field name="Severity">log</field>
                                        <value name="TEXT">
                                          <shadow type="text" id="+NHB?ykAI6ZPShw4hmjW">
                                            <field name="TEXT">1x kurz</field>
                                          </shadow>
                                        </value>
                                      </block>
                                    </next>
                                  </block>
                                </statement>
                                <value name="IF1">
                                  <block type="logic_compare" id="4!Nfr*!#f):V:Eu,.RA7">
                                    <field name="OP">EQ</field>
                                    <value name="A">
                                      <block type="variables_get" id="$QGfD%|i?akr_sZ|FkdR">
                                        <field name="VAR" id="mJU6BZgNQ.*Gz(y[yf}]">buttonId</field>
                                      </block>
                                    </value>
                                    <value name="B">
                                      <block type="math_number" id="Gy:WZ4|+}tT_NX0Bk)TY">
                                        <field name="NUM">2</field>
                                      </block>
                                    </value>
                                  </block>
                                </value>
                                <statement name="DO1">
                                  <block type="comment" id="[NX)^MrLQB?NnA7VCM^5">
                                    <field name="COMMENT">2x kurz</field>
                                    <next>
                                      <block type="debug" id="!pi8*_eoZ^,.Dio@mS]a">
                                        <field name="Severity">log</field>
                                        <value name="TEXT">
                                          <shadow type="text" id="`gEW~X!WJ{TYFB]h;/{H">
                                            <field name="TEXT">2x kurz</field>
                                          </shadow>
                                        </value>
                                      </block>
                                    </next>
                                  </block>
                                </statement>
                                <value name="IF2">
                                  <block type="logic_compare" id="Az8Jz^kTIBu,3):C%bMO">
                                    <field name="OP">EQ</field>
                                    <value name="A">
                                      <block type="variables_get" id="-h6s$UcYk#BRV@FZP%9a">
                                        <field name="VAR" id="mJU6BZgNQ.*Gz(y[yf}]">buttonId</field>
                                      </block>
                                    </value>
                                    <value name="B">
                                      <block type="math_number" id="wjFT)1BA36|@A{A}oUX{">
                                        <field name="NUM">3</field>
                                      </block>
                                    </value>
                                  </block>
                                </value>
                                <statement name="DO2">
                                  <block type="comment" id=":]HHi#*HZ(9~{$8c?$Y]">
                                    <field name="COMMENT">3x kurz</field>
                                    <next>
                                      <block type="debug" id="7D8QUV$[os=7=`hR1k$x">
                                        <field name="Severity">log</field>
                                        <value name="TEXT">
                                          <shadow type="text" id="Hz#]^:n@]Lby+}Hct.Lx">
                                            <field name="TEXT">3x kurz</field>
                                          </shadow>
                                        </value>
                                      </block>
                                    </next>
                                  </block>
                                </statement>
                                <value name="IF3">
                                  <block type="logic_compare" id="S)mGqdtD/[QKqr*t|SEx">
                                    <field name="OP">EQ</field>
                                    <value name="A">
                                      <block type="variables_get" id="KfXAs.v%5-MRrD;UMS|@">
                                        <field name="VAR" id="mJU6BZgNQ.*Gz(y[yf}]">buttonId</field>
                                      </block>
                                    </value>
                                    <value name="B">
                                      <block type="math_number" id="L|(bnG;br,FzKOr-+_Vm">
                                        <field name="NUM">4</field>
                                      </block>
                                    </value>
                                  </block>
                                </value>
                                <statement name="DO3">
                                  <block type="comment" id="Wmn1_HFY3f$c_z#mBX0z">
                                    <field name="COMMENT">1x lang</field>
                                    <next>
                                      <block type="debug" id="av@o}fpLw77iN$/;j|w=">
                                        <field name="Severity">log</field>
                                        <value name="TEXT">
                                          <shadow type="text" id="xfq}u1*-0TR*RN!,_2fx">
                                            <field name="TEXT">1x lang</field>
                                          </shadow>
                                        </value>
                                      </block>
                                    </next>
                                  </block>
                                </statement>
                              </block>
                            </statement>
                            <value name="IF1">
                              <block type="logic_compare" id="^+iDea73.B,ICZ@2+zEL">
                                <field name="OP">EQ</field>
                                <value name="A">
                                  <block type="variables_get" id="y|{(rlUor?{?z@j,52JB">
                                    <field name="VAR" id="ReQ,su)ll%UG`x^@t7%`">buttonMac</field>
                                  </block>
                                </value>
                                <value name="B">
                                  <block type="text" id="vfm}PA$?Av@mlqzsT9cp">
                                    <field name="TEXT">...</field>
                                  </block>
                                </value>
                              </block>
                            </value>
                          </block>
                        </next>
                      </block>
                    </next>
                  </block>
                </statement>
              </block>
            </next>
          </block>
        </next>
      </block>
    </statement>
  </block>
</xml>
Du willst mehr?

Smart-Home-Trainings von A-Z

Steig noch tiefer in die Themen ein und meistere Deine Projekte! Über 13.000 Teilnehmer konnten sich schon von der Qualität der Online-Kurse überzeugen.

ioBroker-Master-Kurs

ioBroker-Master-Kurs

Mehr Infos
Hausbau-Kurs

Hausbau mit KNX

Mehr Infos
Lox-Kurs

Lox-Kurs

Mehr Infos
Node-RED-Master-Kurs

Node-RED-Master-Kurs

Mehr Infos