Programm -> Wert -> Websocket -> Webseite

L I V E Stammtisch ab 20:30 Uhr im Chat
  • Hallo liebe Community,

    ich möchte gern Daten aus meinem Programm in Echtzeit über eine Web-Oberfläche angezeigt bekommen.
    Wichtig hierbei war mir, den Browser nicht zu aktualisieren, um zu sehen, was sich verändert hat, sonder wie gesagt in Echtzeit.

    Nach längerem Suchen bin ich auf die Websockets gestoßen, die mir sowas ermöglichen können.

    Es gibt auch ein Paar Tutorials, die allerdings nicht so funktionieren, wie ich es quasi bräuchte.

    Der Verlauf der Daten sieht also folgendermaßen aus:
    Programm -> Wert -> Websocket -> Webseite

    Als Anfang habe ich mir gedacht, ich schreibe ein kleines Programm, welches ein Counter immer höher zählt.
    Jede Sekunde wird also 1 dazu addiert.
    Diesen Wert möchte ich mir nun über eine Webseite darstellen lassen.

    Ich benötige also den Websocket-Server Code (Python) und den HTML-Code (html-Webseite).


    Kann mir da jemand Tipps für geben, um eben einen bestimmten Wert über den Websocket auf eine Webseite zu bringen?

    Vielen Dank im Vorraus :)

  • Hallo,

    je nach dem, was das tatsächliche Ziel ist, ist das mit AJAX einfacher zu realisieren. Wobei es dann nicht wirklich "Echtzeit" ist, weil das JavaScript auf der Webseite sich periodisch die (neuen) Daten vom Server zieht. D.h. du hast in der Regel ca. 1 Sekunde Verzögerung .

    Zitat

    Als Anfang habe ich mir gedacht, ich schreibe ein kleines Programm, welches ein Counter immer höher zählt.
    Jede Sekunde wird also 1 dazu addiert.
    Diesen Wert möchte ich mir nun über eine Webseite darstellen lassen.


    Na ja, dazu reicht ja nacktes JavaScript im Browser, ohne AJAX oder Websocket ;)

    Kannst du eigentlich JavaScript? Das brauchst du nämlich Client-seitig.


    Gruß, noisefloor

  • Oder noch einfacher im Template ``meta refresh`` hinzufügen:

    Code
    <meta http-equiv="refresh" content="5">

    Das würde aber die gesamte Seite ständig neu laden, was der TE aber nicht möchte da das einem "Browser aktualisieren" gleich käme.

    Bei setInterval() im JavaScript kann man Millisekunden angeben, also auch einzelne Elemente/Werte in nahezu Echtzeit aktualisieren. Er brauch nur eine WebSocket Schnittstelle zwischen JavaScript und seinem Programm, in etwa so:

    websocket.js
    [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);
    }

    // http://stackoverflow.com/questions/4539…-is-console-log
    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 ws;
    var ws_status = "closed";
    function set_ws_status(status) {
    ws_status = status;
    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");
    GetDefaultSettings();
    }
    ws.onclose = function(evt) {
    if (isset(evt.reason)) {
    mylog('Connection closed:'+evt.reason);
    } else {
    mylog('Connection closed!');
    }
    set_ws_status("closed");
    receivedDefault = 'NONE';
    }
    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+'<<<');
    }
    }

    function init() {
    // Initalize the various page elements here...
    WebSocket_Open();
    setInterval(SystemStatusUpdate, 500);
    }

    function Robot_SystemStatusUpdate() {
    if (ws_status == "opened") {
    WebSocket_Send('get.system.loadavg');
    WebSocket_Send('get.system.uptime');
    WebSocket_Send('get.system.ram');
    }
    }

    //----------------------------------------------------------------
    // Parse Response from/over WebSocket
    //----------------------------------------------------------------

    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];
    value = requestsplit[1];
    value2 = requestsplit[2];

    if( command == "localping" )
    if( value == "ok" )
    pingCtr = 5;

    if( command == "get.system.loadavg" ) {
    document.getElementById("LoadAVGnum").innerHTML = value;
    document.getElementById("LoadAVGperc").value=parseFloat(value)*100;
    }
    if( command == "get.system.ram" ) {
    document.getElementById("RAMnum").innerHTML = parseFloat(value)+"%";
    document.getElementById("RAMperc").value=parseFloat(value);
    }
    if( command == "get.system.uptime" ) {
    document.getElementById("uptime").innerHTML = requestsplit[1]+":"+requestsplit[2]+":"+requestsplit[3];
    }
    }
    }
    [/php]

    index.php
    [code=php]
    <?php
    $DEBUG=1;
    error_reporting(E_ALL);
    ini_set('track_errors', 1);
    ini_set('display_errors', 1);
    ini_set('log_errors', 1);
    ini_set("session.gc_maxlifetime", 3600); // default: 60 min
    define('TIMEOUT', 3600); // default: 60 min
    define("TEMP_PATH", "/tmp/");
    session_cache_limiter('public');
    session_cache_expire(3600);
    ini_set("session.gc_maxlifetime", 3600); // default: 60 min
    ini_set('default_socket_timeout', 5);
    /*
    //needed to fix STRICT warnings about timezone issues
    $tz = exec('date +%Z');
    @date_default_timezone_set(timezone_name_from_abbr($tz));
    ini_set('date.timezone',@date_default_timezone_get());
    */
    $S="&nbsp;";
    $s=" ";
    $_SELF=$_SERVER['PHP_SELF'];
    ob_implicit_flush(true);
    @ob_end_flush();
    ?>

    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="robots" content="DISALLOW">
    <meta charset="utf-8">
    <title>WebSocket</title>
    <style type=text/css>
    body { font-family:monospace; font-size:14px; }

    input.vertical {
    -webkit-appearance: slider-vertical; writing-mode: bt-lr;
    border:0; color:#f6931f; font-weight:bold;
    }
    .divtable { display: table; border-spacing: 5px 5px; }
    .divtablecell { display: table-cell; padding: 5px; border: 0px; }
    .divtablecellborder { display: table-cell; padding: 5px; border: 1px solid gray; border-radius: 5px; }
    #box { }
    #contentBox {
    display:inline-block;
    border: 1px solid black;
    margin-left: auto;
    margin-right: auto;
    text-align: left;
    border-radius: 5px;
    box-shadow: 0px 0px 25px #cccccc;
    }
    #Connection { vertical-align: top; }
    #Log {
    letter-spacing:0px;
    font-family: verdana, arial, helvetica, verdana, tahoma, sans-serif;
    font-size:14px;
    text-align:left;
    font-weight:normal;
    }
    </style>
    <script>
    var DEBUG = <?php echo $DEBUG; ?>;
    </script>
    <script src="websocket.js"></script>
    </head>
    <body onLoad='init();'>
    <div style="text-align:center; width:100%; margin-top: 30px;">
    <div id="contentBox">
    <!--- Top Table --->
    <div class="divtable">
    <div id="Connection" class="divtablecellborder">
    <b>Connection:</b>
    <input type="button" value="open" onclick="WebSocket_Open();" />
    <input type="button" value="close" onclick="WebSocket_Close();" />
    <br/> <b>Status:</b> <span id="connectionStatus" >closed</span>
    <br/> <b>Ping:</b> <span id="pingSymbol" >·</span>
    <br/> <b>Power:</b> <input type="button" value="shutdown" onclick="WebSocket_Send('set.system.power:off')" />&nbsp;<input type="button" value="reboot" onclick="WebSocket_Send('set.system.power:reboot')" />
    <br/><br/><br/><br/>
    <b>System:</b> <br/>
    &nbsp; LoadAVG: <progress id="LoadAVGperc" value="0" max="100"></progress> <span id="LoadAVGnum" ></span> <br/>
    &nbsp; RAM: &nbsp; &nbsp; <progress id="RAMperc" value="0" max="100"></progress> <span id="RAMnum" >%</span> <br/>
    &nbsp; Uptime: &nbsp;<span id="uptime" >__:__:__</span> <br/>
    </div>
    </div>
    </div>
    </div>
    <div id="foot">
    <center><p>Copyright 2014 meigrafd <a href="http://goo.gl/I2e5bu" target="new">RoPi</a></p></center>
    </div>
    <?php if (isset($DEBUG) AND $DEBUG == 1) { ?>
    <div id="Log" style="text-align:left; width:100%;">&nbsp;</div>
    <?php } ?>
    </body>
    </html>
    [/php]

    websocket.py
    [code=php]
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    #
    # Starts tornado for Reading/Writing WebInterface <-> websocket.py
    #
    # DONT use CTRL+Z to terminate script, else Port is blocked
    #
    ###
    # import the libraries
    from __future__ import print_function
    import tornado.web
    import tornado.websocket
    import tornado.ioloop
    from datetime import timedelta
    import time
    import threading
    import sys
    import subprocess
    #-------------------------------------------------------------------
    DEBUG = True
    #DEBUG = False
    WebSocketPort = 7070
    #-------------------------------------------------------------------
    # thread docu: http://www.tutorialspoint.com/python/python_multithreading.htm
    # tornado & serial docu: http://forums.lantronix.com/showthread.php?p=3131
    # psutil: https://github.com/giampaolo/psutil
    #-------------------------------------------------------------------

    try:
    DEBUG
    except NameError:
    DEBUG = False

    if DEBUG:
    print("Enabling DEBUG mode!")
    else:
    print("Disabling DEBUG mode!")

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

    #http://www.gossamer-threads.com/lists/python/python/375364
    def awk_it(instring,index,delimiter=" "):
    try:
    return [instring,instring.split(delimiter)[index-1]][max(0,min(1,index))]
    except:
    return ""

    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

    ### 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 == "get.system.loadavg":
    returnlist += "\n get.system.loadavg:"+open("/proc/loadavg").readline().split(" ")[:3][0]
    elif command == "get.system.uptime":
    returnlist += "\n get.system.uptime:"+str(getUptime()).split(".")[0]
    elif command == "get.system.ram":
    returnlist += "\n get.system.ram:"+str(getPiRAM())
    #returnlist += "\n get.system.ram:"+str(psutil.phymem_usage().percent)
    elif command == "set.system.power":
    if value == "off":
    subprocess.Popen(["shutdown","-h","now"])
    return "set.system.power:ok"
    elif value == "reboot":
    subprocess.Popen(["shutdown","-r","now"])
    return "set.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),
    ])


    if __name__ == "__main__":
    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..")
    exit()
    [/php]


  • Hallo,

    bootsmann: könnte bei `meta refresh`dann nur schwierig werden, den Zähler zum Hochzählen zu bewegen ;)

    Gruß, noisefloor

    Ich dachte, der Themaerstelle möchte einfach nicht manuell den Browser aktualisieren. Aber wenn dieser Effekt nicht gewünscht wird, dann ist ``http-equiv="refresh"`` das falsche...

  • Danke schonmal für die zahlreichen Antworten von Euch! :bravo2:

    Ich möchte quasi als Test folgendes Programm in Python schreiben:
    Ein Zähler, beginnend bei 0, addiert (z.B. jede Sekunde) den Wert 1 dazu.
    Dieses Programm braucht dann ja noch einen Websocket-Server.

    Dazu möchte ich eine HTML-Webseite schreiben, die mir einfach nur den aktuellen Wert des Zählers im Programm anzeigt.

    Kommen wir mal zu dem Punkt "Echtzeit".
    Mit Echtzeit meine ich, wenn sich der Wert im Programm ändert, dass sich auch der Wert auf der HTML-Webseite ändern soll.
    Und das, ohne den Browser aktualisieren zu müssen (-> Vorteil von Websockets)

    Man kann ja mit reinem HTML auch Websocket-Clients programmieren, was ich in meinem Fall quasi bräuchte.
    Und das Gegenstück (also der Websocket-Server) auf dem Raspberry Pi mit dem Programm, welches den Wert an den Websocket übergibt, in Python.

    Mir fehlen zwar Kenntnisse in Javascript & Python, die ich mir allerdings versuche anzueignen.
    Ich bin eher der "Learning by Doing-Typ", weshalb mir ein nackter Python-Code mit den oben genannten Aufgaben + der nackte HTML-Code zur Visualisierung sehr helfen würde.
    Wichtig für mich, dass nix außer diese o.g. Aufgabe in den Codes stecken, da ich alles so am besten nachvollziehen kann.


    Nachdem ich diese Codes verstanden habe, soll das ganze von mir für ein Techniker-Abschlussprojekt in einen Solar-Tracker (Sonnenfolger) umgeschrieben werden.
    Die zu visualisierenden Werte sind dann die Spannung und der Strom der Solarzelle.
    Das Programm dazu ist quasi schon fertig, jedoch soll jetzt eben noch der Punkt mit der Visualisierung via Webseite eingebaut werden.

    :)

    Einmal editiert, zuletzt von TRiViUM (28. September 2015 um 15:58)

  • Noch mal die Möglichkeiten zusammengefasst:

    • Wert von einem Script in eine Datei schreiben und...

      • ...diese Datei dann von der Webseite auslesen + angezeigt sowie die ganze Seite automatisch neu laden lassen (refresh).
      • ...mithilfe von AJAX -- wahlweise einen iframe oder einen div (oder ein ähnliches Element) oder den Inhalt eines anderen PHP Scripts -- einen Teil-Bereich aktualisieren lassen.
    • Wie von mir beschrieben über JavaScript und Websocket sowie in Python tornado.
    • Der von bootsmann beschriebene Weg: Webseite mithilfe von bottle unabhängig eines extra Webservers wie apache2 o.ä. generieren und die ganze Seite automatisch neu laden lassen (refresh).


    Da das aber anscheint Bestandteil deiner Prüfung sein soll, würde ich vorschlagen das du dich selber bemühst nach entsprechenden Beispielen zu suchen - das prägt den Lernfaktor weitaus mehr als irgend etwas vorgekaut zu kriegen aber letztlich nicht zu wissen Wieso man sich ausgerechnet für Diesen Weg entschieden hat ...

    Aber ein Tip am Rande: Der einfachste Weg ist selten der beste.

  • Naja das kommt darauf an wie Du es gerne haben möchtest...

    Wenn du kaum Ahnung aber Zeitdruck hast, wird es vermutlich der einfachste aber zugleich schlechteste Weg (1.). Schlecht deshalb weil a) dadurch die SD unnötig belaste und b) das Logfile zugemüllt wird was ebenfalls a) zur Folge hat.
    Und welchen ich bevorzuge nannte ich bereits in 2 Beiträgen :D
    Es gibt vielleicht noch 1-2 andere Möglichkeiten, aber mir ist keine in reinem/purem HTML bekannt, nur mit mind. JavaScript.


  • Naja das kommt darauf an wie Du es gerne haben möchtest...

    Wenn du kaum Ahnung aber Zeitdruck hast, wird es vermutlich der einfachste aber zugleich schlechteste Weg (1.). Schlecht deshalb weil a) dadurch die SD unnötig belaste und b) das Logfile zugemüllt wird was ebenfalls a) zur Folge hat.
    Und welchen ich bevorzuge nannte ich bereits in 2 Beiträgen :D
    Es gibt vielleicht noch 1-2 andere Möglichkeiten, aber mir ist keine in reinem/purem HTML bekannt, nur mit mind. JavaScript.

    Das mit der Zeit ist die eine Sache, aber ich möchte es dann doch schon gern gescheit machen :p
    Ich werde mir deine 3 Dateien mal genauer anschauen.
    Ich brauche ja nur den Teil, in dem der Server Daten zur Webseite schickt.
    Die Webseite muss ja nix zurückschicken/ antworten. Sie hat nur die reine Anzeige-Funktion.

    Danke Dir schonmal für die Snippets :thumbs1:

  • Naja es muss schon anders herum ablaufen: Die Webseite fragt die Daten vom Programm ab. Anders wird das IMHO nichts, da die Webseite ja letztlich beim Client "läuft", also JavaScript läuft Clientseitig und PHP Serverseitig wovon der Client nichts sieht/merkt


  • Naja es muss schon anders herum ablaufen: Die Webseite fragt die Daten vom Programm ab. Anders wird das IMHO nichts, da die Webseite ja letztlich beim Client "läuft", also JavaScript läuft Clientseitig und PHP Serverseitig wovon der Client nichts sieht/merkt

    Bitte korrigiere den von Dir geposteten Code (websocket.py), denn ich bekam auf dem Server immer die Meldung "WARNING:tornado.access:403 GET / (192.168.137.1) 13.28ms" und konnte keine Verbindung aufbauen...

    Und zwar muss in die Klasse "WebSocketHandler" folgende Zeilen ergänzt werden:

    Code
    def check_origin(self, origin):
    return True

    Und in Ganzen:

    Desweiteren bleiben alle Anzeigen leer (LoadAVG, RAM, Uptime), wo ich momentan am rätseln bin, wie ich diese Werte dauernd aktualisiert bekomme, und nicht über nen Button...

    Einmal editiert, zuletzt von TRiViUM (28. September 2015 um 22:26)

  • Ach genau da fällt mir grad just noch eine Möglichkeit ein, unabhängig von WebSocket, quasi sowas wie bootsmann meinte nur ohne bottle, die ich seinerzeit beim 'Windows RPI Gadget' genutzt hab:

    Alex
    15. Oktober 2013 um 09:34

  • Ach genau da fällt mir grad just noch eine Möglichkeit ein, unabhängig von WebSocket, quasi sowas wie bootsmann meinte nur ohne bottle, die ich seinerzeit beim 'Windows RPI Gadget' genutzt hab:

    [Win 7/8] Pi Temperaturanzeige auf dem Desktop

    Wie kann ich es denn in deinem 3-Teiler (php, Javascript & Python) schaffen, dass er mir z.b. die CPU-Auslastung immer anzeigt, also das ganze automatisiert ablaufen lässt?
    Denn bei mir verbindet sich lediglich der Client mit dem Server, aber ich sehe keine CPU-Auslastung bzw RAM-Benutzung.

    ~Greez

  • Auf was bezieht sich diese Frage denn nun? Das von dir zitierte oder dem aus Beitrag#6 ? :s

    Beide Varianten funktionieren Autark und Automatisch. Man darf halt nur nicht blindes Copy&Paste machen - da lernste ja nix bei...

    Es ist ein "absichtlicher Fehler" eingebaut - das was du gestern gepostet hast war aber kein "absichtlicher Fehler" sondern eine Änderung seitens tornado beim Wechsel von v3.x auf v4.x


  • Es ist ein "absichtlicher Fehler" eingebaut

    hat sich schon auf die Code-Snippets von Dir bezogen.
    Dachte ich mir schon, nur wollte Dich nicht gleich beschuldigen :D

    Also der Fehler lag daran, dass die Funktion "Robot_SystemStatusUpdate" im Javascript aufgerufen wird, obwohl die Funktion mit "SystemStatusUpdate" aufgerufen werden muss. :^^:
    Was aber jetzt auch so aussieht wie bei Dir ist, dass bei "Ping" ein Fragezeichen in einem schwarzen Karo steht.
    Formatierungsfehler oder auch noch was im Script zu ändern?

  • Genau das war der 'Fehler' ;)

    'Ping' hab ich nur vergessen aus der index.php raus zu nehmen, dafür ist im websocket.js ja auch keine Funktion vorhanden (welche ich rausgelöscht hab) - würde aber auch nur 'Error' oder 'Ok' anzeigen. Und ursprünglich sollte da der HTML-Code &'#183;' stehen (ohne '), was ein gemittelter Punkt ( · ) sein sollte, aber das hat anscheint das Forum trotz [ php] umgewandelt :-/
    Die Zeile kannste also aus der index.php auch raus nehmen bzw willst du ja vermutlich sowieso eine eigene Anzeige bauen oder nicht?

  • Achso, ok.
    Ja, hatte ich auch schon vorher rausgelöscht :D

    Ja, habe vor mir meine eigene anzeige bauen.
    Ich habe zwar keine PHP-Vorkenntnisse, hoffe aber auf meine schnelle Auffassungsgabe und zahlreiche Tutorials.

    Mal am Rande gefragt:
    Ich möchte mir über das Web-Interface 2 Werte von der Solarzelle anzeigen lassen, Spannung und der Strom.
    Aus der Digital/ Elektrotechnik kennt man wahrscheinlich die kleinen 7-Segment-Anzeigen.
    So eine Schriftart gibt es auch für den Computer.
    Sowas hättei ich auch gern in meiner Webseite.

    Um das Dokument dann auch mit der Schriftart angezeigt zu bekommen, muss diese dann auf dem aufrufendem PC installiert sein?

    So ein Webinterface wäre ganz cool: Klick (nur ohne die Elemente rechts neben den Zahlen)

    Das, was du mit der contentBox gemacht hast, finde ich schon recht cool.
    Nur da muss ich mir noch was einfallen lassen (bzgl. Farben etc.).

    Einmal editiert, zuletzt von TRiViUM (29. September 2015 um 19:55)

Jetzt mitmachen!

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