[Python] Anleitung: Webserver, Websocket und ein bisschen AJAX

  • UnderConstruction.jpg
    Under Construction

    Diese Anleitung befindet sich noch im Aufbau, daher ist die Struktur etwas durcheinander.

    Fragen, Anregungen usw bitte im Diskussions-Thread stellen! :danke_ATDE:


    Einleitung:

    Alles GPIO relevante lässt sich am einfachsten und umfangreichsten über Python behandeln. Aber auch ohne GPIO sind die Möglichkeiten über Python umfassender als über PHP & Co.

    Wer vor hat sein Python Projekt auch übers Web zu steuern oder Informationen abrufen zu können, hat mehrere Möglichkeiten.
    Dabei gilt es aber 2 Sachlagen zu unterscheiden:
    1) Ein permanent laufendes Python Script
    2) Ein Python Script was nur ein mal bei Bedarf ausgeführt wird und direkt die Informationen ausgibt.

    Die 2. Möglichkeit kann wie gehabt mithilfe von PHP und exec(); umgesetzt werden (siehe dazu => hier <=); man kann aber auch eine FIFO Datei o.ä. nutzen. Auf diese Möglichkeit will ich hier aber nicht weiter eingehen - mein Anliegen in dieser Anleitung zielt auf die 1.Möglichkeit ab. Aber auch hier gibt es mehrere Möglichkeiten, wie immer :fies:

    • Eine Kombination aus WebSocket in JavaScript und Python.
    • Eine Übermittlung der Daten über spezielle URL's


    Bei der 2.Variante verzichtet man vollständig auf einen Webserver wie apache2, nginx, lighttpd usw.
    Bei der 1.Variante kann man ebenfalls auf einen Webserver wie apache2 verzichten, kann aber auch weiterhin apache2 usw nutzen.

    Primär möchte ich hier im speziellen mit Python und einem sog. Web-Framework arbeiten. Die bekanntesten wären: bottle, tornado, Flask, Django, Falcon, Pyramid... Weitere findet ihr zum Beispiel > hier < oder > hier <.
    Ich gehe hier erst mal nur auf bottle und tornado ein.

    Anfangs hab ich viel tornado genutzt, mittlerweile nutze ich aber bottle - hatte bei meinem RoPi Projekt einige Probleme mit tornado die ich mit bottle aber nicht mehr hatte... Ich glaub es hing damit zusammen das tornado Klassen benötigt bottle aber nicht unbedingt...
    Letztlich sollte es für euch aber kaum einen Unterschied ausmachen - es gibt immer irgendwelche Pro / Contras.

    Ein Vorteil, nicht nur sein Python Script zum auslesen eines Sensors o.ä. zu verwenden, sondern auch das Web-Interface mit Python umzusetzen besteht darin, dass man Python-Variablen/Werte direkt in den HTML Code einbetten kann...
    Ähnlich wie mit PHP wird dem Client nur Ausgaben angezeigt also HTML. Dh wenn eine sog. "route" (Erklärung weiter unten) angesprochen wird kann man bestimmte Stellen im HTML Code (template) durch Werte von Python-Variablen ersetzen bevor diese dem Client angezeigt werden.
    Das funktioniert aber nur ein mal beim laden der Seite (des templates). Wer dann nicht jedes mal die komplette Seite neu laden möchte greift stattdessen auf AJAX zurück. Auch das ist mit diesen Web-Frameworks kein Problem, JavaScript bzw jQuery wird ebenfalls unterstützt. JavaScript läuft übrigens nur beim Client. AJAX stellt nämlich auch nur Anfragen an den Server der dann darauf antwortet.

    Ein weiterer Vorteil besteht auch darin direkt GPIO's behandeln zu können, diese im vollen Umfang und direkt zu schalten sowie auszulesen da bekanntlich die meisten Module ala RPi.GPIO oder pigpio für Python existieren.
    Aber auch die andere Hardware des Raspberry's lassen sich über Python besser ansprechen als über PHP...

    Auch hat WebSocket den Vorteil das für den Zeitraum wie der Client die Seite offen hat, permanent eine Verbindung zwischen Client und Server besteht - wofür das HTTP Protokoll nicht gedacht und in der Lage ist. AJAX ist daher langsamer und auch belastender als der Weg über WebSocket.

    Und last but not least bedeutet solch eine Umsetzung über Python auch eine verbesserte Realtime Anwendung, da weitaus weniger Verzögerungen verursacht werden als ein Umweg über exec(); usw.

    Um gezielte Daten zwischen "Webserver" und "Client" auszutauschen verwendet man also am besten WebSocket.

    Man kann aber auch bestimmte "route"n anlegen um allgemeine Informationen abzurufen - route'n sind quasi URL's, so wäre " / " die Haupt-Route und zum Beispiel " /data/ " eine weitere die man zum abrufen der allgemeinen Informationen verwenden kann und " /cmd/ " könnte eine Route sein um Befehle abzusetzen... Bei letzterem könnte der Aufruf zum Beispiel so aussehen: /cmd/forward ... Der Befehl wäre dann also: forward
    Diesen Teil kann man übrigens auch mithilfe des Modules BaseHTTPServer (python2) bzw http.server (python3) realisieren, damit habe ich es nämlich in meinem RPi Info Windows Gadget umgesetzt.

    Zur Übergabe der Daten sollte man JSON verwenden, da sich so mehrere Informationen mit nur einer Abfrage übermitteln und standardisiert verarbeiten lassen. Auch darauf möchte ich hier eingehen da ich das mittlerweile für mein RoPi Projekt nutzen (anfangs nutzte ich WebSockets aber habe dann im laufe der Entwicklung eine bessere Vorgehensweise entdeckt). HTML5 Spiele nutzen ebenfalls diese Vorgehensweise.
    Auf der folgenden Seite findet ihr dazu eine detaillierte Beschreibung: https://www.fullstackpython.com/websockets.html

    Den HTML Code kann man entweder direkt in das Python Script, in die jeweilige Route-Funktion, einbetten - oder separate html Dateien verwenden. Letzteres ist die zu bevorzugende Möglichkeit. Dabei werden die Dateien in einem sog. template Verzeichnis abgelegt, Bilddateien oder auch die JavaScript Dateien wiederum legt man in einem sog. "static" Verzeichnis ab... Die Verzeichnisse kann man aber selbstverständlich auch einstellen.

    ..das sollte als Einführung erst mal reichen ;)


    Anmerkung:

    Leider verschluckt das Forum manche Leerzeichen in den Code-Blöcken, weshalb ich die wichtigsten Teile zusätzlich auf codepad.org verlinkt habe. Wenn ihr also Probleme habt dann probiert es bitte mit den codepad.org Links.


    Vorab: Ich achte nicht unbedingt auf absolute Korrektheit, PEP lass ich ebenfalls außen vor. Wer es professioneller als hier beschrieben umsetzen möchte möge sich bitte selber genauer zu dem Thema einlesen :fies: Ich befinde mich hier selbst noch in der Lernphase.

    Ich versuche hier nur die Oberflächlichen Details anzukratzen um es nicht allzu kompliziert werden zu lassen - es gibt aber unzählige Möglichkeiten...




    [an=index][/an]

    Aufgrund dessen was ich hier alles zeigen möchte unterteile ich die Anleitung in 3 Abschnitte:

    • [al=chapter1]Ein Webserver in Python, mit einsetzen von Variablen beim laden der Seite.[/al]
    • [al=chapter2]Ein Webserver in Python, mit WebSocket, der sowohl Poll als auch Push beherrscht.[/al]

      • [al=chapter2_auto]Automatisierung[/al]
    • [al=chapter3]Ein Webserver in Python, mit unterschiedlichen "route"s für reines Polling, ohne WebSocket.[/al]


    Poll bedeutet das der Client Daten vom Server abfragt.
    Push bedeutet das der Server die Daten sendet, ohne expliziter Anforderung des Clients. Der Client also nicht extra eine Aufforderung zum senden der Daten an den Server schicken muss.


    Auch wenn ihr eigentlich nur den 2. oder 3. Abschnitt umsetzen wollt solltet ihr auch den 1. beachten bzw lesen!


    Eine Erklärung wie ihr selber eine Erweiterung einbauen könnt findet ihr => in diesem Thread <=

    Ein weiteres Projekt was ich mittlerweile ebenfalls auf der hier vorgestellten Basis umgesetzt habe: Python: Sensor Werte mit HighCharts visualisieren


    => bottle - the right way to use <=


    UnderConstruction.jpg
    Under Construction

    Spoiler anzeigen


    Für WebSocket gibt es alternativ auch noch die Python Module websocket-server oder ws4py mit denen ich auch schon Erfahrung gemacht habe.


    Nun aber erst mal zu einem simplen Grundgerüst für WebSocket:

    bottle

    Als nächstes legen wir uns eine eigene JavaScript Datei an in der wir unsere Funktionen usw einfügen: nano /home/pi/static/control.js
    Und fügen dort folgendes ein:
    control.js => http://codepad.org/z7cxw2GX
    [code=php]
    if (typeof(String.prototype.strip) === "undefined") {
    String.prototype.strip = function() {
    return String(this).replace(/^\s+|\s+$/g, '');
    };
    }

    function isset(strVariableName) {
    try {
    eval( strVariableName );
    } catch( err ) {
    if ( err instanceof ReferenceError )
    return false;
    }
    return true;
    }

    function sleep(millis, callback) {
    setTimeout(function() { callback(); } , millis);
    }

    //source of: http://www.html5tutorial.info/html5-range.php
    function printValue(sliderID, textbox) {
    var x = document.getElementById(textbox);
    var y = document.getElementById(sliderID);
    x.value = y.value;
    }


    function mylog(message) {
    if (isset(DEBUG) && DEBUG == 1) {
    console.log(message);
    var logthingy;
    logthingy = document.getElementById("Log");
    if( logthingy.innerHTML.length > 5000 )
    logthingy.innerHTML = logthingy.innerHTML.slice(logthingy.innerHTML.length-5000);
    logthingy.innerHTML = logthingy.innerHTML+"<br/>"+message;
    logthingy.scrollTop = logthingy.scrollHeight*2;
    }
    }

    //----------------------------------------------------------------

    /*
    var telemetryTimer;
    $(document).ready(function() {
    // start Main Timers
    telemetryTimer = setTimeout(get_telemetry, 1000);
    });
    */

    function Send(command) {
    $.ajax({
    type: "GET",
    url: "/cmd/" + command,
    async: true,
    dataType: "JSON",
    success: function(obj) {
    console.log("JSON Data: " + obj.ID + ":" + obj.value);
    document.getElementById(obj.ID).innerHTML = obj.value;
    },
    });
    }

    function get_telemetry() {
    $.getJSON("/data/")
    .fail(function() {
    console.log("Error processing get_telemetry");
    clearTimeout(telemetryTimer);
    })
    .done(function(data) {
    $.each(data, function(id,val) {
    if (document.getElementById(id) !== null) {
    console.log("JSON Data: " + id + ":" + val);

    if (id == "LoadAVGnum") {
    document.getElementById(id).innerHTML = val + "%";
    } else if (id == "LoadAVGperc") {
    document.getElementById(id).value = val;
    } else if (id == "RAMnum") {
    document.getElementById(id).innerHTML = val + "MB";
    } else if (id == "RAMperc") {
    document.getElementById(id).value = val;

    } else {
    document.getElementById(id).innerHTML = val;
    }
    }
    })
    telemetryTimer = setTimeout(get_telemetry, 2000);
    });
    }

    function parseResponse(requestlist) {
    //mylog("Parsing: "+requestlist)
    for (var i=0; i<requestlist.length; i++) {
    var requestsplit = requestlist[i].strip().split(':')
    requestsplit[requestsplit.length] = "dummy";
    command = requestsplit[0];
    val = requestsplit[1];
    val2 = requestsplit[2];

    if (command == "LoadAVGnum") {
    document.getElementById("LoadAVGnum").innerHTML = val + "%";
    } else if (command == "LoadAVGperc") {
    document.getElementById("LoadAVGperc").value = val;
    } else if (command == "RAMnum") {
    document.getElementById("RAMnum").innerHTML = val + "MB";
    } else if (command == "RAMperc") {
    document.getElementById("RAMperc").value = val;
    }

    }
    }
    [/php]

    Auch hier ist bis jetzt noch kein WebSocket enthalten!

    In dieser JS Datei sind erst mal alle Voraussetzungen gegeben um " /data/ " und " /cmd/ " zu nutzen.
    Nach dem horizontalen Stich ist oben ein Teil noch auskommentiert, welcher bewirkt das nach laden der Seite nach 1000ms (1sec) die Funktion "get_telemetry()" ausgeführt und /data/ angesprochen wird. Das wird von dieser Funktion dann alle 2000ms wiederholt und vom Pythonscript zurückgegebene JSON Daten entsprechend verarbeitet.. Die Sachen ganz oben sind hilfreiche Funktionen, wie zum Beispiel "isset".


    Nun aber erst mal zum WebSocket Teil... Das können wir ebenfalls in die Datei control.js einfügen, es kann aber auch eine separate Datei sein, muss dann nur in der index.html zusätzlich geladen werden. Letzteres verwende ich hier und die Datei muss natürlich auch wieder ins static Verzeichnis.

    websocket.js => http://codepad.org/XidIvxoD
    [code=php]
    var ws;
    var ws_status = "closed";
    function set_ws_status(status) {
    ws_status = status;
    if (document.getElementById("connectionStatus") !== null) {
    document.getElementById("connectionStatus").innerHTML = status;
    }
    }

    function WebSocket_Close() {
    ws.close();
    }

    function WebSocket_Open() {
    ws = new WebSocket("ws://"+location.host+":7070");
    //mylog(ws);
    ws.onerror = function(evt) {
    mylog('Error detected: '+evt.data);
    }
    ws.onopen = function() {
    mylog('Connection opened!');
    set_ws_status("opened");
    }
    ws.onclose = function(evt) {
    if (isset(evt.reason)) {
    mylog('Connection closed:'+evt.reason);
    } else {
    mylog('Connection closed!');
    }
    set_ws_status("closed");
    }
    ws.onmessage = function(evt) {
    var message = evt.data;
    mylog('Received message: >>>'+message+'<<<');
    parseResponse(message.split("\n"));
    }
    }

    function WebSocket_Send(data) {
    if (ws_status == "opened") {
    ws.send(data);
    mylog('Sent message: >>>'+data+'<<<');
    }
    }
    [/php]


    Natürlich müssen wir ins Python Script auch noch Unterstützung für WebSocket einbauen. Ich poste jetzt aber nur die zusätzlichen Zeilen sodass man das auch standalone verwenden könnte - Wie die Scripts vollständig aussehen folgt später in dieser Anleitung.

    bottle
    [code=php]
    from __future__ import print_function

    [/php]


    tornado
    [code=php]
    from __future__ import print_function
    import tornado.web
    import tornado.websocket
    import tornado.ioloop

    WebSocketPort = 7070
    DEBUG = True

    def printD(message):
    if DEBUG:
    print(message)

    def getUptime():
    with open('/proc/uptime', 'r') as f:
    uptime_seconds = float(f.readline().split()[0])
    uptime = str(timedelta(seconds = uptime_seconds))
    return uptime

    def getPiRAM():
    with open('/proc/meminfo', 'r') as mem:
    tmp = 0
    for i in mem:
    sline = i.split()
    if str(sline[0]) == 'MemTotal:':
    total = int(sline[1])
    elif str(sline[0]) in ('MemFree:', 'Buffers:', 'Cached:'):
    tmp += int(sline[1])
    free = tmp
    used = int(total) - int(free)
    usedPerc = (used * 100) / total
    return usedPerc

    def getPiTemperature():
    with open("/sys/class/thermal/thermal_zone0/temp", 'r') as f:
    content = f.read().splitlines()
    return float(content[0]) / 1000.0

    ### Parse request from webif
    #required format-> command:value
    def WebRequestHandler(requestlist):
    returnlist = ""
    for request in requestlist:
    request = request.strip()
    requestsplit = request.split(':')
    requestsplit.append("dummy")
    command = requestsplit[0]
    value = requestsplit[1]
    if value == "dummy":
    value = "0"
    if command == "localping":
    returnlist += "\n localping:ok"

    elif command == "LoadAVRnum":
    returnlist += "\n LoadAVRnum:"+open("/proc/loadavg").readline().split(" ")[:3][0]
    elif command == "Uptime":
    returnlist += "\n Uptime:"+str(getUptime()).split(".")[0]
    elif command == "RAMperc":
    returnlist += "\n RAMperc:"+str(getPiRAM())
    #returnlist += "\n RAMperc:"+str(psutil.phymem_usage().percent)
    elif command == "PiTEMP":
    returnlist += "\n PiTEMP:"+str(getPiTemperature())
    elif command == "System.Power":
    if value == "off":
    subprocess.Popen(["shutdown","-h","now"])
    return "System.Power:ok"
    elif value == "reboot":
    subprocess.Popen(["shutdown","-r","now"])
    return "System.Power:ok"

    return returnlist

    ### WebSocket server tornado <-> WebInterface
    class WebSocketHandler(tornado.websocket.WebSocketHandler):
    connections = []
    # the client connected
    def open(self):
    printD("New client connected")
    self.write_message("You are connected")
    self.connections.append(self)
    # the client sent the message
    def on_message(self, message):
    printD("Message from WebIf: >>>"+message+"<<<")
    requestlist = message.splitlines()
    self.write_message(WebRequestHandler(requestlist))
    # client disconnected
    def on_close(self):
    printD("Client disconnected")
    self.connections.remove(self)

    # start a new WebSocket Application
    # use "/" as the root, and the WebSocketHandler as our handler
    application = tornado.web.Application([
    (r"/", WebSocketHandler),
    ])

    try:
    # start the tornado webserver on port WebSocketPort
    application.listen(WebSocketPort)
    tornado.ioloop.IOLoop.instance().start()
    except Exception, e1:
    print("Error...: " + str(e1))
    except KeyboardInterrupt:
    print("Schliesse Programm..")
    [/php]


    Jetzt müssen wir auch noch die index.html anpassen bzw erweitern und bauen auch gleich einen Button zum testen ein:

    index.html
    [code=php]
    <!DOCTYPE html>
    <html>
    <head>
    <script src="/static/jquery.min.js" type="text/javascript"></script>
    <script src="/static/control.js" type="text/javascript"></script>
    <script src="/static/websocket.js" type="text/javascript"></script>
    </head>
    <body>
    Test: {{ test }}
    <br/>
    <input type="button" value="CPU Temperatur" onClick="WebSocket_Send('PiTEMP')" />
    <br/>
    <span id="PiTEMP"></span> &deg;C
    </body>
    </html>
    [/php]


  • [Python] Anleitung: Webserver, Websocket und ein bisschen AJAX? Schau mal ob du hier fündig wirst!

  • [an=chapter1][/an]Zu Abschnitt 1: => Ein Webserver in Python, mit einsetzen von Variablen beim laden der Seite.


    Ihr solltet euch von Anfang an eine ordentliche Struktur angewöhnen - aktuell speziell was den Ablageort des Python Scripts sowie den Web Dateien betrifft. Das hier gezeigte hat aber nichts mit /var/www/ zu tun!

    Im selben Verzeichnis wo das Python Script liegt sollten 2 Verzeichnisse erstellt werden:

    • templates
    • static


    Die index.html gehört ins templates Verzeichnis, ebenso wie alle weiteren *.html's.
    Ins static Verzeichnis kommen später die JavaScript Dateien (zB jquery.min.js) und ggf noch *.css oder Bilddateien etc.


    Wichtig: Die Python Script Datei darf nicht wie das Module heißen! Ihr dürft die Datei also nicht bottle.py oder tornado.py nennen!
    Ich verwende hier daher: web_bottle.py und web_tornado.py
    Als Hauptverzeichnis nehme ich der Einfachheit Halber direkt /home/pi/
    Wo Ihr die Dateien letztlich ablegt ist aber egal.

    Das Python Script könnt ihr als normaler Benutzer ausführen solange der Port über 1024 liegt (niedriger darf nur root verwenden). Ich verwende hier den Port 8080 um kein Konflikt mit einem anderen Webserver wie apache2 usw zu haben.

    Im derzeitigen Zustand der Python Scripts wie ich sie gleich zeige spielt es eigentlich noch keine Rolle ob man sie mit python2 oder python3 ausführt. Beachtet aber das python3 nicht vollständig kompatibel zu python2 ist und einige Sachen anders gehandhabt werden, insbesondere Types. Aufgrund dessen gestaltet sich python3 daher etwas komplizierter.


    Bevor wir jetzt zum Code kommen müssen wir erst mal die Module installieren:

    Python2 bottle:

    Code
    sudo apt-get install python-bottle


    Python2 tornado:

    Code
    sudo apt-get install python-tornado

    ODER

    Python3 bottle:

    Code
    sudo apt-get install python3-bottle


    Python3 tornado:

    Code
    sudo apt-get install python3-tornado


    web_bottle.py => http://codepad.org/ieQc34zG

    Spoiler anzeigen

    [code=php]
    from __future__ import print_function
    import os.path
    import bottle
    from random import randrange

    bottle.debug(True) #sollte spaeter ausgeschaltet werden!
    bottle.TEMPLATE_PATH.insert(0, os.path.join(os.path.dirname(__file__), 'templates'))

    @bottle.route('/')
    def MainHandler():
    values = {
    'test': randrange(1, 1000),
    }
    return bottle.template('index.html', values)

    @bottle.route('/static/<filename>')
    def StaticHandler(filename):
    if filename.endswith(".css"):
    bottle.response.content_type = 'text/css'
    elif filename.endswith(".js"):
    bottle.response.content_type = 'text/javascript'
    elif filename.endswith(".png"):
    bottle.response.content_type = 'image/png'
    return bottle.static_file(filename, root=os.path.join(os.path.dirname(__file__), 'static'))

    @bottle.error(404)
    def error404(error):
    return 'Error 404: Nothing here, sorry.'

    try:
    bottle.run(host='0.0.0.0', port=8080, quiet=True)
    except (KeyboardInterrupt, SystemExit):
    print('\nQuit\n')
    [/php]


    web_tornado.py => http://codepad.org/7OK8Nidl

    Spoiler anzeigen

    [code=php]
    from __future__ import print_function
    import os.path
    import tornado.web
    import tornado.httpserver
    import tornado.ioloop
    from random import randrange

    class Application(tornado.web.Application):
    def __init__(self):
    handlers = [
    (r"/", MainHandler),
    (r"/static/(.*)", StaticHandler),
    ]
    settings = dict(
    template_path=os.path.join(os.path.dirname(__file__), "templates"),
    static_path=os.path.join(os.path.dirname(__file__), "static"),
    debug=True,
    autoescape=None
    )
    tornado.web.Application.__init__(self, handlers, **settings)

    class MainHandler(tornado.web.RequestHandler):
    #called every time someone sends a GET HTTP request
    @tornado.web.asynchronous
    def get(self):
    self.render(
    "index.html",
    test = randrange(1, 1000),
    )

    # deliver static files to page
    class StaticHandler(tornado.web.RequestHandler):
    def get(self, filename):
    with open("static/" + filename, "r") as fh:
    self.file = fh.read()
    # write to page
    if filename.endswith(".css"):
    self.set_header("Content-Type", "text/css")
    elif filename.endswith(".js"):
    self.set_header("Content-Type", "text/javascript")
    elif filename.endswith(".png"):
    self.set_header("Content-Type", "image/png")
    self.write(self.file)

    try:
    http_server = tornado.httpserver.HTTPServer(Application())
    http_server.listen(8080)
    tornado.ioloop.IOLoop.instance().start()
    except (KeyboardInterrupt, SystemExit):
    print("\nQuit\n")
    [/php]


    index.html

    Spoiler anzeigen


    [code=php]
    <!DOCTYPE html>
    <html>
    <head>
    </head>
    <body>
    Test: {{ test }}
    </body>
    </html>
    [/php]


    Ihr habt nun also zum Beispiel im Verzeichnis /home/pi/ das Script web_bottle.py und ein Verzeichnis /home/pi/templates/ sowie die Datei /home/pi/templates/index.html
    Führt nun ein Python Script eurer Wahl (bottle oder tornado) aus und surft die IP eures Pi's mit Zusatz des Ports 8080 an - zum Beispiel http://192.168.0.10:8080

    Jedes mal wenn ihr die Seite aufruft (ladet) ändert sich der "Test: xxx" Text, also beim ersten Aufruf steht da zB. "Test: 970", drückt ihr dann F5 (reload) steht da "Test: 160" usw. Was da passiert wurde zuvor im Script festgelegt: Beim generieren/rendern des templates wird die Variable "test" in den HTML Code eingesetzt.
    Das kann man für Statische Informationen verwenden wie zum Beispiel Variablen für JavaScript die man nicht unbedingt fest (hardcoded) in den HTML Code einbauen möchte, sondern bequemer nur das Python Script anpassen brauch... Oder eine Debug-Ausgabe von JavaScript die ihr nur bei Bedarf einschaltet, oder einsetzen einer Versionsnummer etc..


    [al=index]=> Back to Index <=[/al]

  • [an=chapter2][/an]Zu Abschnitt 2: => Ein Webserver in Python, mit WebSocket, der sowohl Poll als auch Push beherrscht.


    Für WebSocket benötigen wir eine JavaScript Datei die wir ins Verzeichnis static ablegen.

    websocket.js => http://codepad.org/YWh3SW9I

    Spoiler anzeigen

    Für Python3:

    Code
    sudo apt-get install python3-ws4py


    web_bottle.py => http://codepad.org/I1XozH3l

    Spoiler anzeigen


    [code=php]
    from __future__ import print_function
    import os.path
    import bottle
    from threading import Thread
    from wsgiref.simple_server import make_server
    from ws4py.websocket import WebSocket
    from ws4py.server.wsgirefserver import WSGIServer, WebSocketWSGIRequestHandler
    from ws4py.server.wsgiutils import WebSocketWSGIApplication
    from random import randrange

    DEBUG = 1

    #-------------------------------------------------------------------

    def printD(message):
    if DEBUG:
    print(message)

    def getUptime():
    with open('/proc/uptime', 'r') as f:
    uptime_seconds = float(f.readline().split()[0])
    uptime = str(timedelta(seconds = uptime_seconds))
    return uptime

    def getPiRAM():
    with open('/proc/meminfo', 'r') as mem:
    tmp = 0
    for i in mem:
    sline = i.split()
    if str(sline[0]) == 'MemTotal:':
    total = int(sline[1])
    elif str(sline[0]) in ('MemFree:', 'Buffers:', 'Cached:'):
    tmp += int(sline[1])
    free = tmp
    used = int(total) - int(free)
    usedPerc = (used * 100) / total
    return usedPerc

    def getPiTemperature():
    with open("/sys/class/thermal/thermal_zone0/temp", 'r') as f:
    content = f.read().splitlines()
    return float(content[0]) / 1000.0

    #-------------------------------------------------------------------

    ### Parse request from webif
    #required format-> command:value
    def WebRequestHandler(requestlist):
    returnlist = ""
    for request in requestlist:
    request = request.strip()
    requestsplit = request.split(':')
    requestsplit.append("dummy")
    command = requestsplit[0]
    value = requestsplit[1]
    if value == "dummy":
    value = "0"

    if command == "localping":
    returnlist += "\n localping:ok"
    elif command == "LoadAVRnum":
    returnlist += "\n LoadAVRnum:"+open("/proc/loadavg").readline().split(" ")[:3][0]
    elif command == "Uptime":
    returnlist += "\n Uptime:"+str(getUptime()).split(".")[0]
    elif command == "RAMperc":
    returnlist += "\n RAMperc:"+str(getPiRAM())
    #returnlist += "\n RAMperc:"+str(psutil.phymem_usage().percent)
    elif command == "PiTEMP":
    returnlist += "\n PiTEMP:"+str(getPiTemperature())
    elif command == "System.Power":
    if value == "off":
    subprocess.Popen(["shutdown","-h","now"])
    return "System.Power:ok"
    elif value == "reboot":
    subprocess.Popen(["shutdown","-r","now"])
    return "System.Power:ok"
    return returnlist

    class myWebSocketHandler(WebSocket):
    connections = []
    def opened(self):
    printD("New WebSocket client connected")
    self.send("You are connected")
    self.connections.append(self)
    def received_message(self, message):
    msg = message.data.decode()
    printD("Message from WebIf: >>>"+msg+"<<<")
    requestlist = msg.splitlines()
    self.send(WebRequestHandler(requestlist))
    def closed(self, code, reason):
    printD("WebSocket closed %s %s" % (code, reason))
    self.connections.remove(self)

    #-------------------------------------------------------------------

    @bottle.route('/')
    def MainHandler():
    values = {
    'test': randrange(1, 1000),
    'debug': 1,
    }
    return bottle.template('index.html', values)

    @bottle.route('/static/<filename>')
    def StaticHandler(filename):
    if filename.endswith(".css"):
    bottle.response.content_type = 'text/css'
    elif filename.endswith(".js"):
    bottle.response.content_type = 'text/javascript'
    elif filename.endswith(".png"):
    bottle.response.content_type = 'image/png'
    return bottle.static_file(filename, root=os.path.join(os.path.dirname(__file__), 'static'))


    try:
    websocket_server = make_server(
    '', 7070,
    server_class=WSGIServer,
    handler_class=WebSocketWSGIRequestHandler,
    app=WebSocketWSGIApplication(handler_cls=myWebSocketHandler)
    )
    websocket_server.initialize_websockets_manager()
    # Start Child Thread for WebSocket
    print('Starting Child Thread for WebSocket_Server')
    ws = Thread(target=websocket_server.serve_forever)
    ws.setDaemon(True)
    ws.start()

    bottle.debug(True) #sollte spaeter ausgeschaltet werden!
    bottle.TEMPLATE_PATH.insert(0, os.path.join(os.path.dirname(__file__), 'templates'))
    bottle.run(host='0.0.0.0', port='8080', debug=True, quiet=False)
    except KeyboardInterrupt:
    pass
    finally:
    print('Shutting down Servers')
    ws.join(1)
    try:
    ws.shutdown()
    except:
    pass
    [/php]


    [al=index]=> Back to Index <=[/al]

  • [an=chapter2_auto][/an]Automatisierung für Abschnitt 2: => Ein Webserver in Python, mit WebSocket, der sowohl Poll als auch Push beherrscht.


    In Beitrag#3 (Abschnitt 2) nutzen wir einen Button um manuell Informationen vom Python-Script abzurufen. Das kann man aber natürlich auch über 2 verschiedene Wege automatisieren:

    • Im JavaScript (also Client-seitig) Timer einbauen die regelmäßig Befehle senden => Poll
    • Im Python-Script Timer einbauen die regelmäßig Daten senden => Push

    Zu bevorzugen wäre die 1.Variante da es dort einfacher ist, festzustellen ob eine Verbindung besteht und der Client selbst nur einen Server anzusprechen hat... Bei der 2.Variante gestaltet sich das je nach verwendeten Web-Frameworks umständlicher da mehrere Clients möglich sind usw.


    Für die 1.Variante muss am Python Scripts nichts verändert werden, wir bearbeiten stattdessen nur die control.js.
    Wie im 2.Abschnitt bereits erwähnt wäre es nun recht hilfreich eine " init " Funktion zu erstellen, in der sowohl die WebSocket Verbindung aufgebaut als auch die Timer initialisiert werden - darum kümmern wir uns als erstes.

    Für einen Timer in JavaScript gibt es 2 verschiedene Methoden:
    * setInterval
    * setTimeout
    Bei beiden gibt man die auszuführende Funktion und die Zeit in Millisekunden an, also 1000 entsprechen 1 Sekunde. Es gibt zwischen den beiden Methoden aber einen gravierenden Unterschied:
    setTimeout führt die Funktion nach Ablauf der Zeit nur ein mal aus, für einen dauerhaften Zyklus muss am Ende der Funktion also erneut setTimeout genutzt werden.
    setInterval führt die Funktion in einem dauerhafte Zyklus immer wieder aus.
    Egal für welche Methode man sich entscheidet sollte man die Rückgabe des Timers, also die Timer ID, in eine Variable schreiben sodass man diese ggf später zurücksetzen kann - falls die Verbindung verloren geht kann das wichtig werden sonst hagelts Fehlermeldungen.

    Wichtig ist hier das wir nur den Funktionsnamen angeben also keinen Aufruf machen. Sobald man die () mit angibt ist es ein Aufruf, also: weglassen!
    Und ein Problem gibt es hierbei auch mit InternetExplorer <= 9, der unterstützt nämlich nicht die direkte Angabe von Parametern die der Funktion übergeben werden sollen - aber IE hält sich generell nicht an HTML Standards, also wenn möglich einen anderen Browser nutzen!
    Wenn ihr unbedingt IE9 oder älter nutzen wollt, dann müsst ihr für den Aufruf eine eigene Funktion definieren, in der WebSocket_Send("PiTEMP"); o.ä. drin steht..

    [code=php]
    // Initalize the various page elements here...
    var pitempTimer;
    function init() {
    WebSocket_Open();
    // start Main Timers
    pitempTimer = setInterval(WebSocket_Send, 2000, "PiTEMP");
    }
    [/php]

    Hiermit stellen wir also erst eine WebSocket Verbindung her und starten zugleich einen Interval, der alle 2 Sekunden WebSocket_Send("PiTEMP") ausführt. Eigentlich ganz einfach, oder? :cool:
    Beachtet das ein zu kurzer Intervall evtl. zu viel CPU-Last erzeugt, in diesem Fall sind 2 Sekunden eigentlich ausreichend, kürzer als 1000ms sollte man nur dann verwenden wenn man weiß was man tut und sich absolut sicher ist dass das wirklich notwendig ist.

    Was ihr jetzt noch machen müsst ist die index.html Datei zu bearbeiten, die Zeile " <body onload="WebSocket_Open()"> " zu suchen und wie folgt anzupassen:
    [code=php] <body onload="init()">[/php]

    Thats it ;)


    Für die 2.Variante müsst ihr die jeweilige web_*.py Datei bearbeiten und sobald eine WebSocket Verbindung hergestellt wurde einen Timer mithilfe von threading.Timer() starten...

    In der web_tornado.py wäre das in der WebSocketHandler Klasse innerhalb der def open Funktion, in der web_tornado.py wiederum in der myWebSocketHandler Klasse und der def opened Funktion.
    Wichtig ist das ihr der Timer Funktion das "self" Objekt mit übergibt damit auch an den jeweiligen Client gesendet werden kann.

    Zunächst müssen wir eine Funktion erstellen die vom Timer aufgerufen wird. Wir nennen diese Funktion einfach mal pushDataTimed. Da threading.Timer() nur ein mal ausgeführt wird, müssen wir uns in der Funktion selber darum kümmern diese in einem immer wiederkehrenden Intervall auszuführen - dazu übergeben wir der Funktion gleich die Parameter "was" und "wann".

    Beispiel für web_tornado.py:
    [code=php]
    import threading

    def pushDataTimed(clients, what, when):
    what = str(what)
    when = float(when)
    for index, client in enumerate(clients):
    if client:
    client.write_message( WebRequestHandler(what.splitlines()) )
    timed = threading.Timer( when, pushDataTimed, [clients, what, when] )
    timed.start()
    timers.append(timed)
    [/php]

    Wichtig ist auch das wir die gestarteten timer-IDs uns merken, also in eine Liste hinzufügen, damit beim beenden des Scripts die Timer auch beendet werden - falls wir das nicht tun lässt sich das Script nämlich nicht beenden :fies:

    Die Anpassung in der WebSocketHandler Klasse würde dann wie folgt aussehen:

    [code=php]# ...

    timed = threading.Timer( 2.0, pushDataTimed, [self.connections, "PiTEMP", "2.0"] )
    timed.start()
    timers.append(timed)
    [/php]2.0 ist wieder alle 2 Sekunden, die Zeit sollte als Floating-Point angegeben werden.


    Die vollständigen Python-Script würden dann so aussehen:

    web_tornado.py => http://codepad.org/9m9gqdWu

    Spoiler anzeigen


    [code=php]
    from __future__ import print_function
    import os.path
    import tornado.web
    import tornado.websocket
    import tornado.httpserver
    import tornado.ioloop
    import threading
    from random import randrange

    DEBUG = 1
    httpPort = 8080
    websocketPort = 7070

    #-------------------------------------------------------------------

    def printD(message):
    if DEBUG:
    print(message)

    def getUptime():
    with open('/proc/uptime', 'r') as f:
    uptime_seconds = float(f.readline().split()[0])
    uptime = str(timedelta(seconds = uptime_seconds))
    return uptime

    def getPiRAM():
    with open('/proc/meminfo', 'r') as mem:
    tmp = 0
    for i in mem:
    sline = i.split()
    if str(sline[0]) == 'MemTotal:':
    total = int(sline[1])
    elif str(sline[0]) in ('MemFree:', 'Buffers:', 'Cached:'):
    tmp += int(sline[1])
    free = tmp
    used = int(total) - int(free)
    usedPerc = (used * 100) / total
    return usedPerc

    def getPiTemperature():
    with open("/sys/class/thermal/thermal_zone0/temp", 'r') as f:
    content = f.read().splitlines()
    return float(content[0]) / 1000.0

    #-------------------------------------------------------------------

    ### Parse request from webif
    #required format-> command:value
    def WebRequestHandler(requestlist):
    returnlist = ""
    for request in requestlist:
    request = request.strip()
    requestsplit = request.split(':')
    requestsplit.append("dummy")
    command = requestsplit[0]
    value = requestsplit[1]
    if value == "dummy":
    value = "0"

    if command == "localping":
    returnlist += "\n localping:ok"
    elif command == "LoadAVRnum":
    returnlist += "\n LoadAVRnum:"+open("/proc/loadavg").readline().split(" ")[:3][0]
    elif command == "Uptime":
    returnlist += "\n Uptime:"+str(getUptime()).split(".")[0]
    elif command == "RAMperc":
    returnlist += "\n RAMperc:"+str(getPiRAM())
    #returnlist += "\n RAMperc:"+str(psutil.phymem_usage().percent)
    elif command == "PiTEMP":
    returnlist += "\n PiTEMP:"+str(getPiTemperature())
    elif command == "System.Power":
    if value == "off":
    subprocess.Popen(["shutdown","-h","now"])
    return "System.Power:ok"
    elif value == "reboot":
    subprocess.Popen(["shutdown","-r","now"])
    return "System.Power:ok"

    return returnlist

    def pushDataTimed(clients, what, when):
    what = str(what)
    when = float(when)
    for index, client in enumerate(clients):
    if client:
    client.write_message( WebRequestHandler(what.splitlines()) )
    timed = threading.Timer( when, pushDataTimed, [clients, what, when] )
    timed.start()
    timers.append(timed)

    ### WebSocket server tornado <-> WebInterface
    class WebSocketHandler(tornado.websocket.WebSocketHandler):
    connections = []
    # the client connected
    def check_origin(self, origin):
    return True
    def open(self):
    printD("New WebSocket client connected")
    self.write_message("You are connected")
    self.stream.set_nodelay(True)
    self.connections.append(self)
    timed = threading.Timer( 2.0, pushDataTimed, [self.connections, "PiTEMP", "2.0"] )
    timed.start()
    timers.append(timed)
    # the client sent the message
    def on_message(self, message):
    printD("Message from WebIf: >>>"+message+"<<<")
    requestlist = message.splitlines()
    self.write_message(WebRequestHandler(requestlist))
    # client disconnected
    def on_close(self):
    printD("WebSocket client disconnected")
    self.connections.remove(self)

    #-------------------------------------------------------------------

    class Web_Application(tornado.web.Application):
    def __init__(self):
    handlers = [
    (r"/", MainHandler),
    (r"/static/(.*)", StaticHandler),
    ]
    settings = dict(
    template_path=os.path.join(os.path.dirname(__file__), "templates"),
    static_path=os.path.join(os.path.dirname(__file__), "static"),
    debug=DEBUG,
    autoescape=None
    )
    tornado.web.Application.__init__(self, handlers, **settings)

    class MainHandler(tornado.web.RequestHandler):
    #called every time someone sends a GET HTTP request
    @tornado.web.asynchronous
    def get(self):
    self.render(
    "index.html",
    debug = DEBUG,
    test = randrange(1, 1000),
    )

    # deliver static files to page
    class StaticHandler(tornado.web.RequestHandler):
    def get(self, filename):
    with open("static/" + filename, "r") as fh:
    self.file = fh.read()
    # write to page
    if filename.endswith(".css"):
    self.set_header("Content-Type", "text/css")
    elif filename.endswith(".js"):
    self.set_header("Content-Type", "text/javascript")
    elif filename.endswith(".png"):
    self.set_header("Content-Type", "image/png")
    self.write(self.file)

    try:
    timers = list()
    ws_app = tornado.web.Application([(r"/", WebSocketHandler),])
    ws_server = tornado.httpserver.HTTPServer(ws_app)
    ws_server.listen(websocketPort)

    web_server = tornado.httpserver.HTTPServer(Web_Application())
    web_server.listen(httpPort)
    tornado.ioloop.IOLoop.instance().start()
    except (KeyboardInterrupt, SystemExit):
    for index, timer in enumerate(timers):
    if timer:
    timer.cancel()
    print("\nQuit\n")
    [/php]

    web_bottle.py => http://codepad.org/rvnz9Kfb

    Spoiler anzeigen


    [code=php]
    from __future__ import print_function
    import os.path
    import bottle
    from threading import Thread, Timer as threadTimer
    from wsgiref.simple_server import make_server
    from ws4py.websocket import WebSocket
    from ws4py.server.wsgirefserver import WSGIServer, WebSocketWSGIRequestHandler
    from ws4py.server.wsgiutils import WebSocketWSGIApplication
    from random import randrange

    DEBUG = 1
    httpPort = 8080
    websocketPort = 7070

    #-------------------------------------------------------------------

    def printD(message):
    if DEBUG:
    print(message)

    def getUptime():
    with open('/proc/uptime', 'r') as f:
    uptime_seconds = float(f.readline().split()[0])
    uptime = str(timedelta(seconds = uptime_seconds))
    return uptime

    def getPiRAM():
    with open('/proc/meminfo', 'r') as mem:
    tmp = 0
    for i in mem:
    sline = i.split()
    if str(sline[0]) == 'MemTotal:':
    total = int(sline[1])
    elif str(sline[0]) in ('MemFree:', 'Buffers:', 'Cached:'):
    tmp += int(sline[1])
    free = tmp
    used = int(total) - int(free)
    usedPerc = (used * 100) / total
    return usedPerc

    def getPiTemperature():
    with open("/sys/class/thermal/thermal_zone0/temp", 'r') as f:
    content = f.read().splitlines()
    return float(content[0]) / 1000.0

    #-------------------------------------------------------------------

    ### Parse request from webif
    #required format-> command:value
    def WebRequestHandler(requestlist):
    returnlist = ""
    for request in requestlist:
    request = request.strip()
    requestsplit = request.split(':')
    requestsplit.append("dummy")
    command = requestsplit[0]
    value = requestsplit[1]
    if value == "dummy":
    value = "0"

    if command == "localping":
    returnlist += "\n localping:ok"
    elif command == "LoadAVRnum":
    returnlist += "\n LoadAVRnum:"+open("/proc/loadavg").readline().split(" ")[:3][0]
    elif command == "Uptime":
    returnlist += "\n Uptime:"+str(getUptime()).split(".")[0]
    elif command == "RAMperc":
    returnlist += "\n RAMperc:"+str(getPiRAM())
    #returnlist += "\n RAMperc:"+str(psutil.phymem_usage().percent)
    elif command == "PiTEMP":
    returnlist += "\n PiTEMP:"+str(getPiTemperature())
    elif command == "System.Power":
    if value == "off":
    subprocess.Popen(["shutdown","-h","now"])
    return "System.Power:ok"
    elif value == "reboot":
    subprocess.Popen(["shutdown","-r","now"])
    return "System.Power:ok"
    return returnlist

    def pushDataTimed(clients, what, when):
    what = str(what)
    when = float(when)
    for index, client in enumerate(clients):
    if client:
    client.send( WebRequestHandler(what.splitlines()) )
    timed = threadTimer( when, pushDataTimed, [clients, what, when] )
    timed.start()
    timers.append(timed)

    class myWebSocketHandler(WebSocket):
    connections = []
    def opened(self):
    printD("New WebSocket client connected")
    self.send("You are connected")
    self.connections.append(self)
    timed = threadTimer( 2.0, pushDataTimed, [self.connections, "PiTEMP", "2.0"] )
    timed.start()
    timers.append(timed)
    def received_message(self, message):
    msg = message.data.decode()
    printD("Message from WebIf: >>>"+msg+"<<<")
    requestlist = msg.splitlines()
    self.send(WebRequestHandler(requestlist))
    def closed(self, code, reason):
    printD("WebSocket closed %s %s" % (code, reason))
    self.connections.remove(self)

    #-------------------------------------------------------------------

    @bottle.route('/')
    def MainHandler():
    values = {
    'test': randrange(1, 1000),
    'debug': 1,
    }
    return bottle.template('index.html', values)

    @bottle.route('/static/<filename>')
    def StaticHandler(filename):
    if filename.endswith(".css"):
    bottle.response.content_type = 'text/css'
    elif filename.endswith(".js"):
    bottle.response.content_type = 'text/javascript'
    elif filename.endswith(".png"):
    bottle.response.content_type = 'image/png'
    return bottle.static_file(filename, root=os.path.join(os.path.dirname(__file__), 'static'))


    try:
    timers = list()
    websocket_server = make_server(
    '', websocketPort,
    server_class=WSGIServer,
    handler_class=WebSocketWSGIRequestHandler,
    app=WebSocketWSGIApplication(handler_cls=myWebSocketHandler)
    )
    websocket_server.initialize_websockets_manager()
    # Start Child Thread for WebSocket
    print('Starting Child Thread for WebSocket_Server')
    ws = Thread(target=websocket_server.serve_forever)
    ws.setDaemon(True)
    ws.start()

    bottle.debug(True) #sollte spaeter ausgeschaltet werden!
    bottle.TEMPLATE_PATH.insert(0, os.path.join(os.path.dirname(__file__), 'templates'))
    bottle.run(host='0.0.0.0', port=httpPort, debug=True, quiet=False)
    except KeyboardInterrupt:
    pass
    finally:
    for index, timer in enumerate(timers):
    if timer:
    timer.cancel()
    print('Shutting down Servers')
    ws.join(1)
    try:
    ws.shutdown()
    except:
    pass
    [/php]


    ...Aber wie gesagt solltet ihr die 1. Variante bevorzugen => Poll(ing)


    [al=index]=> Back to Index <=[/al]

  • [an=chapter3][/an]Zu Abschnitt 3: => Ein Webserver in Python, mit unterschiedlichen "route"s für reines Polling, ohne WebSocket.


    Im laufe der Entwicklung für mein Roboter Projekt habe ich eine, meiner Meinung nach, bessere Möglichkeit gefunden mit der man komplett auf WebSockets verzichten kann. Das hat folgende Vorteile:

    • Es muss keine Socket Verbindung hergestellt und gehalten werden.
      Das wiederum hat den Vorteil keine Sonderbehandlung einbauen zu müssen, wie zum Beispiel: was passiert wenn die Verbindung verloren geht.
    • Mit nur einer Anfrage können dank JSON quasi unendlich viele Informationen auf ein mal abgefragt werden.
      Gestaltet man seine Seite dann so das die ID dem "name" entspricht, kann man sich noch mehr Code sparen.

    Als Basis kommen die reinen Webserver Scripts aus dem 1.Abschnitt zum Einsatz und müssen nur um 2 route's erweitert werden:

    • /data/
      Zum allgemeinen abrufen von Daten
    • /cmd/
      Zum Senden von Befehlen, wie zum Beispiel /cmd/forward ... Der Befehl wäre dann also: forward

    Ihr könnt die URI's natürlich auch anders benennen, das spielt keine Rolle, Hauptsache ihr nutzt im JavaScript auch die richtigen ;)

    Wie bereits erwähnt verwende ich JSON um die übermittelten Daten zu verpacken. Dazu kann man in Python ganz einfach ein Dictionary erzeugen, also quasi ein Array. Hier ein Beispiel wie ich es mit bottle nutze:

    Als nächstes legen wir uns eine eigene JavaScript Datei an in der wir unsere Funktionen usw einfügen: nano /home/pi/static/control.js
    Und fügen dort folgendes ein:
    control.js => http://codepad.org/YOdSenoo
    [code=php]
    if (typeof(String.prototype.strip) === "undefined") {
    String.prototype.strip = function() {
    return String(this).replace(/^\s+|\s+$/g, '');
    };
    }

    function isset(strVariableName) {
    try {
    eval( strVariableName );
    } catch( err ) {
    if ( err instanceof ReferenceError )
    return false;
    }
    return true;
    }

    function sleep(millis, callback) {
    setTimeout(function() { callback(); } , millis);
    }

    //source of: http://www.html5tutorial.info/html5-range.php
    function printValue(sliderID, textbox) {
    var x = document.getElementById(textbox);
    var y = document.getElementById(sliderID);
    x.value = y.value;
    }


    function mylog(message) {
    if (isset(DEBUG) && DEBUG == 1) {
    console.log(message);
    if (document.getElementById("Log") !== null) {
    var logthingy;
    logthingy = document.getElementById("Log");
    if( logthingy.innerHTML.length > 5000 )
    logthingy.innerHTML = logthingy.innerHTML.slice(logthingy.innerHTML.length-5000);
    logthingy.innerHTML = logthingy.innerHTML+"<br/>"+message;
    logthingy.scrollTop = logthingy.scrollHeight*2;
    }
    }
    }

    //----------------------------------------------------------------

    /*
    var telemetryTimer;
    $(document).ready(function() {
    // start Main Timers
    telemetryTimer = setTimeout(get_telemetry, 1000);
    });
    */

    function Send(command) {
    $.ajax({
    type: "GET",
    url: "/cmd/" + command,
    dataType: "JSON",
    success: function(data) {
    mylog("Command Response: " + data);
    if (document.getElementById(command) !== null) {
    document.getElementById(command).innerHTML = data;
    }
    }
    });
    }

    function get_telemetry() {
    $.getJSON("/data/")
    .fail(function() {
    console.log("Error processing get_telemetry");
    clearTimeout(telemetryTimer);
    })
    .done(function(data) {
    $.each(data, function(id,val) {
    if (document.getElementById(id) !== null) {
    mylog("JSON Data: " + id + ":" + val);

    if (id == "LoadAVGnum") {
    document.getElementById(id).innerHTML = val + "%";
    } else if (id == "LoadAVGperc") {
    document.getElementById(id).value = val;
    } else if (id == "RAMnum") {
    document.getElementById(id).innerHTML = val + "MB";
    } else if (id == "RAMperc") {
    document.getElementById(id).value = val;

    } else {
    document.getElementById(id).innerHTML = val;
    }
    }
    })
    telemetryTimer = setTimeout(get_telemetry, 2000);
    });
    }
    [/php]

    In dieser JS Datei sind erst mal alle Voraussetzungen gegeben um " /data/ " und " /cmd/ " zu nutzen.
    Die Sachen ganz oben sind hilfreiche Funktionen, wie zum Beispiel "isset".
    Nach dem horizontalen Strich ist oben ein Teil noch auskommentiert, welcher bewirkt das nach laden der Seite nach 1000ms (1sec) die Funktion "get_telemetry()" ausgeführt und /data/ angesprochen wird. Das wird von dieser Funktion dann alle 2000ms wiederholt und vom Pythonscript zurückgegebene JSON Daten entsprechend verarbeitet..
    "Send" ist unsere Funktion um Befehle zu senden.

    Jetzt müssen wir auch noch die index.html anpassen bzw erweitern und bauen auch gleich zwei Button zum testen der "Send" Funktion ein:

    index.html
    [code=php]
    <!DOCTYPE html>
    <html>
    <head>
    <script>
    var DEBUG = {{ debug }};
    </script>
    <script src="/static/jquery.min.js" type="text/javascript"></script>
    <script src="/static/control.js" type="text/javascript"></script>
    </head>
    <body>
    Test: {{ test }}
    <br/>
    <input type="button" value="RAM" onClick="Send('PiRAM')" />
    <span id="PiRAM"></span>
    <br/> <input type="button" value="CPU Temperatur" onClick="Send('PiTEMP')" />
    <span id="PiTEMP"></span> &deg;C
    <br/>
    <p><div id="Log" style="text-align:left; width:100%;">&nbsp;</div></p>
    </body>
    </html>
    [/php]


    Nun zu dem Python Script:
    Als Basis nehmen wir das web_bottle.py Script aus dem [al=chapter1]1.Abschnitt[/al] und ergänzen dieses um die obigen 2 Python Funktionen wobei wir die CommandHandler Funktion erweitern um auf unseren Button zu reagieren:

    web_bottle.py => http://codepad.org/52oDG5ES
    [code=php]
    from __future__ import print_function
    import os.path
    import bottle
    import json
    from random import randrange

    DEBUG = 1

    bottle.debug(True) #sollte spaeter ausgeschaltet werden!
    bottle.TEMPLATE_PATH.insert(0, os.path.join(os.path.dirname(__file__), 'templates'))

    def printD(message):
    if DEBUG:
    print(message)

    def getPiRAM():
    with open('/proc/meminfo', 'r') as mem:
    tmp = 0
    for i in mem:
    sline = i.split()
    if str(sline[0]) == 'MemTotal:':
    total = int(sline[1])
    elif str(sline[0]) in ('MemFree:', 'Buffers:', 'Cached:'):
    tmp += int(sline[1])
    free = tmp
    used = int(total) - int(free)
    usedPerc = (used * 100) / total
    return free

    def getPiTemperature():
    with open("/sys/class/thermal/thermal_zone0/temp", 'r') as f:
    content = f.read().splitlines()
    return float(content[0]) / 1000.0

    @bottle.route('/')
    def MainHandler():
    values = {
    'test': randrange(1, 1000),
    'debug': DEBUG,
    }
    return bottle.template('index.html', values)

    @bottle.route('/static/<filename>')
    def StaticHandler(filename):
    if filename.endswith(".css"):
    bottle.response.content_type = 'text/css'
    elif filename.endswith(".js"):
    bottle.response.content_type = 'text/javascript'
    elif filename.endswith(".png"):
    bottle.response.content_type = 'image/png'
    return bottle.static_file(filename, root=os.path.join(os.path.dirname(__file__), 'static'))

    @bottle.route('/data/')
    def TelemetryHandler():
    printD("Telemetry Request.")
    bottle.response.content_type = 'application/json'
    data = {}
    data["PiTEMP"] = getPiTemperature()
    data["PiRAM"] = getPiRAM()
    return json.dumps(data)

    @bottle.route('/cmd/<command>')
    def CommandHandler(command):
    response = ''
    printD("Command Request: {}".format(command))
    if command == 'PiTEMP':
    response = getPiTemperature()
    elif command == 'PiRAM':
    response = getPiRAM()
    printD("response: %s"% response)
    return str(response)

    @bottle.error(404)
    def error404(error):
    return 'Error 404: Nothing here, sorry.'

    try:
    bottle.run(host='0.0.0.0', port=8080, quiet=True)
    except (KeyboardInterrupt, SystemExit):
    print('\nQuit\n')
    [/php]

    Hier gibts eine Besonderheit, die Ihr nicht auch so handhaben müsst:
    CommandHandler ist dafür vorgesehen Befehle vom Web-Interface zu empfangen und zu verarbeiten. Ich habe hier in diesem Beispiel eingebaut das durch den Befehl PiTEMP die CPU-Temperatur ausgelesen und wieder zurück gegeben werden soll - das müsst ihr so nicht machen, es muss also nicht unbedingt etwas zurück gegeben werden. Wenn muss in diesem Fall aber eine entsprechende "id" in der Webseite vorhanden sein um dort die Antwort einzufügen.

    Wenn ihr also nun das web_bottle.py ausführt, http://192.168.0.10:8080/ ansurft und auf den "CPU Temperatur" Button klickt, wird im Hintergrund die URL "/cmd/PiTEMP" aufgerufen, das Python Script liest die CPU Temperatur aus und gibt das Ergebnis zurück, woraufhin der Wert in <span id="PiTEMP"></span> eingefügt wird.

    Wenn ihr in der control.js den erwähnten auskommentieren Bereich wieder einkommentiert, würde kontinuierlich (alle 2000ms also 2 Sekunden) /data/ abgefragt werden
    [code=php]
    var telemetryTimer;
    $(document).ready(function() {
    // start Main Timers
    telemetryTimer = setTimeout(get_telemetry, 1000);
    });
    [/php]Das wird automatisch ausgeführt sobald die Seite "ready" ist und nach 1 Sekunde die JavaScript Funktion get_telemetry ausgeführt. Anschließend wird die Funktion alle 2 Sekunden aufgerufen.


    [al=index]=> Back to Index <=[/al]

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!