[Modul] Shelly

Moin Kai,

genau, es werden die MQTT Parameter des Shellies (Gateway) genutzt, um auch diese Daten weiterzuleiten.

  • „info/windowStateID“ ist der Zustand des Sensors (offen/geschlossen)
  • „info/device“ habe ich mal rausgenommen und das im MainTopic besser umgesetzt

Nun sieht das Ergebnis bei mir so aus, die MAC ist natürlich die des BT-Devices:

Ich habe versucht mich an dem Schema von Shelly zu orientierten, sollte es von Dir anders gewünscht sein (Du hattest es etwas anders beschrieben) kann ich das gerne auch noch anpassen.

Viele Grüße
Dennis

– Aktuelles Script:

/* ============================= Config CHANGE HERE =============================== */
let mqttID = "shelly"; //MQTT Topic ID, (behind topicPrefix)
let sendMQTT = true; //send Button Data also per MQTT, true/false 
let topicPrefix = ""; //Here you can set your own mqtt Topic Prefix
let vSwitch = false; // create MQTT virtual Switch states, true/false
let debug = false; //show debug msg in log, true/false, warning setting this to 'true' will delay reaktion speed a lot
/* ============================== STOP CHANGING HERE =============================== */

//========= Skript Setup =========
let activeScan = true; //active or passiv Bluetooth Scan, true/false
let _cid = "0ba9"; //Allterco, Company ID(MFD)
let uuid = "fcd2"; //BTHome, Service ID
let devID1 = "SBBT"; // BluButton1, deviceID, --> SBBT-002C evt. 002C = charge?
let devID2 = "SBDW"; // Blu Door/Window, deviceID
let bthObjectIDs = []; //Used BTHome Service.Payload ObjectID List for BluButton1,(here you can find a full List --> " https://bthome.io/format ")
bthObjectIDs[0x00] = { key: 'pid', dataType: 'uint8' };
bthObjectIDs[0x01] = { key: 'battery', dataType: 'uint8', unit: '%' };
bthObjectIDs[0x3a] = { key: 'buttonID', dataType: 'uint8' };
bthObjectIDs[0x05] = { key: 'illuminance', dataType: 'uint24', factor: 0.01 };
bthObjectIDs[0x1a] = { key: 'doorStateID', dataType: 'uint8' };
bthObjectIDs[0x20] = { key: 'moistureLvl', dataType: 'uint8' };
bthObjectIDs[0x2d] = { key: 'windowStateID', dataType: 'uint8' };
bthObjectIDs[0x3f] = { key: 'rotationLvl', dataType: 'int16', factor: 0.1 };
let vSwitchCache = {}, last_packet_id = 0x100, last_addr = 0, scanDuration = BLE.Scanner.INFINITE_SCAN;
//=========== End Setup ===========
function SendMQTTmsg(mac,event){
    mac = mac.toUpperCase();
    mac = mac.substr(0, 2) + mac.substr(3, 2) + mac.substr(6, 2) + mac.substr(9, 2) + mac.substr(12, 2) + mac.substr(15, 2);
    if(MQTT.isConnected()){
        let mainTopic = mqttID + event.info.data.device.toLowerCase() + '-'+ mac + '/', inputID = JSON.stringify(event.info.data.inputID);
        if(topicPrefix.length !== 0) mainTopic = topicPrefix +'/'+ mqttID +'-'+ mac + '/';
        if(inputID !== '0' && inputID !== '254' && vSwitch){ // do not trigger virtual switch logik for beacon or hold events.
            if(vSwitchCache['s'+inputID] === undefined || vSwitchCache['s'+inputID] === 'false'){
                vSwitchCache['s'+inputID] = 'true';
            }else{
                vSwitchCache['s'+inputID] = 'false';
            }
        }
        if(debug) print('MQTT Publishing: _Topic: ', mainTopic, 'events/rpc _Payload: ', JSON.stringify(event));
        MQTT.publish(mainTopic + 'events/rpc', JSON.stringify(event), 1, false);
        //MQTT.publish(mainTopic + 'status/input:' + inputID, JSON.stringify(event.info.ts), 1, false);
        if(inputID !== '0' && inputID !== '254' && vSwitch) MQTT.publish(mainTopic + 'status/switch:' + inputID, vSwitchCache['s'+inputID], 1, false);
        MQTT.publish(mainTopic + 'info/battery', JSON.stringify(event.info.data.battery_value), 1, true);
        MQTT.publish(mainTopic + 'info/rssi', JSON.stringify(event.info.data.rssi), 1, true);
        MQTT.publish(mainTopic + 'info/lastTimeStamp', JSON.stringify(event.info.ts), 1, true);
        MQTT.publish(mainTopic + 'info/lastAction', event.info.event, 1, true);
        //MQTT.publish(mainTopic + 'info/lastAktionID', inputID, 1, true);
        //MQTT.publish(mainTopic + 'info/mac', event.info.data.mac, 1, true);
        //MQTT.publish(mainTopic + 'info/device', event.info.data.device, 1, true);
        //MQTT.publish(mainTopic + 'info/encryption', JSON.stringify(event.info.data.encryption), 1, true);
        MQTT.publish(mainTopic + 'info/gateway', event.info.data.gateway, 1, true);
        MQTT.publish(mainTopic + 'info/windowStateID', JSON.stringify(event.info.data.windowStateID), 1, true);
        MQTT.publish(mainTopic + 'info/rotationLvl', JSON.stringify(event.info.data.rotationLvl), 1, true);
        MQTT.publish(mainTopic + 'info/illuminance', JSON.stringify(event.info.data.illuminance), 1, true);
    }else{
        print('Error: MQTT is still not ready, cant send msg');
    }
}
function FilterEvents(obj){
    if(obj.info === undefined || obj.info.data === undefined || obj.info.data.generation !== 'BLU') return;
    SendMQTTmsg(obj.info.data.mac,obj);
}
let BTHomeDecoder = {
    UInt_To_Int: function (uint, bitsz) {
        let mask = 1 << (bitsz - 1); //create mask to filter last bit
        if (uint & mask) {
            return uint - (1 << bitsz); //convert uint to negative int
        } else {
            return uint; //uint is positive, no change needed.
        }
    },
    GetMaxBytes: function (dataType) {
        if (dataType === 'uint8' || dataType === 'int8') return 1;
        if (dataType === 'uint16' || dataType === 'int16') return 2;
        if (dataType === 'uint24' || dataType === 'int24') return 3;
        return 255;
    },
    GetValue: function (dataType, buffer) {
        let maxBytes = this.GetMaxBytes(dataType);
        if (buffer.length < maxBytes) {
            print('Error: ValueBuffer has, ', buffer.length, ' Bytes with DataType, ', dataType);
            return null;
        }
        let data = null;
        let _1stByte = buffer.at(0); //get 1.Byte of Buffer
        let _2ndByte = null;
        if (maxBytes > 1) _2ndByte = buffer.at(1); //get 2.Byte of Buffer
        let _3rdByte = null;
        if (maxBytes > 2) _3rdByte = buffer.at(2); //get 3.Byte of Buffer
        if (dataType === 'uint8') data = _1stByte;
        if (dataType === 'int8') data = this.UInt_To_Int(_1stByte, 8);
        if (dataType === 'uint16') data = 0xffff & ((_2ndByte << 8) | _1stByte);
        if (dataType === 'int16') data = this.UInt_To_Int(0xffff & ((_2ndByte << 8) | _1stByte), 16);
        if (dataType === 'uint24') data = 0x00ffffff & ((_3rdByte << 16) | (_2ndByte << 8) | _1stByte);
        if (dataType === 'int24') data = this.UInt_To_Int(0x00ffffff & ((_3rdByte << 16) | (_2ndByte << 8) | _1stByte), 24);
        return data;
    },
    Unpack: function (payload) {
        if (typeof payload !== "string" || payload.length === 0) return null;
        let btHomeObj = {}, byte = payload.at(0), value, objectID; //byte = payload.at(0) --> get 1.Byte of Payload
        if (byte & 0x1) {
            btHomeObj['encryption'] = true; //analyze 1. Bit and set btHomeObj encryption boolean
        } else {
            btHomeObj['encryption'] = false;
        }
        btHomeObj['version'] = byte >> 5; //cut,move to 6.Bit, save the rest as version   
        if (btHomeObj['version'] !== 2) return null; //analyse the rest Bits 6,7,8 inside version, exit if BTHome Version is not 2
        if (btHomeObj['encryption']) return btHomeObj; //exit if payload is encrypted
        payload = payload.slice(1); //remove used 1.Byte from payload
        while (payload.length > 0) {
            byte = payload.at(0); //get 1.Byte of Payload
            objectID = bthObjectIDs[byte]; //compare 1.Byte with known BTHome objectIds list, add parameter if known object
            if (typeof objectID === 'undefined') {
                print('Error: Unknown BThome Object, decimal_ID: ', byte, ' convert to hex and compare hex_ID "0x.." with full objID list --> https://bthome.io/format');
                break; //exit loop, if objectID is unknown
            }
            payload = payload.slice(1); //remove used 1.Byte from payload
            value = this.GetValue(objectID.dataType, payload);
            if (value === null) break; //exit loof, if value null
            if (typeof objectID.factor !== 'undefined') value = (value * objectID.factor); //add factor
            btHomeObj[objectID.key] = value;
            if (typeof objectID.unit !== 'undefined') value = (JSON.stringify(value) + objectID.unit); //add unit
            if (typeof objectID.unit !== 'undefined') btHomeObj[(objectID.key+"String")] = value;
            payload = payload.slice(this.GetMaxBytes(objectID.dataType)); //remove used value Bytes from payload
        }
        return btHomeObj;
    },
};
function ScanCB(status, response) {
    if (status !== BLE.Scanner.SCAN_RESULT) return; //exit if Scan status is stopped or unknown
    if (response.service_data === undefined || response.service_data[uuid] === undefined) return; //Filter only BThome Responses
    let BTHomeObj = BTHomeDecoder.Unpack(response.service_data[uuid]); //decode Sevice Data
    if (BTHomeObj === null) {print('Error: Failed to Unpack service_data of, ', response.addr); return;}
    if (last_packet_id === BTHomeObj.pid && last_addr === response.addr) return; //exit if packet is already known.
    if (debug) print('Received packet, raw Data:', JSON.stringify(response));
    if (response.local_name !== undefined) BTHomeObj.device = 'Unknown-Device--> ' + response.local_name;
    if (response.local_name !== undefined && response.local_name.indexOf(devID1) >= 0) BTHomeObj.device = 'BluButton1'; //search for Blu Button1
    if (response.local_name !== undefined && response.local_name.indexOf(devID2) >= 0) BTHomeObj.device = 'BluDoorWindow'; //search for Blu Door/Window
    if (response.local_name === undefined) BTHomeObj.device = 'Hidden-Device';
    last_packet_id = BTHomeObj.pid, last_addr = response.addr, BTHomeObj.addr = response.addr, BTHomeObj.rssi = response.rssi, BTHomeObj.gateway = Shelly.getDeviceInfo().id;
    if (debug) print('Received BTHome packet: ', JSON.stringify(BTHomeObj));
    if (typeof BTHomeObj.buttonID === 'number' && BTHomeObj.illuminance === undefined) { //somehow filter for blu Button
        let buttonEventMap = ['wake_up','single_push','double_push','triple_push','long_push','pairing_push','default_reset_push'], buttonInput = BTHomeObj.buttonID;
        if(buttonInput > 6 && buttonInput !== 254)buttonInput = 'unknown_push';
        if(buttonInput < 7)buttonInput = buttonEventMap[buttonInput];
        if(buttonInput === 254)buttonInput = 'hold_push';
        Shelly.emitEvent(buttonInput, {
            generation: 'BLU',
            gateway: BTHomeObj.gateway,
            device: BTHomeObj.device,
            battery_value: BTHomeObj.battery,
            battery_string: BTHomeObj.batteryString,
            inputID: BTHomeObj.buttonID,
            mac: BTHomeObj.addr,
            rssi: BTHomeObj.rssi,
            pid: BTHomeObj.pid,
            encryption: BTHomeObj.encryption
        });
    }
    if (typeof BTHomeObj.illuminance === 'number'){ //somehow, filter for blu door/window
         Shelly.emitEvent('blu_DW_ChangedStatus', {
            generation: 'BLU',
            gateway: BTHomeObj.gateway,
            device: BTHomeObj.device,
            battery_value: BTHomeObj.battery,
            battery_string: BTHomeObj.batteryString,
            doorStateID: BTHomeObj.doorStateID,
            windowStateID: BTHomeObj.windowStateID,
            illuminance: BTHomeObj.illuminance,
            moistureLvl: BTHomeObj.moistureLvl,
            rotationLvl: BTHomeObj.rotationLvl,
            buttonID: BTHomeObj.buttonID,
            mac: BTHomeObj.addr,
            rssi: BTHomeObj.rssi,
            pid: BTHomeObj.pid,
            encryption: BTHomeObj.encryption
        });
    }
}
function Start_BLE_Scan() {
    let bleScanStarted = BLE.Scanner.Start({ duration_ms: scanDuration, active: activeScan }, ScanCB);
    if (bleScanStarted === false) {
        print('Error: BTHome Scanner could not be started, will try again in 5sek.');
        Timer.set(5000, false, Start_BLE_Scan);
    } else {
        let scanType = 'Passiv';
        if (activeScan) scanType = 'Active';
        print('Success: BTHome ', scanType, ' Scanner running in Background');
    }
}
//__Main__
Start_BLE_Scan();
if(sendMQTT) Shelly.addEventHandler(FilterEvents);

Hallo,

ich habe hier den ersten neuen Shelly 1 plus mini. MQTT ist aktiv.
Der Shelly Konfigurator in Symcon finden den Shelly Min auch.
Ich kann aber keine neue Instanz erstellen. Dar Erstellen Button ist ausgegraut.

Ist der Shelly 1 plus Mini noch zu neu?

Vielen Dank und viele Grüße

Joerg

Ich habe deinen Beitrag mal in der richtige Thea verschoben.
Ich selbst habe noch kein Shelly 1 Plus Mini und konnte es noch nicht hinzufügen.

Kannst du mal ein Debug vom Konfigurator erstellen?

Grüße,
Kai

Hallo Kai,

ich habe den Shelly Mini auch erst seit gestern.
Wenn ich den Debug im Konfigurator öffne und dann runterlade hat die Dumb Datei keinen Inhalt.
Sorry, aber ich weiß ich nicht wie ich das richtig anstelle.

Viele Grüße
Joerg

Du musst das Debug öffnen.
Und dann auf aktualisieren klicken:

Grüße,
Kai

Hallo Kai,

hier jetzt das Debug

dump.txt (67,5 KB)

Kannst du die aktuelle Beta Version mal testen, ob es damit funktioniert?
Ich konnte es nicht testen.

Grüße,
Kai

1 „Gefällt mir“

Hallo Kai,

ja mit de aktuellen Beta wird der Shelly Mini erkannt und die Instanz lässt sich jetzt erstellen.

Vielen Dank für Deine Mühe und viele Grüße.

Joerg

Die Daten sind auch alle richtig innerhalb der Instanz?

Grüße,
Kai

Ja es sieht alles gut aus. >Ich kann keine Fehler feststellen.

Hallo @KaiS ,

habe in den letzten Wochen an meinem Shelly Rollo Problem ein wenig herumgetüftelt.

Ich komme an einer Stelle nicht weiter. Bei meinen alten Shelly 2.5 konnte ich die Werte für Position einfach per Skript überschreiben und das ganze wurde dann auch wie gewünscht ausgeführt.

Bei den neuen Plus2PM funktioniert das leider nicht. Der Wert, zB 50 für 50%, wird zwar in die Variable eingetragen, aber nicht ausgeführt. Gleiches Verhalten als ob man in der Variablem den Wert simuliert verändert.

Habe ich da irgendwo ein Denkfehler, oder hat sich da etwas zwischen den Shelly 2.5 und dem Plus2PM geändert?

Viele Grüße

Pulpataro

Wie schreibst du den Wert in die Variable?
Nutzt du RequestAction?

Grüße,
Kai

Hallo @KaiS ,

danke für den Reminder, hatte beim Test außerhalb meines Skriptes tatsächlich wieder SetValueInteger verwendet. Habs zur Kontrolle dann noch einmal manuell mit RequestAction gegengeprüft und natürlich wird der Wert rein geschrieben UND auch ausgeführt.

Jetzt wird es aber richtig spooky. Ich habe also heftig in die Tischplatte gebissen und mein ursprüngliches Skript, welches ja mit den Shelly 2.5 einwandfrei funktioniert, kontrolliert.

Hier war auch schon korrekterweise der RequestAction Befehl drin.

Das Skript wird verwendet um die Jalousien auf einen bestimmten Wert zu kippen. Daher einmal ganz runter fahren und dann 3 Prozent hochfahren um die Lamellen zu öffnen.

Befehl schließen wird von ALLEN, auch den Plus2PM einwandfrei durchgeführt.
Als nächstes habe ich ein temporäres Ereignis angelegt, welches bei Erreichen des Punktes Geschlossen auslöst und dann 3 Prozent auffährt.
Das Ereignis wird von ALLEN Shellys, auch den Plus2PM erkannt und ausgelöst. Nach Auslösung wird das Ereignis gelöscht und Fertig.
Aber der Befehl im Action des Ereignisses wird nur von den Shelly 2.5 ausgeführt. Die Plus2PM reagieren gar nicht.
Also bin ich hingegangen und habe überprüft, ob genau bei dieser Konfiguration genau bei den Shelly Plus2PM sich ein Tippfehler oder sonst etwas eingeschlichen hat.
Dazu habe ich den noch nicht ausgelösten Befehl aus der Action des Ereignisses herauskopiert und einzeln getestet.
Und was soll ich sagen, dieser einzeln herauskopierte Befehl funktioniert einwandfrei, solange er sich nicht in der Action des Ereignisses befindet …

Triggerskript, fährt die Jalousien herunter und erstellt das Ereignis zum aufkippen


<?php

function fahre_Raffstore($PID,$Position)
	{
	 $variable = IPS_GetVariable($PID);
	 $profileName = $variable['VariableCustomProfile'];
	 if ($profileName == '') {
   	   $profileName = $variable['VariableProfile'];
	  }
	 $value = $Position;
	 if ($profileName != '') {
   	 	$profile = IPS_GetVariableProfile($profileName);
   	 	$value = ($value * 0.01 * ($profile['MaxValue'] - $profile['MinValue'])) + $profile['MinValue'];
	  }
	 RequestAction($PID, $value);
	}


function kippe_Raffstore($PTid,$Position)
	{
     fahre_Raffstore($PTid,0);
     $eid = IPS_CreateEvent(0);                 // Ausgelöstes Ereignis anlegen
     IPS_SetEventTrigger($eid, 4, $PTid);       // Bei Änderung von Variable mit ID $PTid
     IPS_SetEventTriggerValue($eid, 0);         // Value für Eventtrigger setzen
     $script = "IPS_RunScriptWaitEx(88888, Array('RaffID' => '$PTid', 'value' => '$Position', 'eid' => '$eid'));";
     IPS_SetEventScript($eid, $script);         // Eventscript einfügen
     IPS_SetParent($eid, $PTid);                // Ereignis zuordnen
     IPS_SetEventActive($eid, true);            // Ereignis aktivieren
     IPS_SetEventTriggerSubsequentExecution($eid, false);
	}

kippe_Raffstore(11111, 3);
kippe_Raffstore(22222, 3);
kippe_Raffstore(33333, 3);
kippe_Raffstore(44444, 3);        
kippe_Raffstore(55555, 3);
kippe_Raffstore(66666, 3);
kippe_Raffstore(77777, 3);

?>

Skript 8888, das die Jalousien um den übergebenen Wert, 3, zum Kippen hochfährt

<?
$value = $_IPS['value'];
$RaffID = $_IPS['RaffID'];
$eid = $_IPS['eid'];
IPS_DeleteEvent($eid);
$variable = IPS_GetVariable($RaffID);
$profileName = $variable['VariableCustomProfile'];
if ($profileName == '') {
   $profileName = $variable['VariableProfile'];
}
if ($profileName != '') {
   $profile = IPS_GetVariableProfile($profileName);
   $value = ($value * 0.01 * ($profile['MaxValue'] - $profile['MinValue'])) + $profile['MinValue'];
}
RequestAction($RaffID, $value);
?>

Ein manueller Test, Eintragen des Request Action Befehls in das Ereignis mit manuellem herunterfahren kam zu keinem anderen Ergebnis. Ereignis wurde ausgelöst aber nicht ausgeführt.

Grüße

Pulpataro

Warum machst du das ganze nicht in einem Ablaufplan?
Das wäre wesentlich übersichtlicher und besser nachzuvollziehen.

Grüße,
Kai

komischer neumodischer Kram ^^ Scherz beiseite,

Nee, habe ich nicht getestet, als ich das ganze angefangen habe, gab es die glaube ich so wie heute noch nicht.
Werde ich auf alle Fälle mal testen. Kann man die Ablaufpläne ggfs auch per Skript ändern? Also zum Beispiel aufgrund eines komplett anderen Wertes, zB Temperatur den Winkel (Aufkippen) ändern?

Du könntest dir sowas auch als Variable anlegen, dann kannst du das aus dem Webfront ggf. ändern.

Grüße,
Kai

Nochmal zurück zu deinem Problem, wird eine Meldung im Log erzeugt, wenn das Ereignis ausgelöst wird und das Shelly nichts macht?

Grüße,
Kai

Guter Hinweis,

Notiz an mich selber: Vor dem Schreiben erst nachdenken ^^

Ich schaue mir das direkt mal an

Grüße

Pulpataro

Übersichtlicher nur bedingt, aber auf alle Fälle deutlich mehr Arbeit, beim Skript muss ich nur eine Zeile kopieren und zwei Zahlen anpassen.
Mit dem Ablaufplan Editor komme ich so gut wie gar nicht klar. Finde ich recht kompliziert und unübersichtlich.

Habe mich grad mal durchgewurschtelt und es getestet:

Aber das Funktioniert auch nicht, was mich ehrlich gesagt etwas erleichtert, scheint ja wirklich irgendwo etwas nicht zu stimmen.

Ereignis wurde wohl ausgelöst, das war es dann aber auch

Ist im Log etwas zu sehen?

Grüße,
Kai