< Tech-Blog >

SmartHome mit Sonoff Basic - günstig und trotzdem sicher

Heimautomation ohne China-Cloud mit Sonoff Basic published: 12. July 2018

SmartHome realy smart

Der Wunsch: Geräte, Steckdosen, Licht per Wifi steuern, aber: OHNE China-Cloud (China KLAUT), ohne Internet und günstig

Die Realität: Eine Wifi-Steckdose kostet im Durchschnitt um die 30,00 bis 50,00 Euro, läuft meist mit einer App die ohne Internet- und Cloudzugriff nicht funktioniert. crying

Die LÖSUNG: Sonoff Basic + Tasmonta-Firmware + Tasmoadmin,

Sonoff-Module sind kleine Schaltrelais für 230V die über Wifi steuerbar sind (An, Aus, Aktueller Schaltzustand (Status), Sensoren (je nach Sonoff-Modell)). Die Dinger kosten in der Bucht oder in Amazonien zwischen 6,00 und 12,00 Euro. (Um Stress, Zeit und Geld zu sparen würde ich nur bei Händlern mit deutschem Lagerplatz bestellen. Alles andere endet meist auf dem Zollamt mit Diskussionen, Spannung, ohne Spass und ganz ohne Spiel.)

Los geht 's

Wir kaufen ein..... (Alle Links sind nur Vorschläge ! FREIE Verkäuferwahl für ALLE und IMMER)

  1. Sonoff Basic
  2. Stifleisten
  3. USB 2 TTL Adapter

Wir benötigen zusätzlich:

  1. Feinlötkölben und Lötzinn
  2. Schrauberdreher
  3. PC mit USB-Anschluss
  4. Atom Editor + Tasmonta-Firmware
  5. optional: Tasmoadmin (entweder auf einem vorhandenen Homeserver installieren (z.B. Raspberry Pi) oder als WAMP direkt vom PC)
  6. ein klein wenig Zeit (ca. 1h bis zur ersten Inbetriebnahme)

Der Ablauf des SmartHome-Projektes:

HINWEIS: Alle Arbeiten am Sonoff IMMER OHNE NETZSPANNUNG. 230V Netzspannung kann tödlich sein! NICHT LUSTIG...

  1. Sonoff Basic vorsichtig öffnen
  2. Stiftleiste (5 Stifte) einlöten
  3. Verkabelung des USB 2 TTL Adapters mit dem Sonof Basic
  4. Atom downloaden und installieren
  5. Tasmota Firmware downloaden und mit Atom Editor öffnen
  6. Tasmota Firmware im Atom Editor anpassen ( wichtige Datei: Atom -> Projekt ->sonoff -> unser_config.h: Wifi, Zeitzone und Sprache konfigurieren,...)
  7. Tasmota kompilieren und auf das Sonoff Relais packen.
  8. Sonoff rebooten
  9. Mit dem Browser auf Sonoff zugreifen und steuern.

HowTos und Anleitungen gibt es im Internet ohne Ende:

HowTo 1, HowTo 2, VideoHowTo 1, VideoHowTo 2

 

Kompliziert ist das alles nicht... ABER es gibt Stolperstellen:

  1. Atom und der USB-Adapter... da gibts bei der Verbindung zum Sonoff immer wieder Probleme. Meist hilft nur: Atom schliessen, Adapter neu einstecken, Atom öffnen
  2. Um das Sonoff-Teil mit der neuen Firmware beschreiben zu können muss das Gerät auf "Empfang" stehen. Dazu muss im ausgeschalteten (vom USB-Bus getrennten) Zustand der schwarze Schalterstift auf dem Sonoff gedrückt und gehalten werden, gleichzeitig den USB-Adapter am PC einstecken und schon funktioniert die Beschreiberei.
  3. Tasmoadmin: Sollte entweder als WAMP-Installation betrieben werden, oder auf irgendeinem Apachen im Netzwerk. NGINX ist nicht sachdienlich, da Tasmoadmin .htacess mit rewrites und redirects verwendet.... das kann der auf Performance ausgelegte NGINX nicht.

 

Tasmoadmin Übersichtsseite:

 

 

 

 

 

 

 

 

 

 

 

 

 

Geräte-Konfigurationsseite:

 

 

 

 

 

 

 

 

 

 

 

 

Der Code für den ganzen Spass:

in Datei: /resources/js/start.js

gleich am Anfang der Datei in die Funktion

$( document ).on( "ready", function () {
    deviceTools();
    updateStatus();
   ....

  /* hier einfügen: */
    scanForTurnoff();
    createSetButton();

});

...direkt im Anschluss:

/* LX START */

newHTML2 = [];

function turnofflx() {
    device_ips.forEach(function(item) {
        getbuffer = '';
        $.get("http://" + item + "/cm?cmnd=Power%20Off", function(data) {
            $(getbuffer).html(data);
        });
    });
};

$(document).on("click", "#alloff", function() {
    turnofflx();
});

function scanForTurnoff() {
    device_ips = [];
    device_sets = [];
    device_setsshow = [];
    i2 = 1;
    $('.card').each(function() {
        i2++;
        var temp = $(this).data('device_set').toString();
        if ((temp != '')) {
            var count = (temp.match(/\|/g) || []).length;
        } else {}
        if ((count >= 1) || (temp != '')) {
            count++;
            for (var i = 0; i < count; i++) {
                device_sets.push(temp.split("|")[i] + '|' + $(this).data('device_ip') + '|' + $(this).data('device_id') + '|' + $(this).data('device_relais') + '|' + $(this).data('device_itemname'));
                device_setsshow.push("<div id='div" + i2 + i + "' class='groupsquare' style='width:10px; height: 10px; margin-left: 1px; top: 1px;position: relative;float: left;border-radius: 5px;overflow: hidden;'><div style='display:none'>" + temp.split("|")[i] + "</div></div>");
            }
            $(this).prepend("<div style='width: 100%;position: absolute;opacity: 1;hidden:z-index: 10000'>" + device_setsshow.join(''));
            device_setsshow = [];
            device_ips.push($(this).data('device_ip'));
        } else {
            device_ips.push($(this).data('device_ip'));
            device_sets.push($(this).data('device_set') + '|' + $(this).data('device_ip') + '|' + $(this).data('device_id') + '|' + $(this).data('device_relais') + '|' + $(this).data('device_itemname'));
        }
    });
    if (device_ips.length === 0) {} else {
        newHTML2.push('<button id="alloff" class="btn btn-secondary" style="background:#fd6906"><b>ALL OFF</b></button>');
    };
};

function createSetButton() {
    device_sets.sort();
    //console.table(device_sets);
    var setcheck = '';
    var devsetparts = '';
    var setcheck2 = device_sets[0].split("|")[0];
    coloradd = 0;
    device_sets.forEach(function(item) {
        coloraddr = Math.floor(Math.random() * 155);
        coloraddg = Math.floor(Math.random() * 155);
        coloraddb = Math.floor(Math.random() * 155);
        coloradd = coloradd + 6;

        var devip = item.split("|")[1];
        var devset = item.split("|")[0];
        var devsetpart = item.split("|")[4];

        var rand = '#' + Math.floor(Math.random() * 16777215).toString(16);

        var hue = Math.floor(Math.random() * 300),
            saturation = Math.floor(Math.random() * 80) + 30,
            lightness = Math.floor(Math.random() * 80) + 20,
            //rand = "hsl(" + hue + ", " + saturation + "%, " + lightness + "%)";
            //rand = "rgb("+(80+coloraddr)+","+(200-coloraddg)+","+(coloraddb+coloradd+coloradd-250)+")"; //random
            //rand = "rgb("+coloraddr+","+coloraddg+","+coloraddb+")"; //random
            //rand = "rgb("+(55-coloradd/2)+","+(coloradd+3*coloradd)+","+(255-coloradd*2)+")"; //blue to green
            //rand = "rgb("+(255-coloradd*3)+","+(coloradd*2)+","+(coloradd*4.5)+")"; //red to blue
            //rand = "rgb("+(coloradd+40)+","+(155-coloradd*1.5)+","+(coloradd*2.5)+")";
            //rand = "rgb("+coloradd*0.2+","+coloradd*2.5+","+coloradd*1+")"; //black to green
            //rand = "rgb("+(200-coloradd*2)+","+coloradd*1.5+","+coloradd*1+")"; //red to green
            //rand = "rgb("+coloradd+","+coloradd+","+coloradd+")"; //greyscale
            //rand = "rgb(" + coloradd + "," + (coloradd * 2) + "," + (coloradd * 0.5) + ")"; //black to green
            //rand = "rgb("+coloradd+","+coloradd+","+(coloradd*4)+")"; //black to blue
            //rand = "rgb(10,"+(coloradd*2)+","+(coloradd*2.5)+")"; // patrol
            //rand = "rgb(80,100,"+(coloradd*2)+")"; // green to blue light
            //rand = "rgb(" + coloraddr + "," + coloraddg + "," + coloraddb + ")"; //random

            //colors based on set-name START
            rand = "";
        devsetinter = devset;
        if (devset.length < 3) {
            devsetinter += "1z";
        }
        for (var i = 0; i < 3; i++) {
            rand += Math.round((devsetinter.charCodeAt(i)) * 1.2) + ","; // greater multiplicator -> lighter color
        }
        rand = "rgb(" + rand.slice(0, -1) + ")";
        //colors based on set-name END            

        if (devset != '') {
            if (devset != setcheck) {
                setcheck = devset;
                devsetparts = devsetpart + '';
                newHTML2.push('<button class="setsoff btn btn-secondary" style="margin-top: 12px; background:' + rand + '">Toggle Set: ' + devset + " - [ " + devsetparts + ' ]</button>');
                //alert(rand);
                $(".groupsquare:contains('" + devset + "')").css("background", rand);
                $(".groupsquare:contains('" + devset + "')").css("color", "#fff");
                $(".groupsquare:contains('" + devset + "')").css("font-size", "0.7em");
                rand = "";

            } else {
                newHTML2.pop();
                devsetparts += ', ' + devsetpart + ' ';
                newHTML2.push('<button class="setsoff btn btn-secondary" style="margin-top: 12px; background:' + rand + '">Toggle Set: ' + devset + " - [ " + devsetparts + ' ]-</button>');
                $(".groupsquare:contains('" + devset + "')").css("background", rand);
                rand = "";
            }
        }
    });
    $("#settoggler").html(newHTML2.join(""));
}

function turnofflxset(settoturnoff) {
    device_sets.sort();
    newHTML2 = [];
    device_sets.forEach(function(item) {
        getsetsbuffer = '';
        var devset = item.split("|")[0];
        var devip = item.split("|")[1];
        var devid = item.split("|")[2];
        var devrelais = item.split("|")[3];
        if (devset === settoturnoff) {
            Sonoff.toggle(devip, devid, devrelais);
            var devip = "";
            /*
    $.get( "http://" + devip + "/cm?cmnd=Power%20Off", function( data ) {
    $( getsetsbuffer ).html( data );
    });
    */
        }
    });
};

$(document).on("click", ".setsoff", function() {
    var settoturnoffin = $(this).text();
    $(".setsoff").css("border", "0px solid red");
    $(this).css("border", "2px dashed #30bd1f");
    settoturnoff = settoturnoffin.split(" ")[2];
    turnofflxset(settoturnoff);
    settoturnoff = '';
});

/* LX END */

 

in Datei: /pages/start.php

ungefähr ab Zeile 15:

Den Abschnitt:

<div class='card box_device position-relative' style=''
                             data-device_id='<?php echo $device_group->id; ?>'
                             data-device_group='<?php echo count( $device_group->names ) > 1 ? "multi" : "single"; ?>'
                             data-device_ip='<?php echo $device_group->ip; ?>'
                             data-device_relais='<?php echo $key + 1; ?>'
                        >

ersetzen durch:

<div class='card box_device position-relative' style=''
                             data-device_id='<?php echo $device_group->id; ?>'
                             data-device_group='<?php echo count( $device_group->names ) > 1 ? "multi" : "single"; ?>'
                             data-device_ip='<?php echo $device_group->ip; ?>'
                             data-device_set='<?php echo $device_group->set; ?>'
                             data-device_itemname='<?php echo $devicename; ?>'
                             data-device_relais='<?php echo $key + 1; ?>'
                        >


ungefähr bei Zeile 40:

den Abschnitt

<?php endforeach;
            endforeach; ?>

ersetzen durch:

<?php endforeach;
            endforeach; ?>
        <div id="settoggler" style="width: 100%"></div>

in Datei: /pages/device_action.php

ungefähr bei Zeile 67

den Abschnitt:

$device      = [];
            $device[ 0 ] = $_REQUEST[ "device_id" ];
            $device[ 1 ] = implode( "|", $_REQUEST[ "device_name" ] );
            $device[ 2 ] = $_REQUEST[ "device_ip" ];
            $device[ 3 ] = $_REQUEST[ "device_username" ];
            $device[ 4 ] = $_REQUEST[ "device_password" ];
            $device[ 5 ] = isset( $_REQUEST[ "device_img" ] ) ? $_REQUEST[ "device_img" ] : "bulb_1";
            $device[ 6 ] = $_REQUEST[ "device_position" ];

ersetzen durch:

$device      = [];
            $device[ 0 ] = $_REQUEST[ "device_id" ];
            $device[ 1 ] = implode( "|", $_REQUEST[ "device_name" ] );
            $device[ 2 ] = $_REQUEST[ "device_ip" ];
            $device[ 3 ] = $_REQUEST[ "device_username" ];
            $device[ 4 ] = $_REQUEST[ "device_password" ];
            $device[ 5 ] = isset( $_REQUEST[ "device_img" ] ) ? $_REQUEST[ "device_img" ] : "bulb_1";
            $device[ 6 ] = $_REQUEST[ "device_position" ];
            $device[ 7 ] = $_REQUEST[ "device_set" ];

ungefähr bei Zeile 104

den Abschnitt:

$device      = [];
            $fp          = file( $filename );
            $device[ 0 ] = count( $fp )+1;
            $device[ 1 ] = implode( "|", isset( $_REQUEST[ "device_name" ] ) ? $_REQUEST[ "device_name" ] : [] );
            $device[ 2 ] = isset( $_REQUEST[ "device_ip" ] ) ? $_REQUEST[ "device_ip" ] : "";
            $device[ 3 ] = isset( $_REQUEST[ "device_username" ] ) ? $_REQUEST[ "device_username" ] : "";
            $device[ 4 ] = isset( $_REQUEST[ "device_password" ] ) ? $_REQUEST[ "device_password" ] : "";
            $device[ 5 ] = isset( $_REQUEST[ "device_img" ] ) ? $_REQUEST[ "device_img" ] : "bulb_1";
            $device[ 6 ] = isset( $_REQUEST[ "device_position" ] ) ? $_REQUEST[ "device_position" ] : "";

 

ersetzen durch:

$device      = [];
            $fp          = file( $filename );
            $device[ 0 ] = count( $fp )+1;
            $device[ 1 ] = implode( "|", isset( $_REQUEST[ "device_name" ] ) ? $_REQUEST[ "device_name" ] : [] );
            $device[ 2 ] = isset( $_REQUEST[ "device_ip" ] ) ? $_REQUEST[ "device_ip" ] : "";
            $device[ 3 ] = isset( $_REQUEST[ "device_username" ] ) ? $_REQUEST[ "device_username" ] : "";
            $device[ 4 ] = isset( $_REQUEST[ "device_password" ] ) ? $_REQUEST[ "device_password" ] : "";
            $device[ 5 ] = isset( $_REQUEST[ "device_img" ] ) ? $_REQUEST[ "device_img" ] : "bulb_1";
            $device[ 6 ] = isset( $_REQUEST[ "device_position" ] ) ? $_REQUEST[ "device_position" ] : "";
            $device[ 7 ] = isset( $_REQUEST[ "device_set" ] ) ? $_REQUEST[ "device_set" ] : "";

 

ungefähr bei Zeile 297

den Abschnitt:

<small id="device_nameHelp" class="form-text text-muted d-none d-sm-block">
                                        &nbsp;
                                    </small>
                                </div>

ersetzen durch:

<small id="device_nameHelp" class="form-text text-muted d-none d-sm-block">
                                        &nbsp;
                                    </small>
                                </div>                                
                                <div class="form-group col-12 col-sm-9">
                                    <label for="device_img">
                                        <?php echo __( "LABEL_IMG", "DEVICE_ACTIONS" ); ?>
                                    </label>
                                    <input type="text"
                                           class="form-control"
                                           id="device_img"
                                           name='device_img'
                                           placeholder="<?php echo __( "PLEASE_ENTER" ); ?>"
                                           value='<?php echo( isset( $device->img )
                                                      && !isset( $_REQUEST[ 'device_img' ] )
                                       ? $device->img : ( isset( $_REQUEST[ 'device_img' ] )
                                           ? $_REQUEST[ 'device_img' ]
                                           : "" ) ); ?>'
                                           ><!--required-->
                                    <small id="device_nameHelp" class="form-text text-muted d-none d-sm-block">
                                        &nbsp;
                                    </small>
                                </div>
                                
                                <div class="form-group col-12 col-sm-9">
                                    <label for="device_set">
                                        <?php echo __( "LABEL_SET", "DEVICE_ACTIONS" ); ?>
                                    </label>
                                    <input type="text"
                                           class="form-control"
                                           id="device_set"
                                           name='device_set'
                                           placeholder="<?php echo __( "PLEASE_ENTER" ); ?>"
                                           value='<?php echo( isset( $device->set )
                                                      && !isset( $_REQUEST[ 'device_set' ] )
                                       ? $device->set : ( isset( $_REQUEST[ 'device_set' ] )
                                           ? $_REQUEST[ 'device_set' ]
                                           : "" ) ); ?>'
                                           ><!--required-->
                                    <small id="device_nameHelp" class="form-text text-muted d-none d-sm-block">
                                        &nbsp;
                                    </small>
                                </div>

 

FERTIG. Jetzt können Gerätegruppen angelegt werden und mit EINEM Button getogglet (an-, abgeschaltet) werden.

Eingabebeispiel für Feld "Geräte-Setname": Test1|Test2|Test3 usw...
Die Farben der Buttons und Punkte (über dem Gerätebild) werden aus den ersten 3 Buchstaben des Set-Namens generiert und sind somit immer gleich. Andere Farbsets und Zufallsfarben können durch auskommentieren aktiviert werden. Der Abschnitt  //colors based on set-name START bis //colors based on set-name END muss dafür deaktiviert werden.

Beispiel für patrolfarbenes Set:

//rand = "hsl(" + hue + ", " + saturation + "%, " + lightness + "%)";
            //rand = "rgb("+(80+coloraddr)+","+(200-coloraddg)+","+(coloraddb+coloradd+coloradd-250)+")"; //random
            //rand = "rgb("+coloraddr+","+coloraddg+","+coloraddb+")"; //random
            //rand = "rgb("+(55-coloradd/2)+","+(coloradd+3*coloradd)+","+(255-coloradd*2)+")"; //blue to green
            //rand = "rgb("+(255-coloradd*3)+","+(coloradd*2)+","+(coloradd*4.5)+")"; //red to blue
            //rand = "rgb("+(coloradd+40)+","+(155-coloradd*1.5)+","+(coloradd*2.5)+")";
            //rand = "rgb("+coloradd*0.2+","+coloradd*2.5+","+coloradd*1+")"; //black to green
            //rand = "rgb("+(200-coloradd*2)+","+coloradd*1.5+","+coloradd*1+")"; //red to green
            //rand = "rgb("+coloradd+","+coloradd+","+coloradd+")"; //greyscale
            //rand = "rgb(" + coloradd + "," + (coloradd * 2) + "," + (coloradd * 0.5) + ")"; //black to green
            //rand = "rgb("+coloradd+","+coloradd+","+(coloradd*4)+")"; //black to blue
            rand = "rgb(10,"+(coloradd*2)+","+(coloradd*2.5)+")"; // patrol
            //rand = "rgb(80,100,"+(coloradd*2)+")"; // green to blue light
            //rand = "rgb(" + coloraddr + "," + coloraddg + "," + coloraddb + ")"; //random

            //colors based on set-name START

/*
            rand = "";
        devsetinter = devset;
        if (devset.length < 3) {
            devsetinter += "1z";
        }
        for (var i = 0; i < 3; i++) {
            rand += Math.round((devsetinter.charCodeAt(i)) * 1.2) + ","; // greater multiplicator -> lighter color
        }
        rand = "rgb(" + rand.slice(0, -1) + ")";

*/
        //colors based on set-name END

 

Um ein Gerätebild zu hinterlegen müssen 2 PNG-Dateien im Verzeichnis /resources/img/device_icons/ hinterlegt werden:

Grösse: 256x256 Pixel
Name: geraetename_off.png und geratename_on.png
In Tasmoadmin auf der Gerätekonfigurations-Seite im Feld "Geräte-Bild" dann "geraetename" (Dateiname der Bilddatei OHNE "_on.png" und "_off.png" !) eintragen und fertig ist die Laube.

Viel Spass