Funksteckdosen über das Web mit dem Raspberry Pi schalten

Im letzten Artikel habe ich gezeigt, wie man Funksteckdosen mit dem Raspberry Pi und etwas Zusatz-Hardware über die Kommandozeile schalten kann.  Dieser Aufbau soll nun um ein Webinterface erweitert werden um die Steckdosen mit dem Smartphone bequem schalten zu können.

Als Webserver soll Node.js zum Einsatz kommen. Die Installation auf dem Raspberry Pi habe ich dazu ja bereits beschrieben. Zum schalten wird das Kommandozeilenprogramm pilight von CurlyMoo benutzt.

1. Node.js-Modul zum Aufruf des Kommandos aus Javascript

Die Datei outlets.js bilde eine Node.js-Modul zum schalten der Steckdosen, das dann später in unserer Hauptdatei benutzt werden kann:

Nachtrag vom 28. Oktober 2013: Die Datei ist nun auf das Schalten der Funksteckdosen mit pilight angepasst.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* Outlet-Module */

var spawn = require("child_process").spawn;

//Helper-Function
function isInt(x) {
    var y=parseInt(x);
    if (isNaN(y)) return false;
    return x==y && x.toString()==y.toString();
}

/*
* Turns a outlet-unit on or off.
*/

exports.send = function(unit, state, resultCallbak) {
    if(!isInt(unit)){
        throw new Error(unit + " is not a valid unit id.");
    }

    var u = "-u " + unit;
    var onOff = (state ? "-t" : "-f");

    var child = spawn("pilight-send", ["-p kaku_switch", u, "-i 4762303", onOff]);

    child.on("exit",function(code) {
        var success = (code == 0);
        resultCallbak(success);
    });

};

Eventuell muss beim spawn-Befehl noch das Protokoll (hier: kaku_switch) angepasst werden.

2. REST-Schnittstelle für das schalten der Steckdosen

Zur einfachen Programmierung der REST-Schnittstelle wird die Node.js-Middleware connect und urlrouter benötigt. Diese beiden Module lassen sich einfach über den Paketmanager npm von Node.js installieren:

npm install connect urlrouter

Die eigentliche Schnittstelle lässt sich dann ganz bequem schreiben (Bei mir die main.js.) als :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var connect = require('connect');
var urlrouter = require('urlrouter');
var http = require("http");

var outlets = require('./outlets.js');

var app = connect();

//Logger:
app.use(connect.logger('dev'));

app.use(urlrouter(function (app) {
  app.get('/outlets/send/:id/:onoff', function (req, res, next) {
    var id = parseInt(req.params.id);
    var onOff = req.params.onoff == 'on' ? true : false;

    if(!isNaN(id) && (req.params.onoff == 'on' || req.params.onoff == 'off')){
      outlets.send(id, onOff, function(success){
        res.setHeader("Content-Type","text/plain");
        res.writeHead(200);
        res.end(""+success);
      });
    }else{
      res.writeHead(400);
      res.end('Bad Request');
    }
  });
}));

app.use(connect.static('public'));

http.createServer(app).listen(80);

Nun kann zum Beispiel die erste Steckdose über folgenden Aufruf eingeschaltet werden (Neustart von Node.js nicht vergessen!):

http://ip-des-raspberry-pi/outlets/send/0/on

3. Optionale Authentifizierung:

Soll der Zugriff auf das Webinterface nur per Passwort möglich sein,  kann noch folgender Code in der outlets.js ergänzt und Benutzername und Password entsprechend angepasst werden. Falls das Webinterface aus dem Internet erreichbar sein soll, sollte man dann aber den Server auf https umstellen, da sonst jeder den Benutzernamen und das Passwort einfach mitlesen kann, wenn er die Daten abfängt.

1
2
3
app.use(connect.basicAuth(function(user, pass){
    return user == 'admin' && pass == 'admin';
}));

4. GUI

Alle Dateien im Ordner public werden durch die Zeile app.use(connect.static('public')); statisch ausgeliefert. Mit Hilfe von jQuery mobile ist schnell ein kleines Interface gebaut. Die Datei public/index.html sieht bei mir folgendermaßen aus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="apple-mobile-web-app-status-bar-style" content="black" />
        <title>piControl</title>
        <link rel="stylesheet" href="https://s3.amazonaws.com/codiqa-cdn/mobile/1.2.0/jquery.mobile-1.2.0.min.css" />
        <script src="https://s3.amazonaws.com/codiqa-cdn/jquery-1.7.2.min.js"></script>
        <script src="https://s3.amazonaws.com/codiqa-cdn/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>
        <script src="picontrol.js"></script>
    </head>
    <body>
        <div data-role="page" id="outlet-control">
            <div data-theme="a" data-role="header">
                <h3>
                    PiControl
                </h3>
            </div>
            <div data-role="content">
                <h3>Steckdose 1</h3>
                <div data-role="controlgroup" data-type="horizontal">
                    <a id="off0" data-role="button" href="#outlet-control" data-icon="minus" data-iconpos="left">Aus</a>
                    <a id="on0" data-role="button" href="#outlet-control" data-icon="plus" data-iconpos="left">An</a>
                </div>
                <h3>Steckdose 2</h3>
                <div data-role="controlgroup" data-type="horizontal">
                    <a id="off1" data-role="button" href="#outlet-control" data-icon="minus" data-iconpos="left">Aus</a>
                    <a id="on1" data-role="button" href="#outlet-control" data-icon="plus" data-iconpos="left">An</a>
                </div>
                <h3>Steckdose 3</h3>
                <div data-role="controlgroup" data-type="horizontal">
                    <a id="off2" data-role="button" href="#outlet-control" data-icon="minus" data-iconpos="left">Aus</a>
                    <a id="on2" data-role="button" href="#outlet-control" data-icon="plus" data-iconpos="left">An</a>
                </div>
            </div>
        </div>
    </body>
</html>

Die Logik übernimmt die public/picontrol.js mit folgender Javascript-Code, welcher die REST-Schnittstelle benutzt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$(document).on('pageinit', function(a){

    for(var i = 0; i < 3; i++){
        (function(num){
            $('#on'+num).bind( 'vclick', function(event, ui) {
                $.get('/outlets/send/'+num+'/on', function(data) {});
            });

            $('#off'+num).bind( 'vclick', function(event, ui) {
                $.get('/outlets/send/'+num+'/off', function(data) {});
            });
        })(i);
    }

    $.ajaxSetup({
        timeout: 7000/*ms*/
    });

    $(document).ajaxStart(function() {
        $.mobile.loading("show",{
            text: 'Lade...',
            textVisible: true,
            textonly: false
        });
    });

    $(document).ajaxStop(function() {
        $.mobile.loading("hide");
    });

    $(document).ajaxError(function(event, jqxhr, settings, exception) {
         var error;
         if(exception){
            error = 'Fehler: ' + exception;
         }else{
            error = 'Ein Fehler ist aufgetreten.';
         }
         alert(error);
    });

});

Viel Spaß beim herum experimentieren! Bei Fragen einfach einen Kommentar schreiben. Es könnte ja auch noch Andere interessieren.

 

17 Antworten

  1. Danke für dieses mehrteilige Tutorial. Habe es mit einem Kollegen nachgebaut und es funktioniert sehr fein.

  2. Vielen Dank für diese tollen Tutorials!!!
    Ich habe versucht soweit alles aufzubauen und zu verstehen. Ich kann mittlerweile über das Terminal mit dem Befehl „pilight-send -p kaku_switch -i 4762303 -u 0 -t“ oder -f die Schalter schalten (bei mir Rolladen). Node.js ist erfolgreich aufgesetzt und auch das Webinterface lädt ohne Probleme. Wie kann ich nun die Verschiedenen Units und IDs ansprechen? Leider scheitert es da an wohl fehlenden Javascript-Kenntnissen. Hast Du da noch einen Tipp parat?

    • Hallo Sebastian,

      Du musst Zeile 23 in der outlets.js anpassen. Aus:

      1
      var child = spawn("./send", [u, "-i 4762303", onOff], {

      wird:

      1
      var child = spawn("pilight-send", [u, "-i 4762303", onOff, "-p kaku_switch"], {

      Es hat sich aber auch einiges geändert, seit dem ich die Anleitung geschrieben habe. pilight bringt nun schon ein eigenes Webinterface mit, dass über „ip-deines-pis:5001“ erreichbar ist, wenn der pilight-daemon läuft. Du musst dazu aber noch eine weitere config-Datei erstellen.

      Es wäre super, wenn du mir die Produktbezeichnung deiner Rolläden geben könntest. Ich arbeite gerade an einer Liste mit kompatiblen Geräten.

  3. Hallo,
    ich bräuchte Hilfe für ein wahrscheinlich triviales Problem. Mit den beiden Kommandos:

    1
    2
    pi@rip-raspi2 ~/pilight $ pilight-send -p elro -i 2 -u 1 -t
    pi@rip-raspi2 ~/pilight $ pilight-send -p elro -i 2 -u 1 -f

    kann ich eine meiner Funksteckdosen schalten.
    Jetzt will ich das auch per Nodes.js erreichen, outlets.js Zeile 23-25 hab ich so eingestellt:

    1
    2
    3
        var child = spawn("./pilight-send", [u, "-i 2", onOff, "-p elro"], {
            cwd : "/home/pi/pilight",
            env : { HOME : "/home/pi/"}

    Da tut sich leider nichts. Im node.log steht:

    1
    2
    3
    4
    GET /outlets/send/1/on 200 42ms
    GET /outlets/send/1/off 200 27ms
    GET /outlets/send/2/on 200 25ms
    GET /outlets/send/2/off 200 22ms

    Je nachdem ob ich Steckdose 2 oder 3 schalte.
    Für einen hilfreichen Tipp wäre ich sehr dankbar.

    • Hi Richard,

      leider gibt die Funktion spawn nicht die Ausgabe des übergebenen Kommandos aus, daher siehst Du keine Fehlermeldung in der Logdatei.

      Ergänze bitte mal noch folgendes nach dem „spawn“ Befehl:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      child.stdout.on('data', function (data) {
        console.log('stdout: ' + data);
      });

      child.stderr.on('data', function (data) {
        console.log('stderr: ' + data);
      });

      child.on('close', function (code) {
        console.log('child process exited with code ' + code);
      });

      und schau Dir danach den Inhalt der Logdatei an bzw. poste ihn hier, dann kann ich hoffentlich mehr sagen.

      Viele Grüße und Danke für den Kaffee!
      Oliver

  4. Hallo Oliver,
    hier der Log:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    GET /outlets/send/1/on 200 78ms
    stdout: Usage: pilight-send -p protocol [options]

    The supported protocols are:
         raw                Raw codes
         relay              Control connected relay's
         select-remote          SelectRemote Switches
         impuls             Impuls Switches
         elro               Elro Switches
         cogex              Cogex Switches
         kaku_old           Old KlikAanKlikUit Switches
         kaku_dimmer            KlikAanKlikUit Dimmers
         coco_switch            CoCo Technologies Switches
         nexa_switch            Nexa Switches
         dio_switch         D-IO (Chacon) Switches
         kaku_switch            KlikAanKlikUit Switches

    child process exited with code 0
    stdout: Usage: pilight-send -p protocol [options]

    The supported protocols are:
         raw                Raw codes
         relay              Control connected relay's
         select-remote          SelectRemote Switches
         impuls             Impuls Switches
         elro               Elro Switches
         cogex              Cogex Switches
         kaku_old           Old KlikAanKlikUit Switches
         kaku_dimmer            KlikAanKlikUit Dimmers
         coco_switch            CoCo Technologies Switches
         nexa_switch            Nexa Switches
         dio_switch         D-IO (Chacon) Switches
         kaku_switch            KlikAanKlikUit Switches

    GET /outlets/send/1/off 200 48ms
    child process exited with code 0

    Danke für Deine Mühen.

    • Ah, die Parameterreihenfolge scheint die Falsche zu sein. Probiere es mal so rum:

      1
      var child = spawn(“./pilight-send”, ["-p elro", u, "-i 2", onOff], { ...
  5. Damit lässt Nodes sich nicht starten.

    • Sry, die drei Punkte am Ende waren nur symbolisch gemeint. Der volle Code:

      1
      2
      3
      4
      var child = spawn("./pilight-send", ["-p elro", u, "-i 2", onOff], {
              cwd : "/home/pi/pilight",
              env : { HOME : "/home/pi/"}
          });
  6. Erst einmal danke für das Tutorial.
    Ich habe es als Anleitung genommen um Node.js auf meinem Pi einzurichten. RCSwitch-Pi lief vorher schon. Deswegen habe ich die Scripte und Webseiten ein wenig überarbeitet und erweitert.
    Ich benutze nun ein wenig mehr JQuery und Javascript. Es wurden auch Gruppen eingeführt und ein wenig mehr.

    Falls alles so läuft wie es soll werde ich hier noch eine Nachricht hinterlassen.

    • Hi Rüdiger,

      Hört sich cool an. Ich freue mich deinen Code zu sehen. Falls du sogar einen Blog-Post dazu schreiben willst, dann melde dich einfach bei mir.

      Viele Grüße
      Oliver

  7. Das Problem ist gelöst, wenn ich den exec String vor dem dem excec Aufruf zusammenstelle klappts, siehe:
    var exstr = „/home/pi/pilight/pilight-send -p elro -i 2 “ + u + onOff;
    var child = cp.exec(exstr, function (err, stdout, stderr)
    {if (err) {console.error(error);} else {util.debug(stderr);console.log(stdout);}});
    Herzlichen Dank für die Unterstützung und Gruss
    Richard

  8. Hallo, ich muss sagen eine spitzen Seite, habe alles hinbekommen mit der Steckdosen Schaltung, auch pilight konnte ich installieren und den Webgui einrichten, jetzt versuche ich mich mit Node.js um ein eigenes Interface zu bauen, da ich mit dieser Anleitung besser zurecht komme. Allerdings weiß ich nicht so recht in welchen Ordner ich die main.js und outlets.js packen soll, ich hab es in den node-scripts Ordner gepackt, das ausführen funktioniert auch, allerdings passiert nichts wenn ich den Befehl:http://ip-des-raspberry-pi/outlets/send/0/on ausführe, ich hab das outlets Skript auch an meine Elro Steckdosen angepasst, leider passiert nichts. vielleicht kann mir geholfen werden?

    • Hi Stefan,

      ja kommen beide in den gleichen Ordner. Hast du node mit sudo gestartet? Du kannst dir auch die Ausgabe des span Befehls anzeigen lassen: http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options

      Viele Grüße
      Oliver

      • Hm ich habe node mit:

        1
         sudo node main.js

        gestartet, ich habe parallel in einem Fenster die pilight-receive laufen und logischerweiße auch Receiver und Transmitter angeschlossen, wie ichs auch drehe, ich sehe das mein Empfänger rein gar nichts empfängt, dabei ist es ja auch egal was für ein Protocoll ich sende, hab einfach mal nichts an der outlets.js geändert, leider auch keinen empfang. So langsam bin ich ratlos, werde aber nochmal drüber schauen

Schreibe einen Kommentar

Home Software Funksteckdosen über das Web mit dem Raspberry Pi schalten
© sweet pi - sweet home
Top