AutoUpdate für Zigbee2MQTT

Moin,
vor fast genau 3 Jahren habe ich schon mit IPS, Z2M und dem Modul Zigbee2MQTT Updates für Geräte durchgeführt. In den letzten 3 Jahren ist an allen Ecken viel geschehen. Ich habe es mir jetzt angesehen und es geht mit kleinen Änderungen immer noch.

Die Idee hinter diesem Trick ist ganz einfach. Die Zigbee-Geräte sind nicht immer empfangsbereit für ein Updatebegehren da sie, wie ich, fast immer schlafen. Nachdem sie aber Statusänderungen gemeldet haben sind sie noch eine gewisse Zeit wach und nehmen Befehle entgegen und das nutze ich aus. Ich reagiere auf Aktualisierung von LQI und schicke dann sofort den Update-Befehl.

Im Zigbee2MQTT-Modul muss etwas hinzugefügt werden. In der Datei Zigbee2MQTTHelper.php muss Folgendes etwa bei Zeile 1930 ergänzt werden. Die beste Stelle ist vor/hinter der jetzigen Auswertung von „updateAvailable“.

                if (array_key_exists('update', $Payload)) {
						//Bleibt hier. gibt es nicht als Expose
					$this->RegisterVariableInteger('Z2M_FWinstalled', $this->Translate('installierte Firmware'), '');
					$this->RegisterVariableInteger('Z2M_FWlatest', $this->Translate('aktuelle Firmware'), '');
					$this->RegisterVariableString('Z2M_UpdateStatus', $this->Translate('Update Status'), '');
                    if (isset($Payload['update']["installed_version"])) $this->SetValue('Z2M_FWinstalled', $Payload['update']["installed_version"]);
                    if (isset($Payload['update']["latest_version"])) $this->SetValue('Z2M_FWlatest', $Payload['update']["latest_version"]);
                    if (isset($Payload['update']["progress"])){
                    	$this->SetValue('Z2M_UpdateStatus', $Payload['update']["state"] . " " . $Payload['update']["progress"] . "%");
                    }
                    else $this->SetValue('Z2M_UpdateStatus', $Payload['update']["state"]);
                }

Hiermit liefert Z2M wieder den Status von Update in „state“ und es wird als „Update Status“ als Variable ausgegeben. Ich werte 3 Stati aus:
idle = kein Update verfügbar
available = Update ist verfügbar
updating XY% = es läuft ein Update und XY% sind schon erledigt.

Am Ende von den Updates gibt es noch ein Paket mit Informationen zur Firmware Version. Die kann ich nicht mehr auswerten weil meine Geräte schon alle aktuelle Firmware haben. Wenn die Änderungen im Zigbee2MQTT Modul eingebaut worden sind und das Modul neu geladen wurde muss es einige Tagen laufen damit die ersten möglichen Updates auch übermittelt worden sind.

Weiter muss ein MQTT-Klient angelegt werden mit dem Topic „zigbee2mqtt/bridge/request/device/ota_update/update“ (ohne Anführungszeichen). Das GateWay für diesen Klienten auf den MQTT-Server von Zigbee2MQTT festlegen. Die ID der Variablen dieses Klienten merken denn die muss im folgenden Script eingesetzt werden.

<?php
// Variable anlegen wo die aktuell behandelte Z2M Instanz gespeichert wird.
$InstanzID = @IPS_GetVariableIDByName('InstanzID', $_IPS['SELF']);
if($InstanzID === false)
{
    $InstanzID = IPS_CreateVariable(1);
    IPS_SetName($InstanzID, 'InstanzID');
    IPS_SetParent($InstanzID, $_IPS['SELF']);
// Bisher keine Instanz behandeln    
    SetValue($InstanzID, 0);
}
// Variable anlegen wo der Trigger für die Update-Routine gespeichert wird
$TriggerID = @IPS_GetVariableIDByName('TriggerID', $_IPS['SELF']);
if($TriggerID === false)
{
    $TriggerID = IPS_CreateVariable(1);
    IPS_SetName($TriggerID, 'TriggerID');
    IPS_SetParent($TriggerID, $_IPS['SELF']);
// Bisher kein Trigger gesetzt    
    SetValue($TriggerID, 0);
}

// aktuell behandelte Instanz laden
$Instanz = GetValue($InstanzID);
switch ($_IPS['SENDER']){
// Wenn das Script durch Trigger aufgerufen wurde    
    case "Variable":
        $VarID1 = @IPS_GetObjectIDByIdent('Z2M_UpdateStatus', $Instanz);
// aktuellen Update-Status auslesen
        $Var1 = GetValue($VarID1);
// Wenn Update verfügbar Z2M sagen das es das Update starten möge
        if ($Var1 == 'available'){
// Konfiguration der Z2M Instanz laden um an den Topic von dem Gerät zu kommen            
            $config = json_decode(IPS_GetConfiguration($Instanz), true);
// Hier die ID der Variablen vom erstellten MQTT-Klienten eintragen
            RequestAction(<ID von MQTT-Klienten Variable eintragen>, $config["MQTTTopic"]);
        }
// Kein Update mehr verfügbar, d.h. Update-Routine wurde beendet
        if ($Var1 == 'idle'){
// Ins Logfile eintragen            
            IPS_LogMessage("Update Ende", IPS_GetName($Instanz));
// Trigger deaktivieren
            $Trigger = GetValue($TriggerID);
            IPS_SetEventActive($Trigger, false);
            SetValue($InstanzID, 0);
// Nach 2 Minuten neu auf Updates prüfen
            IPS_SetScriptTimer($_IPS['SELF'], 120);
        }
        break;
// Wenn Script in Console oder per Timer gestartet wurde
    case "Execute":
    case "TimerEvent":
// Alle Zigbee2MQTT Instanzen in ein Feld einlesen
        $InstanzIDsList = IPS_GetInstanceListByModuleID('{E5BB36C6-A70B-EB23-3716-9151A09AC8A2}');  // Zigbee2MQTT
// alle Z2M Instanzen untersuchen
        foreach ($InstanzIDsList as $Instanz) {
// Gibt es Status-Variable
            $VarID1 = @IPS_GetObjectIDByIdent('Z2M_UpdateStatus', $Instanz);
// ID von LQI ermitteln für den Trigger
            $VarID2 = @IPS_GetObjectIDByIdent('Z2M_Linkquality', $Instanz);
// Status-Variable vorhanden
            if ($VarID1 > 0){
                $Var1 = GetValue($VarID1);
// Gibt es Update für dieses Gerät?
                if ($Var1 == 'available'){
// Update Start ins Logfile eintragen
                    IPS_LogMessage("Update Start", IPS_GetName($Instanz));
// Jetzt nicht mehr per Timer sondern per Trigger aufrufen
                    IPS_SetScriptTimer($_IPS['SELF'], 0);
// aktuelle Instanz merken
                    SetValue($InstanzID, $Instanz);
// Gibt es schon Trigger?
                    $Trigger = GetValue($TriggerID);
// Nein
                    if ($Trigger == 0){
// Trigger einrichten und ID für später merken
                        $Trigger = IPS_CreateEvent(0);
                        IPS_SetParent($Trigger, $_IPS['SELF']);
                        IPS_SetEventAction($Trigger, "{7938A5A2-0981-5FE0-BE6C-8AA610D654EB}",[]);
                        IPS_SetEventActive($Trigger, false);
                        SetValue($TriggerID, $Trigger);
                    }
// Trigger auf LQI von aktueller Instanz setzen
                    IPS_SetEventTrigger($Trigger, 0, $VarID2);
// Trigger aktivieren                    
                    IPS_SetEventActive($Trigger, true);
                    return;
                }
            }
        }
// Wenn es keine Updates mehr gibt Script nur einmal pro Woche aufrufen        
        IPS_SetScriptTimer($_IPS['SELF'], 7 * 24 * 3600);
        break;
}
?>

Wenn das Script einmal gestartet wurde läuft es im Prinzip ewig und sorgt immer für aktuelle Firmware. Möchte man es anhalten muss das Trigger-Ereignis und der Script-Timer deaktiviert werden.

Zu den Updates gibt es noch einiges zu sagen:

  1. Bei Lampen und Steckdosen ist es unkompliziert da die Geräte immer auch Befehle warten und sich auch oft melden. Oft startet das Update bei der ersten Aktualisierung von LQI. Möchte man es erzwingen einfach Steckdose oder Lampe schalten und auf den Update Status achten. Es kann einige Sekunden/Minuten dauern bis updating auch gemeldet wird. Bei Lampen/Steckdosen dauert ein Update meistens weniger als 1 Stunde.
  2. Bei Sensoren die sich oft melden wie Bewegung- oder Helligkeitssensoren ist es ähnlich unkompliziert wie bei den Lampen. Update dauert hier aber deutlich länger im Stundenbereich.
  3. Bei Aktoren die sich nur sehr selten von alleine melden wie z.B. Schalter sollte man etwas nachhelfen indem man eine Taste drückt und dann wartet ob sich der Update Status ändert. Bei meinen Tadfri-Schalter (Ikea) reichte oft ein einziger Tastendruck.
  4. Das ein Update beendet wurde heißt leider noch nicht das es auch erfolgreich war. Gerade bei batteriebetriebenen Sensoren kann es öfter vorkommen das es nicht auf Anhieb klappt. In diesem Fall hilft es den Sensor/Aktor in die Nähe des Koordinators zu stellen wenn möglich. Wenn mir jemand das Payload am Ende eines erfolgreichen Updates schicken könnte würde ich es in das Script einbauen so das man sofort erkennen kann ob es erfolgreich war.

Selbst wenn man das Script nicht benutzen möchte ist es hilfreich. In IPS sieht man wo ein Update verfügbar ist und kann dann gezielt das Update in der GUI von Z2M starten.

Frohe Weihnacht Ralf

1 „Gefällt mir“

Vielen Dank für das nette Gift.

Wünsche ein frohes Weihnachtsfest

Moin,
eines habe ich noch vergessen. Falls ihr Z2M schon lange benutzt und auch OTA Updates verwendet habt steht bei Ikea vielleicht noch „test url benutzen“ das sollte deaktiviert werden denn dort liegen jetzt ältere Firmware Versionen.

Ralf

Moin aus Langwedel,
für das Modul Zigbee2MQTT gibt es mittlerweile ja auch das hilfreiche und sinnvolle Instanz „Zigbee2MQTT Bridge“. Ist diese Bridge Instanz installiert kann man es sich sparen einen MQTT-Klienten anzulegen und statt dessen folgendes Script für das AutoUpdate zu benutzen:

<?php
// Liste von ID die ignoriert werden sollen. Sinnvoll wenn  ein Update mehrfach fehlschlägt. 
// Bei mir waren es 2 Tadfri Dimmer
$ignoreIDs = [29432, 12310];

// Variable für die Instanz-ID für ein Update anlegen wenn nicht vorhanden
$InstanzID = @IPS_GetVariableIDByName('InstanzID', $_IPS['SELF']);
if($InstanzID === false)
{
    $InstanzID = IPS_CreateVariable(1);
    IPS_SetName($InstanzID, 'InstanzID');
    IPS_SetParent($InstanzID, $_IPS['SELF']);
    SetValue($InstanzID, 0);
}

// Trigger-Event anlegen wenn nicht vorhanden
$TriggerID = @IPS_GetVariableIDByName('TriggerID', $_IPS['SELF']);
if($TriggerID === false)
{
    $TriggerID = IPS_CreateVariable(1);
    IPS_SetName($TriggerID, 'TriggerID');
    IPS_SetParent($TriggerID, $_IPS['SELF']);
    SetValue($TriggerID, 0);
}

// Aktuelle ID einer Instanz laden für die ein Update gemacht werden soll
$Instanz = GetValue($InstanzID);
switch ($_IPS['SENDER']){
// Empfangsqualität der zu aktualisierenden Instanz hat sich geändert
    case "Variable":
// ID der Bridge-Instanz suchen
        $BridgeIDsList = IPS_GetInstanceListByModuleID('{00160D82-9E2F-D1BD-6D0B-952F945332C5}');  // ID Zigbee2MQTT Bridge normal gibt es nur eine Instanz
        $BridgeID = $BridgeIDsList[0];
// Status vom Update
        $VarID1 = @IPS_GetObjectIDByIdent('Z2M_UpdateStatus', $Instanz);
        $Var1 = GetValue($VarID1);
// Update ist verfügbar also Update anstoßen
        if ($Var1 == 'available'){
            $config = json_decode(IPS_GetConfiguration($Instanz), true);
            Z2M_PerformOTAUpdate($BridgeID, $config["MQTTTopic"]);
        }
// Update beendet
        if ($Var1 == 'idle'){
            IPS_LogMessage("Update Ende", IPS_GetName($Instanz));
            $Trigger = GetValue($TriggerID);
            IPS_SetEventActive($Trigger, false);
            SetValue($InstanzID, 0);
            IPS_SetScriptTimer($_IPS['SELF'], 120);
        }
        break;
// Script wurde gestartet
    case "Execute":
// Script wurde durch Timer gestartet
    case "TimerEvent":
// Alle Zigbee2MQTT Instanzen in ein Feld einlesen
        $InstanzIDsList = IPS_GetInstanceListByModuleID('{E5BB36C6-A70B-EB23-3716-9151A09AC8A2}');  // alle Zigbee2MQTT Instanzen untersuchen
        foreach ($InstanzIDsList as $Instanz) {
// Wenn Instanz ignoriert werden soll weiter mit nächsten Element in der Liste weiter machen
            if (in_array($Instanz, $ignoreIDs)) {
                continue;
            }
// Gibt es Status-Variable
            $VarID1 = @IPS_GetObjectIDByIdent('Z2M_UpdateStatus', $Instanz);
            $VarID2 = @IPS_GetObjectIDByIdent('Z2M_Linkquality', $Instanz);
            if ($VarID1 > 0){
                $Var1 = GetValue($VarID1);
// Update verfügbar also Trigger-Event auf Empfangsqualität dieser Instanz setzen
                if ($Var1 == 'available'){
                    IPS_LogMessage("Update Start", IPS_GetName($Instanz));
                    IPS_SetScriptTimer($_IPS['SELF'], 0);
                    SetValue($InstanzID, $Instanz);
                    $Trigger = GetValue($TriggerID);
// Trigger gibt es noch nicht also anlegen
                    if ($Trigger == 0){
                        $Trigger = IPS_CreateEvent(0);
                        IPS_SetParent($Trigger, $_IPS['SELF']);
                        IPS_SetEventAction($Trigger, "{7938A5A2-0981-5FE0-BE6C-8AA610D654EB}",[]);
                        IPS_SetEventActive($Trigger, false);
                        SetValue($TriggerID, $Trigger);
                    }
// Quelle für Trigger setzen
                    IPS_SetEventTrigger($Trigger, 0, $VarID2);
                    IPS_SetEventActive($Trigger, true);
                    return;
                }
            }
        }
// Wenn es kein Update mehr gibt 1 Woche warten und dann wieder suchen
        IPS_SetScriptTimer($_IPS['SELF'], 7 * 24 * 3600);
        break;
}
?>

Ralf

1 „Gefällt mir“

Danke dir Ralf für die Aktualisierung. Muss die Zigbee2MQTTHelper.php trotzdem bearbeitet werden?

VG
Stefan

Hi,
z.Z. noch ja. Vielleicht ändert sich das ja noch irgendwann.

Ralf

1 „Gefällt mir“

Hinweis für User mit mehreren Z2M Installationen:
Das Script funktioniert aktuell nur so lange es nur eine Z2M Installation im Symcon gibt.
Sind mehrere Z2M Installationen vorhanden, so muss hier zusätzlich auf das BaseTopic geprüft werden und die passende Bridge zum BaseTopic genutzt werden.
Michael

Hi,
stimmt. Muss ich mir mal ansehen. Danke für den Hinweis. Für mich gilt oft „Der Mensch, der mir am nächsten ist Bin ich, ich bin ein Egoist“ :rofl:

Hier das geänderte Script das mit mehreren Bridges klar kommen sollte. Getestet mit 2 Bridges.

<?php
$ignoreIDs = [29432, 12310];
$InstanzID = @IPS_GetVariableIDByName('InstanzID', $_IPS['SELF']);
if($InstanzID === false)
{
    $InstanzID = IPS_CreateVariable(1);
    IPS_SetName($InstanzID, 'InstanzID');
    IPS_SetParent($InstanzID, $_IPS['SELF']);
    SetValue($InstanzID, 0);
}
$TriggerID = @IPS_GetVariableIDByName('TriggerID', $_IPS['SELF']);
if($TriggerID === false)
{
    $TriggerID = IPS_CreateVariable(1);
    IPS_SetName($TriggerID, 'TriggerID');
    IPS_SetParent($TriggerID, $_IPS['SELF']);
    SetValue($TriggerID, 0);
}

$Instanz = GetValue($InstanzID);
switch ($_IPS['SENDER']){
    case "Variable":
        $DeviceConfig = json_decode(IPS_GetConfiguration($Instanz), true);
        $DeviceBaseTopic = $DeviceConfig["MQTTBaseTopic"];
        $BridgeIDsList = IPS_GetInstanceListByModuleID('{00160D82-9E2F-D1BD-6D0B-952F945332C5}');  // Zigbee2MQTT
        $BridgeID = 0;
        foreach ($BridgeIDsList as $BridgeIDItem){
            $BridgeConfig = json_decode(IPS_GetConfiguration($BridgeIDItem), true);
            $BridgeBaseTopic = $BridgeConfig["MQTTBaseTopic"];
            if ($DeviceBaseTopic == $BridgeBaseTopic){
                $BridgeID = $BridgeIDItem;
                break;
            }
        }
        if ($BridgeID === 0) return;
        $VarID1 = @IPS_GetObjectIDByIdent('Z2M_UpdateStatus', $Instanz);
        $Var1 = GetValue($VarID1);
        if ($Var1 == 'available'){
            $config = json_decode(IPS_GetConfiguration($Instanz), true);
            Z2M_PerformOTAUpdate($BridgeID, $config["MQTTTopic"]);
        }
        if ($Var1 == 'idle'){
            IPS_LogMessage("Update Ende", IPS_GetName($Instanz));
            $Trigger = GetValue($TriggerID);
            IPS_SetEventActive($Trigger, false);
            SetValue($InstanzID, 0);
            IPS_SetScriptTimer($_IPS['SELF'], 120);
        }
        break;
    case "Execute":
    case "TimerEvent":
// Alle Zigbee2MQTT Instanzen in ein Feld einlesen
        $InstanzIDsList = IPS_GetInstanceListByModuleID('{E5BB36C6-A70B-EB23-3716-9151A09AC8A2}');  // Zigbee2MQTT
// alle Z2M Instanzen untersuchen
        foreach ($InstanzIDsList as $Instanz) {
            if (in_array($Instanz, $ignoreIDs)) {
                continue;
            }
// Gibt es Status-Variable
            $VarID1 = @IPS_GetObjectIDByIdent('Z2M_UpdateStatus', $Instanz);
            $VarID2 = @IPS_GetObjectIDByIdent('Z2M_Linkquality', $Instanz);
            if ($VarID1 > 0){
                $Var1 = GetValue($VarID1);
                if ($Var1 == 'available'){
                    IPS_LogMessage("Update Start", IPS_GetName($Instanz));
                    IPS_SetScriptTimer($_IPS['SELF'], 0);
                    SetValue($InstanzID, $Instanz);
                    $Trigger = GetValue($TriggerID);
                    if ($Trigger == 0){
                        $Trigger = IPS_CreateEvent(0);
                        IPS_SetParent($Trigger, $_IPS['SELF']);
                        IPS_SetEventAction($Trigger, "{7938A5A2-0981-5FE0-BE6C-8AA610D654EB}",[]);
                        IPS_SetEventActive($Trigger, false);
                        SetValue($TriggerID, $Trigger);
                    }
                    IPS_SetEventTrigger($Trigger, 0, $VarID2);
                    IPS_SetEventActive($Trigger, true);
                    return;
                }
            }
        }
        IPS_SetScriptTimer($_IPS['SELF'], 7 * 24 * 3600);
        break;
}
?>

Ralf

2 „Gefällt mir“