Entwicklung: RoPi - Autonomer Roboter mit RaspberryPI

Heute ist Stammtischzeit:
Jeden Donnerstag 20:30 Uhr hier im Chat.
Wer Lust hat, kann sich gerne beteiligen. ;)
  • nginx ist der optionale Webserver für das optionale Webinterface. Tornado ist im dazugehörigen optionalen Python Script um einen WebSocket bereitzustellen. Also das WebInterface kommuniziert via JavaScript und somit WebSocket mit dem Python Script und dieses wiederum via Socket mit dem Haupt-'TheBrain'-Script. Man kann also diesen optionalen Part auch auf einem anderen Pi (oder allgemein Rechner) laufen lassen, ohne den Roboter selbst damit zu belasten.

    Den ablauf habe ich hier bzw hier beschrieben.

  • Entwicklung: RoPi - Autonomer Roboter mit RaspberryPI? Schau mal ob du hier fündig wirst!

  • Angeregt durch Wolfgang Glück's Projekt habe ich mich mal wieder intensiver mit dem Projekt beschäftigt und festgestellt das der von mir gewählte A+ tatsächlich für mein Vorhaben nicht ausreicht...
    Zwar ist der Stromverbrauch wirklich niedrig, allerdings reicht weder RAM noch CPU wirklich aus, insbesondere RAM ist bei Verwendung mit der RaspiCam ziemlich schlecht betucht, schließlich benötigt diese mind. 128MB RAM um flüssige Videos darzustellen aber dann blieben nur noch 128MB RAM für die Roboter-Scripts sowie PathFinding... :(
    Aber auch die CPU Leistung ist mit nur einem Core recht mager... Zwar reichte das bisher noch aus, spätestens wenn PathFinding bzw Mapping dazu kommt ist der mit allem aber schon etwas überfordert.

    Ich hab mich also nun entschlossen auch einen Pi-2 zu verwenden, ausgestattet mit einem Raspbian-Lite und der Modifikation das ich die letzten 2 Cores isoliere, Exklusiv nutzbar nur für die Roboter-Scripts: Prozess/Tasks einem bestimmten Kern zuordnen
    Der Hintergrund ist der, das man mit Multithreading oder processing nicht exklusiv nur die Scripts auf dem Core laufen lassen kann, sondern dann weiterhin der Kernel CPU-Last von anderen Programme auf diese Cores legen kann. Durch die Isolation wird das verhindert sodass allgemein Linux (also die ganzen Dienste und Programme wie systemd, nginx, ssh usw) nur noch auf die ersten beiden Cores Zugriff hat und ich meine Roboter-Scripts dann mithilfe 'taskset' auf die anderen beiden Cores auslagere. Durch diese Maßnahme sieht man dann auch sehr gut die tatsächliche Auslastung des Scripts und jeder Takt führt tatsächlich eine Operation des Scripts aus ;)
    Aufgrund der Verzögerung beim Wechsel von 600MHz auf 900MHz habe ich zudem auch 'force_turbo=1' eingestellt. Da bemerke ich bereits bei modernen PC's einen Unterschied also wirds vermutlich auch beim Pi zu merken sein (Annahme).

    Was den Stromverbrauch betrifft weiß ich aber noch nicht wie sich das auswirkt. Aufgrund der Aussage von Wolfgang Glück, dass der Arduino nur über USB versorgt, nicht genug Strom zur Verfügung hat; werde ich bei mir aber auch noch mal den Aufbau des Gefährts überarbeiten und meine PowerBank nicht direkt an den Pi anschließen sondern erst auf eine Adapterplatine-marke-eigenbau und von dort auf die beiden Devices aufteilen. Ich wollte nämlich auch noch weitere US-Sensoren verbauen also wäre es spätestens dann fällig. Aktuell habe ich nur einen HC-SR04 Ultraschall-Sensor auf dem Pan/Tilt, hatte mir aber bereits 2014 weitere 5 davon bestellt und zusätzlich auch noch einen US-020, welcher eine Reichweite von 7m hat der soll dann stattdessen auf den Pan/Tilt.

    Nunja, mehr dazu in nächster Zeit ;)

  • Ein kleiner Zwischenbericht meinerseits - hab an diesem Projekt lange nichts mehr gemacht... :blush:

    Habe nun begonnen den Code - inspiriert durch Wolfgang Glück's Projekt - komplett neu zu schreiben. Ziel ist eine höhere Modularität sowie die direkte Integration des Web-Interfaces in Python um unabhängig eines Webservers wie nginx zu sein... Das interessiert mich sowieso ;)

    Desweiteren habe ich den RaspberryPi A+ nun durch einen RaspberryPi 2B ersetzt um mehr Rechenleistung zu haben - langfristig betrachtet reicht der A+ alleine schon aufgrund von nur 256MB RAM nicht aus.
    Ursprünglich wollte ich mehrere Scripts, wie bisher auch, über Socket miteinander kommunizieren lassen, und dann mithilfe von psutil das jeweilige Script explizit einem Core zuweisen. Das hatte ich soweit auch schon umgesetzt und funktioniert auch super, allerdings gefällt mir das noch nicht ;)

    Spoiler anzeigen

    [code=php]import psutil
    Core_Affinity = [2] # CPU Cores: 0,1,2,3
    p = psutil.Process()
    p.cpu_affinity(Core_Affinity) # from now on, this process will run on CPU-Core #3 only
    [/php]

    Auch habe ich bisher verstärkt auf Threading gesetzt, habe aber zwischenzeitlich recherchiert das man das möglichst vermeiden sollte, da mehrere Threads letztlich langsamer verarbeitet werden als tatsächlich einzelne Prozesse. Also nutze ich jetzt ebenso wie Wolfgang: multiprocessing

    Wolfgang nutzt das normale queue Module, was auch in multiprocessing integriert ist - allerdings muss man die jeweiligen Instanzen vorher erzeugen und an die einzelnen Classes übergeben damit die darauf zugreifen und untereinander "kommunizieren" können. Auch fand ich seinen Weg etwas umständlich die jeweiligen queue Objekte der Class zu übergeben... Anfangs hatte ich das zwar auch so probiert wie Wolfgang es gemacht hat, hatte dann aber einen einfacheren Weg gefunden indem ich ein Dictionary mit den queue-Objects erzeugt hab und nur dieses Dictionary der Class übergeben habe - dadurch konnte ich in der Class dann auf die benötigten queue Objects zugreifen und hatte bei späterer Erweiterung nicht so viel anzupassen.

    Allerdings bin ich von dem Standard queue nun ebenfalls weg weil ich was besseres gefunden habe: pystalkd
    Das ist einfach genial und ziemlich schnell. Es läuft einfach ein Dienst im Hintergrund (beanstalkd) wohin sich die einzelnen Scripts verbinden und auf Queues zugreifen können. Man brauch also nur dafür zu sorgen das dieser Dienst bei Systemstart mit startet und lässt jede Class selber eine Verbindung dort hin herstellen. Sowas ähnliches gibt es zwar auch mit multiprocessing auf Basis des normalen queue (> via BaseManager <) aber das läuft nicht so gut und schnell.
    Außerdem bietet es ebenso wie PriorityQueue die Möglichkeit die Queues zu Prioritisieren. Dh wenn wichtige Events stattfinden wie zum Beispiel "Notaus" kann man diesem Job eine höhere Priorität zuweisen und läuft somit nicht Gefahr dass erst andere abgearbeitet werden.. Auch muss man Jobs's explizit löschen, was für mich den Vorteil hat das mehrere Funktionen die selben Jobs abrufen können also weniger Traffic erzeugt wird.

    Beispiel:

    Spoiler anzeigen

    nur ein grober Auszug[code=php]from pystalkd import Beanstalkd

    # Command and control intercom
    intercom = {
    "options":options, # Options from the Command Line
    "pins":pins, # Pin assignments on the Arduino
    "mapping":mapping, # Stuff for the Mapping module

    "streaming":True, # Camera Streaming

    "statusMessage":"",
    "restart":False,
    "shutdown":False,

    "pystalkPort":11331,
    "pystalkHost":"127.0.0.1",
    "qPrioL":10, # pystalk Queue Priority Level: Low
    "qPrioM":5, # pystalk Queue Priority Level: Mid
    "qPrioH":1, # pystalk Queue Priority Level: High
    }

    Beanstalkd.DEFAULT_PRIORITY=intercom["qPrioM"]

    # ....

    arduino = interfaceClass('/dev/' + intercom["options"].serialPort)

    utility = utilityClass(arduino, intercom)
    utility.logger("debug", "Hardware interface has started.")

    queue = Beanstalkd.Connection(host=intercom["pystalkHost"], port=intercom["pystalkPort"])
    try:
    if queue.stats_tube('Utility')['current-jobs-ready']:
    utility.logger("debug", "Utility purged of " + \
    str(utility.queuePurge("Utility")) + " entries.")
    except:
    pass

    # Queues:
    # Telemetry => Sensor Werte (auch fuers webif)
    # ControlCommand => zu verarbeitende Befehle
    # Utility => zu Steuerung diverser interner Tools
    [/php]

    [code=php]
    import os
    import sys
    import time
    import logging
    import subprocess
    import RPi.GPIO as GPIO
    from pystalkd import Beanstalkd

    class utilityClass(object):
    """
    Provides various utility functions for the robot.
    """
    # Arduino interface object passed via init
    arduino = ""
    intercom = {}
    # Message queue used to communicate with background process
    queue = ""
    pythonLogger = ""

    def __init__(self, arduino, intercom):
    self.arduino = arduino
    self.intercom = intercom

    # Connect to queue server
    self.queue = Beanstalkd.Connection(host=intercom["pystalkHost"], port=intercom["pystalkPort"])

    # Setup logging by connecting to our main logger
    self.pythonLogger = logging.getLogger('Main.' + __name__)
    self.logger("debug", "Initialized utility")

    # ------------------------------------------------------------------------
    def queuePurge(self, queueName):
    """
    Purges a queue of all contents
    """
    self.queue.watch(queueName)
    jobsDeleted = 0
    jobsReady = 1
    while jobsReady != 0:
    try:
    jobsReady = self.queue.stats_tube(queueName)['current-jobs-ready']
    except:
    jobsReady = 0
    if jobsReady != 0:
    job = self.queue.reserve()
    job.delete()
    jobsDeleted += 1
    return jobsDeleted

    # ------------------------------------------------------------------------
    def disableStream(self):
    """
    Disable streaming of webcam
    """
    self.queue.use("Utility")
    self.queue.put("disableStream")
    self.logger("debug", "Disable stream command queued for action")
    self.intercom["streaming"] = False

    # ------------------------------------------------------------------------
    def enableStream(self):
    """
    Enable streaming of webcam
    """
    self.queue.use("Utility")
    self.queue.put("enableStream")
    self.logger("debug", "Enable stream command queued for action")
    self.intercom["streaming"] = True
    [/php]Meine Python Skills sind mittlerweile auch etwas gestärkt, damals als ich mit dem Projekt anfing waren sie noch nicht so umfassend :lol:

    Die nächste größere Änderung betrifft die Implementierung des Webinterface's mithilfe von Tornado. Da bin ich derzeit noch bei meine alte Seite umzubauen - einige Verständnissprobleme muss ich aber auch noch recherchieren :daumendreh2:

    Desweiteren habe ich auch den Sketch für den Arduino geändert. Der Arduino wird nur noch die Pins ansteuern aber kaum noch Funktionalität mehr beinhalten. Die einzelnen Pins werden über den Raspberry konfiguriert, so gestaltet sich das äußerst flexibel da es nur noch in den Python Scripts eingestellt werden muss. Auch auslesen der Pins übernimmt der Pi. Einzig für die Encoder (Odometrie Sensoren) muss ich eine Funktion im Arduino behalten um die externen Interrupts verzeichnen zu können - das ist schneller als wenn sich der Pi darum kümmern würde.

    Zusätzlich werde ich auch noch ein 20x2 LCD Display an den Arduino anschließen um einige Statusmeldungen direkt anzuzeigen, da nicht immer das Web-Interface angeguckt werden soll.

    Und zu guter letzt erfährt auch das Konstrukt einen kleinen Umbau um den Akku besser anbringen zu können -> aufbocken, eine Etage hinzufügen. Auch kommen noch 4 UltraSchall Sensoren hinzu für die aber derzeit kein Platz ist.


    ..Alles in allem, wieder viel zu tun ;)

  • Noch ein kleiner Zwischenbericht...

    Habe mich die letzten Tage mit tornado beschäftigt und will nicht nur auf einen extra Webserver wie nginx verzichten sondern auch auf WebSocket - die meisten Lösungen welche ich gefunden habe verwenden zum aktualisieren und Senden weiterhin WebSocket, was ich aber irrsinnig finde da man die Daten ja schließlich direkt in Python bereits vorliegen hat...

    Nun habe ich eine brauchbare Lösung gefunden, indem über Tornado zwei verschiedene Handler verwende: /cmd/ und /data/
    Über /cmd/ kann ich Befehle absetzen die dann direkt im Python verarbeitet werden.
    Über /data/ kann ich telemetry Daten abrufen, wie die Werte der Sensoren.

    Für den Anfang habe ich mir ein Grundgerüst gebastelt um unabhängig vom Rest daran zu basteln und das will ich euch nicht vorenthalten ;)

    webserver.py

    Spoiler anzeigen

    [code=php]
    #!/usr/bin/python3
    import tornado.escape
    import tornado.httpserver
    import tornado.ioloop
    import tornado.options
    import tornado.web
    import logging
    import json
    from random import randrange


    class Application(tornado.web.Application):
    def __init__(self):
    handlers = [
    (r"/", MainHandler),
    (r"/data/(.*)", TelemetryHandler),
    (r"/cmd/(.*)", CommandHandler),
    ]
    settings = dict(
    debug=True,
    autoescape=None
    )
    tornado.web.Application.__init__(self, handlers, **settings)

    # Configure logging
    tornado.options.log_file_max_size = (1024**2)*10
    logging.getLogger().setLevel(logging.INFO)
    tornado.log.enable_pretty_logging()


    def get_data():
    return randrange(1, 1000)


    def process_cmd():
    return randrange(1001, 9000)


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


    class TelemetryHandler(tornado.web.RequestHandler):
    #called every time someone sends a GET HTTP request
    def get(self, request):
    print("Telemetry Request")
    out = { "id": "result", "value": get_data() }
    output = json.dumps(out) #Turns json dict into a str
    print(output)
    self.write(output)


    class CommandHandler(tornado.web.RequestHandler):
    #called every time someone sends a GET HTTP request
    def get(self, request):
    print("Command Request")
    out = { "id": "command", "value": process_cmd() }
    output = json.dumps(out) #Turns json dict into a str
    print(output)
    self.write(output)


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

    tornado.html

    Spoiler anzeigen

    [code=php]
    <html>
    <head>
    <title>Tornado test</title>
    <script src="https://code.jquery.com/jquery-1.12.0.min.js" type="text/javascript"></script>
    <script type="text/javascript">
    function Send(command) {
    $.ajax({
    type: "GET",
    url: "/cmd/",
    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() {
    $.ajax({
    type: "GET",
    url: "/data/",
    async: true,
    dataType: "JSON",
    success: function(obj) {
    console.log("JSON Data: " + obj.id + ":" + obj.value);
    document.getElementById(obj.id).innerHTML = obj.value;
    },
    error: function (jqXHR, textStatus, errorThrown) {
    clearTimeout(telemetryTimer);
    },
    });
    telemetryTimer = setTimeout(get_telemetry, 1000);
    }

    var telemetryTimer;

    $(document).ready(function() {
    telemetryTimer = setTimeout(get_telemetry, 1000);
    });
    </script>
    </head>
    <body>
    default: {{test}}<br/>
    <br/>
    <input type="button" value="↑ " style="height:60px; width:60px" onClick="Send('forward')" />

    <br/><br/>
    result: <span id="result"></span>
    <br/>
    command: <span id="command"></span>

    </body>
    </html>
    [/php]Man kann jquery-1.12.0.min.js auch lokal ablegen..

    Beide Dateien im selben Verzeichnis ablegen (egal wo).
    Beim starten des Webservers wird tornado.html in Python Code umgewandelt und {{test}} mit dem Rückgabewert der Funktion get_data() ersetzt. Die mit {{ }} eingesetzten Sachen muss man hier bereits einfügen, man kann aber auch " test = '', " setzen damit nichts eingefügt wird.
    Nachdem webserver.py gestartet wurde die RaspberryPi Adresse mit Port 8000 ansurfen.
    Was dann passiert ist recht simpel:
    Ein Timer wird gestartet welcher jede Sekunde /data/ anspricht was wiederum vom Webserver in TelemetryHandler behandelt wird. Dort erzeuge ich ein Dictionary mit den ganzen Telemetry Informationen, wandle es um in JSON und gebe es zurück bzw schreibe auf das Seiten-Objekt.
    Das Format des Dictionaries sieht einfach vor als Key die ID von zum Beispiel <div id=... oder <span id=... zu verwende und als Value der Wert. Eigentlich ganz einfach :daumendreh2:
    Die Webseite nimmt sich dann die Rückgabe, zerlegt die JSON Daten und aktualisiert nur den div/span etc mit entsprechender ID.
    Dadurch brauch nicht die ganze Seite ständig/vollständig neu geladen werden :cool: Sieht man auch am "{{test}}" der nur ein mal beim laden der Seite gesetzt wird aber alle anderen Werte verändern sich

    Funktioniert beeindruckend gut :D

    Achja: Solange "debug" auf True steht wird die html Datei bei Veränderung automatisch neu geladen, man muss also beim Entwickeln nicht ständig das Python Script restarten. Wenn man mit entwickeln fertig ist sollte man das aber ausschalten.


    //EDIT: Etwas verbessert, wodurch auch einfacher ersichtlich wird wie die json Daten ganz einfach erstellt werden können, und auch mit der jQuery Funktion $.getJSON hantiert wird:

    (ich poste jetzt nur die Änderungen)

    Spoiler anzeigen


    python
    [code=php]
    class TelemetryHandler(tornado.web.RequestHandler):
    def get(self, request):
    print("Telemetry Request")

    data = {}
    data["MotorSpeed"] = randrange(10, 250)
    data["distance"] = randrange(5, 500)
    data["gibtsnich"] = randrange(0, 10)

    output = json.dumps(data)
    print(output)
    self.write(output)
    [/php]

    javascript
    [code=php]
    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) {
    document.getElementById(id).innerHTML = val;
    console.log("JSON Data: " + id + ":" + val);
    }
    })
    telemetryTimer = setTimeout(get_telemetry, 2000);
    });
    }
    [/php]Wenn es auf der Seite kein Element mit der id gibt dann wird es auch nicht verarbeitet. So kann man einfach ein großes Dictionary übergeben ohne sich Gedanken darüber machen zu müssen ob man überhaupt alle Daten benötigt.
    (ansonsten gäbs ne Fehlermeldung von Javascript, und zum anderen will ich nicht extra noch ein Dictionary erstellen sondern direkt alle Telemetry Queues durchfeuern.)

    html
    [code=php]
    <br/><br/>
    MotorSpeed: <span id="MotorSpeed"></span><br/>
    distance: <span id="distance"></span><br/>
    command: <span id="command"></span>
    [/php]

  • na, da sind wir ja fast gleich weit, du bist einen Schritt voraus!

    Für die Kartierung habe ich mir folgendes überlegt:
    Für den Roboter ist es wichtig möglichst genau zu wissen wo er jetzt ist. Ich habe mir dazu folgendes gedacht:
    Der Raspby soll einen statischen Plan der Wohnung haben (Aussenmauern, Innenmauern, Türen, Möbelflächen) Der Plan hat eine Kompassausrichtung, die Raumobjekte bekommen Attribute wie Name (Arbeitszimmer, Flur, Küche...) Abmessungen der Freiflächen (dynamisch ermittelt). Der Roboter kann auf Grund von bekannten Marken wie Türen oder Mauergrenzen (Ecken, Kanten) und evtl. Möbelgrenzen seine Position aktualisieren und die Genauigkeit damit verbessern, denn der Schlupf des Antriebs wird abhängig vom Untergrund (Teppichboden, Steinboden, Holzboden) die Odometriedaten verfälschen. Das hat man beim Segeln früher auch so gemacht, hin und wieder peilt man Landmarken oder Sterne an um seine gekoppelte Position zu korrigieren! Z.B. wenn das GPS ausgefallen ist.

    Voraussetzung sind 6 US Sensoren vorne, 45 Grad vorne links, vorne rechts und seitlich links und rechts sowie oben (Türdurchfahrt)
    Mittels der Sensoren können über Winkelbeziehungen von Geraden, Ecken und Kanten detektiert und Abstände gemessen werden.

    Suchfahrt:
    der Roboter startet an einer definierten Position (x,y Koordinaten) und bewegt sich mäanderfömig durch die Wohnung um die befahrbaren Positionen zu finden (Rasenmäher Methode). Dabei zeichnet er die Freiflächen auf. seine aktuelle Position wird laufend durch bekannte Marken korrigiert . Raster Karte (0.3m x 0.3m, das sind etwa die Roboter Abmessungen).

    Zielfahrt (von Arbeitszimmer, nach Küche):
    Der Roboter startet an einer definierten Position (Objekt, x,y Koordinaten) mit Ziel (Objekt, x,y Koordinaten) und verwendet den Kompass um grob zu wissen in welcher Richtung er steht.
    Er berechnet den kürzesten Weg auf Grund der vorhandenen Karte. (wie im Navi)
    Er fährt los und orientiert sich mit dem align Algorithmus an einer Seitenwand, regelt so seine Geradeausfahrt bis er parallel zur Wand fährt. Das macht er immer wenn er seine Fahrtrichtung wechseln muss
    Neuen Hindernissen weicht er aus, kommt aber wieder auf seinen Zielweg zurück (wie im Navi). Hindernisse werden zusätzlich in die statische Karte übernommen wenn sie mehrmals erkannt werden.
    An Türen, Ecken und Kanten und wenn gepflegt weiteren Marken wie Möbel kann die Position synchronisiert werden.

    Softwarefehler suchen ist wie Pilze suchen. Wenn man erst einen gefunden hat, findet man meist mehrere.

    Bei Hardware ist es schlimmer, da findet man bereits Fehler wenn man gar keine sucht!

    Einmal editiert, zuletzt von Wolfgang Glück (31. Januar 2016 um 10:16)

  • Hm ich knabber derzeit noch an dem Problem meine RaspiCam ins Webif einzubinden... Vorher war das ja leicht... Aber nun steh ich irgendwie den ganzen Tag schon aufm Schlauch.

    Gemäß meines gestrigen Threads habe ich vom picamera Macher pistreaming gefunden, was eine Kombination aus picamera, ffmpeg, http.server, ws4py und jsmpeg.js ist. Der Video Stream wird dabei raw über WebSocket versendet und von jsmpeg.js verarbeitet/dargestellt.
    pistreaming standalone funktioniert super. Versuche ich das aber nun in meine Scripte einzupflanzen scheitere ich kläglich :(

    Ich versuche natürlich tornado.websocket zu verwenden um so wenig wie möglich "andere" Module installieren/importieren zu müssen. An für Sich wäre das ja eigentlich auch kein Problem, einfach nur noch einen Handler hinzufügen usw... Allerdings ist das eigentliche Problem nun dass pistreaming einen background Thread verwendet um den Stream von ffmpeg via WebSocket zu "broadcast"en:

    [code=php]
    class BroadcastThread(Thread):
    def __init__(self, converter, websocket_server):
    super(BroadcastThread, self).__init__()
    self.converter = converter
    self.websocket_server = websocket_server

    def run(self):
    try:
    while True:
    buf = self.converter.stdout.read(512)
    if buf:
    self.websocket_server.manager.broadcast(buf, binary=True)
    elif self.converter.poll() is not None:
    break
    finally:
    self.converter.stdout.close()
    [/php]

    Da ich nicht "ws4py" verwende sondern tornado.websocket kann ich nicht direkt self.websocket_server.manager.broadcast() nutzen. Auch habe ich kein Object in Form von websocket_server was ich hier einfügen könnte, da WebSocketHandler(tornado.websocket.WebSocketHandler) ja nur von meiner tornado Application erzeugt wird....

    Und zu guter letzt blockiert ja bekanntlich IOLoop von tornado. Ich muss also den RaspiCam Streamer vorher starten. Tu ich das habe ich aber doch noch gar kein WebSocketHandler :wallbash:

    :helpnew:

    Gibts da eine Möglichkeit oder muss ich wirklich ws4py verwenden? =(

  • Noch mal ein kleiner Zwischenbericht - der ja bekanntlich auch für mich selbst als Gedankenstütze dient ;)


    Wie einige vielleicht schon mit gekriegt haben, habe ich den kompletten Softwareteil von Grund auf neu aufgesetzt und habe versucht etwas professioneller an die Sachlage heran zu gehen.
    Ich setze jetzt verstärkt auf einzelne Scripts mit Klassen, jeweils für einen bestimmten Aufgabenbereich. Also nicht mehr wild alles in einem Script mit etlichen Threads :blush:
    Grobe Beschreibung der einzelnen Scripts:

    • Main.py
      Initialisierung und Einbindung aller Konfigurationen und Module/Klassen sowie der primäre Loop in dem alles abgearbeitet wird.
    • Toolsbox/AUDIOinterface.py
      Dient der Audio Ausgabe sowie Sprachkommando-Erkennung.
    • Toolsbox/WebControlDaemon.py
      Stellt das Web-Interface via bottle zur Verfügung. Leitet Befehle vom Web-Interface weiter und reicht Telemetry Daten ans Web-Interface. Wird mithilfe von Multiprocessing als eigener Prozess gestartet.
    • Toolsbox/WebControl.py
      Verarbeitet Anfragen vom Web-Interface.
    • Toolsbox/RaspiCamDaemon.py
      Stellt einen Stream der RaspiCam via Websocket zur Verfügung. Wird mithilfe von Multiprocessing als eigener Prozess gestartet.
    • Toolsbox/USBinterface.py
      Stellt die Schnittstelle zwischen RaspberryPi und Arduino zur Verfügung
    • Toolsbox/UtilityDaemon.py
      Stellt unterschiedliche kleinere Funktionen zur Verfügung über dessen genauen Umfang ich mir noch nicht im klaren bin... Wird mithilfe von Multiprocessing als eigener Prozess gestartet.
    • Toolsbox/Utility.py
      Stellt verschiedene Funktionen bereit wie zB das Ein/Ausschalten des RaspiCam-Streams, Bereinigung der Queue's, interne Handhabung des logging Modules oder Abruf von Systeminformationen usw.
    • Toolsbox/Sensor.py
      Dient zur Steuerung des Pan/Tilt Konstrukts auf dem sich ein Ultraschall Sensor sowie die RaspiCam befindet.
    • Toolsbox/Radar.py
      Steuert alle übrigen Ultraschall Sensoren und nimmt auch Einfluss auf Propulsion.py um zB. Hindernissen auszuweichen.
    • Toolsbox/Propulsion.py
      Steuert den Antrieb also die Motoren.
    • Toolsbox/Proximity.py
      Steuert die IR-Sensoren des Explorer-PCB's und dient der unmittelbaren/näheren Umgebungserfassung, wofür die US nicht in der Lage sind.
    • Toolsbox/PS3.py
      Ermöglicht die Steuerung über einen PS3 Dualshock Bluetooth Controller
    • Toolsbox/Navigation.py
      Kontrolliert die aktuelle Position des Gefährts und ggf was nötig ist um an ein Ziel zu gelangen.
    • Toolsbox/Mapping.py
      Kümmert sich um die Kartierung. Wir später ggf auch zum Daemon Script.

    (Toolsbox ist ein Verzeichnis)

    In jeder Datei sind auch weitere Befehle/Funktionen enthalten, um die für ihren Zweck dienlichen Aktionen, ansprechen zu können, wie zum Beispiel das absetzen eines Befehls an den Arduino.

    Jedes Script was irgend etwas mit I/O's zu tun hat beinhaltet auch eigene Initialisierung der GPIO's. Der Sketch auf dem Arduino hat also nichts dergleichen fest hinterlegt sondern die jeweiligen I/O's werden vom Raspberry aus initialisiert und konfiguriert. Der Arduino dient also nur als Port-Expander.

    Die Scripts sind aber natürlich noch nicht fertig - bei allen steht das Grundgerüst und bei ca. der Hälfte funktionieren auch schon fast alle Abläufe, aber die eigentlich wichtigen Sachen wie Navigation usw fehlen noch :daumendreh2:

  • ...Still ists hier gewesen in den letzten Monaten...

    Ich hab mich vor einigen Wochen mal wieder an dieses Projekt gesetzt und den aus Beitrag#167 erwähnten neuen Ansatz überarbeitet....

    • Main.py
      Initialisierung und Einbindung aller Konfigurationen und Module/Klassen sowie der primäre Loop in dem alles abgearbeitet wird.
    • Toolsbox/Audio.py
      Sprachbefehle die mithilfe von CMUSphinx (sphinxbase, pocketsphinx und SpeechRecognition) in Text umgewandelt werden und später als eine Art J.A.R.V.I.S. fungieren soll. Mikrofon direkt am Pi angeschlossen.
    • Toolsbox/AudioDaemon.py
      Sprachbefehle fürs Web-Interface. Benötigt WebSocketSecure und somit HTTPS woran ich bisher leider scheitere... Eine Problemlösung wurde >hier < gesucht.
    • Toolsbox/WebControlDaemon.py
      Stellt das Web-Interface via bottle zur Verfügung. Leitet Befehle vom Web-Interface weiter und reicht Telemetry Daten ans Web-Interface. Wird mithilfe von Multiprocessing als eigener Prozess gestartet.
    • Toolsbox/RaspiCamDaemon.py
      Stellt einen Stream der RaspiCam via Websocket zur Verfügung. Wird mithilfe von Multiprocessing als eigener Prozess gestartet.
    • Toolsbox/USBinterface.py
      Stellt die Schnittstelle zwischen RaspberryPi und Arduino zur Verfügung.
    • Toolsbox/Utility.py
      Stellt verschiedene Funktionen bereit wie zB das Ein/Ausschalten des RaspiCam-Streams, Bereinigung der Queue's, interne Handhabung des logging Modules oder Abruf von Systeminformationen usw.
    • Toolsbox/Sensor.py
      Dient zur Steuerung des Pan/Tilt Konstrukts auf dem sich ein Ultraschall Sensor sowie die RaspiCam befindet.
    • Toolsbox/Radar.py
      Steuert alle übrigen Ultraschall Sensoren und nimmt auch Einfluss auf Propulsion.py um zB. Hindernissen auszuweichen.
    • Toolsbox/Propulsion.py
      Steuert den Antrieb, also die Motoren.
    • Toolsbox/Proximity.py
      Steuert die IR-Sensoren des Explorer-PCB's und dient der unmittelbaren/näheren Umgebungserfassung, wofür die US nicht in der Lage sind.
    • Toolsbox/Navigation.py
      Kontrolliert die aktuelle Position des Gefährts und ggf was nötig ist um an ein Ziel zu gelangen.
    • Toolsbox/Mapping.py
      Kümmert sich um die Kartierung. Wird später ggf auch zum Daemon Script.

    (Toolsbox ist ein Verzeichnis)

    Mittlerweile ist das Projekt also ziemlich umfangreich und kompliziert geworden :daumendreh2:

    AudioDaemon, also die Sprachsteuerung übers Web-Interface, hab ich derzeit auf Eis gelegt, da die Audio-API von Chrome / Firefox nur noch über HTTPS nutzbar ist, sich der Browser dann aber weigert einfaches WS zu nutzen also müsste ich dann auch WSS implementieren - woran ich derzeit scheitere... Für den Audiostream würde ich dann btw opus verwenden.

    Wie in Beitrag#163 beschrieben, verwende ich einen Queue Server "beanstalkd" worauf alle Scripts drauf zugreifen. Dadurch wäre es auch möglich die einzelnen Aufgaben auf unterschiedliche Raspberry's aufzuteilen da der Queue Server netzwerkfähig ist ;)

    Die Main.py sieht derzeit so aus: => http://codepad.org/DE5pd3zy
    Es gibt natürlich noch einige Stellen die mir noch nicht gefallen, aber so ist das mit Dingen in der Entwicklung ;)

    Das Herzstück auf dem Arduino, was aber natürlich auch noch nicht 100% zufriedenstellend fertig ist, sieht derzeit so aus: => http://codepad.org/nNgQzcB0
    Das Gegenstück dazu in Python (USBinterface.py) sieht so aus: => http://codepad.org/NKn9osTO
    ...ich gebe zu das der Code dieser beiden Scripts nicht komplett auf meinen Mist gewachsen ist... :blush: die Quellen weiß ich aber leider nicht mehr :(

    Ein Beispiel wie dann mit den I/O's des Arduino's umgegangen wird könnt ihr anhand der Radar.py erkennen: => http://codepad.org/x1tMNeEt


    Damit kann man erst mal arbeiten, oder :huh:

  • Wie immer etwas schwierig das Problem zu beschreiben.... Also bitte etwas Nachsicht :blush:

    Ich möchte mein RoPi Projekt optimieren indem ich On-Top zwei Ultraschall-Sensoren auf einen DC/Stepper Motor montiere, der sich permanent in die selbe Richtung dreht.

    Code
    <|>
     [|]

    Oben drauf die 2 US mit dem Rücken aneinander. Da drunter der Motor.

    Nun ist aber das Problem, das Kabel zu den US benötigt werden und die sich dann natürlich aufwickeln.... Daher nun meine Frage ob hier jemand eine Idee hat wie man das am besten und zugleich störungssicher umsetzen könnte?

    Spontan dachte ich halt an so Schleifkontakte aber wie baut man sich sowas selber?

  • Servus,
    das habe ich auch schon eine ganze Weile auf meinem "Gedankenstack" ... bis jetzt allerdings leider noch ohne die zündende Idee ...

    Das einzige, was mir bisher eingefallen ist wären zwei kreisrunde Platinen, doppelseitig Cu beschichtet, mit einer Bohrung in der Mitte. Die werden auf einem Kuststoff-Rohr fixiert, in das von unten die vier Kabel durchgeführt sind. Jedes Kabel ist mit jeweils einer Oberfläche verbunden (verlötet).
    Dann halt einen Rotor, ebenfalls das Kunststoff-Rohr als Mittelachse, der die Flächen mit einem Messingschleifer abgreift.
    Um den Verschleiss zu minimieren noch mit Graphit schmieren ...

    Was besseres ist mir bisher leider nicht eingefallen ...

    //EDIT: ich hatte auch schon mal überlegt, einen Stufenschalter zu "vergewaltigen" und die Anschläge zu entfernen ...
    bringt aber auch nur bedingt was, weil das ja nur ein Anschluss wäre.
    Es gibt zwar Stufenschalter mit mehreren Ebenen (z.B. so was hier: http://www.ebay.de/itm/Stufenscha…2-/232223792691) , aber da bin ich noch nicht weitergekommen ...
    In der Modellbau-Ecke ist mir bisher auch noch nix Brauchbares untergekommen und ein Rumdum-Licht fällt auch flach, weil sich da nur aussen ein Reflektor dreht.

    cu,
    -ds-

  • doing: Cool! Das sieht perfekt aus :thumbs1:
    Das Funktionsprinzip sieht auch sehr einfach aus - nur hoffentlich ist das störungsfrei ;)

    Auf ein Ritzel (Getriebe) geklebt und daneben den Motor zum rotieren des ganzen :angel:


    Interessant das bei SparkFun unten drunter auch direkt ein LIDAR gelistet wird, was die nächste Stufe der Optimierung wäre ;)

    DC Motor werde ich wohl auch nicht nutzen sondern einen Stepper - nachdem ich das gerade gesehen hab:

    Externer Inhalt www.youtube.com
    Inhalte von externen Seiten werden ohne deine Zustimmung nicht automatisch geladen und angezeigt.
    Durch die Aktivierung der externen Inhalte erklärst du dich damit einverstanden, dass personenbezogene Daten an Drittplattformen übermittelt werden. Mehr Informationen dazu haben wir in unserer Datenschutzerklärung zur Verfügung gestellt.


    Ich hatte auch schon die Idee einen Spiegel zu verwenden der sich dann dreht, aber das soll angeblich nicht so toll funktionieren.


    :danke_ATDE: für eure Unterstützung, mal wieder :cool:

  • Das mit dem Stepper Motor würde ich mir nochmal überlegen, die Dinger sind ziemlich Stromhungrig (was direkt auf die Akkus durchschlägt) und eine hohe Haltekraft brauchst du meines Erachtens nach für die Ultraschall Sensoren auch nicht. Wie wäre es mit einem Continuous Rotation Servo wie z.B. diesem hier? Ich habe dir extra gleich die Version mit Metallgetriebe herausgesucht...

    Ich verwende einen ähnlichen Slipring mit 12 Adern bei meinen Remote Head auf dem Kamera Slider. In der Anwendung müssen 2 x 12 V bei 2 A (und anderer Kram) über den Slipring laufen, was ausgezeichnet und bisher ohne jede Störung funktioniert - ich würde mir da erstmal keine Gedanken machen.

    Einmal editiert, zuletzt von doing (20. März 2017 um 10:01)

  • Hallo Meigrafd,

    für den Tisch-Sonar habe ich zwei US-Sensoren um 180 ° versetzt auf eine Lochraster-Platine gesteckt und lasse diese über seinen Servo um 180 ° drehen (ca. 90°/s). Dadurch erreiche ich eine Abdeckung von 360° - ohne großen apparativen Aufwand oder Gefahr der Verdrehung von Kabeln.

    Beste Grüße

    Andreas

    Ich bin wirklich nicht darauf aus, Microsoft zu zerstören. Das wird nur ein völlig unbeabsichtigter Nebeneffekt sein.
    Linus Torvalds - "Vater" von Linux

    Linux is like a wigwam, no windows, no gates, but with an apache inside dancing samba, very hungry eating a yacc, a gnu and a bison.

    Einmal editiert, zuletzt von Andreas (20. März 2017 um 19:53)

  • doing: So ein Servo ist aber bestimmt laut oder?


    Ich bin aktuell mal wieder dabei mein bisheriges Konzept zu überarbeiten weil ich immer noch unzufrieden bin :blush:

    Bisher setzte ich ja auf ein Pi2B und einen ArduinoMEGA. Auf dem Pi liefen mehrere Sachen ua. auch der Webserver zur Überwachung. Das gefällt mir irgendwie nicht so gut wie mein erster Ansatz mithilfe von Socket, weil damit das Web-Zeug auch irgendwo im Netzwerk laufen konnte.

    Aktuell denke ich auch darüber nach den Pi2B wieder rauszuwerfen und durch PiZero's zu ersetzen, da diese weitaus weniger Strom fressen als ein QuadCore Pi. Rein Rechnerisch könnte man 3 PiZero für den selben Stromhunger eines Pi2B verwenden, hätte dann auch eine saubere Trennung der Aufgabenbereiche...

    Natürlich nicht allzu sehr übertreiben, aber schon schwerwiegende Aufteilung der Aufgaben pro PiZero:
    - 1x PiZero für die RaspiCam.
    - 1x PiZero für SLAM.
    - 1x PiZeroW als Host/Master, an dem auch der Arduino angeschlossen ist.
    Die beiden 'Client'-PiZero werden via "virtual network" mit mit 'Master' verbunden, übertragen ihre Daten also via Netzwerk.

    Das Konstrukt mit Pi2B verbraucht mit allem PiPaPo durchschnittlich 550mA.
    Nach dem Umbau:
    PiZero mit aktiver RaspiCam verbraucht im Durchschnitt 150mA. Der kümmert sich auch um den einen US aufm Pan/Tilt wodrauf auch die RaspiCam montiert ist.
    PiZero mit OpenCV für die SLAM Berechnungen verbraucht im Durchschnitt 100mA. Ohne OpenCV waren es 140mA.
    PiZero-Master verbraucht im Durchschnitt 90mA.
    =~ 340mA.

    Bei beiden Konstrukten kommt zusätzlicher Strombedarf im Betrieb hinzu, wenn zum Beispiel der Pan/Tilt bewegt wird, oder die Corner-IR's abgefragt werden.

    Nicht unerheblich ist auch der Bedarf des UltraSchall-Konstrukts, derzeit bestehend aus 1x US-015 (3mA) und 6x HC-SR04 (á 15mA) == durchschnittlich 94mA.

    Die 6x HC-SR04 möchte ich nun durch 2 permanent rotierende LIDAR Laser ersetzen (bis vorgestern sollten es erst nur US werden aber dann muss der "Rotation Servo" ständig stoppen). RPLIDAR ist mir btw freilich zu teuer, ich hab mich eher für ein LIDAR-Lite entschieden der reicht für mein Projekt absolut.
    Der benötigt dann zwar 130mA bzw 100mA im Dauerbetrieb aber das kann ich durch den Umbau ja locker wegstecken ;)

    Das alte Konstrukt kam mit allem PiPaPo auf ca. 680mA peak.
    Das neue Konstrukt müsste dann bei ca. 500mA peak landen, sofern ich mich nicht verrechnet hab :D Tatsächliche Messungen stehen noch aus - hab noch nichts bestellt.


    Hab bei meinen Recherchen zum LIDAR auch folgendes interessantes Projekt gefunden =>

    Externer Inhalt www.youtube.com
    Inhalte von externen Seiten werden ohne deine Zustimmung nicht automatisch geladen und angezeigt.
    Durch die Aktivierung der externen Inhalte erklärst du dich damit einverstanden, dass personenbezogene Daten an Drittplattformen übermittelt werden. Mehr Informationen dazu haben wir in unserer Datenschutzerklärung zur Verfügung gestellt.


  • doing: So ein Servo ist aber bestimmt laut oder?

    Ein fiepender Stepper ist nicht leiser, wobei die Frequenzen des Steppers deutlich mehr nerven, als die Gearbox des Servos. Steht dein Robot nur rum und grübelt über den schlausten Fahrweg und wer ihm da wieder was Doofes in den Weg gestellt hat? Wenn nein, dann sind die Motoren des Antriebs bestimmt lauter als der Servo :)


    PiZero mit aktiver RaspiCam verbraucht im Durchschnitt 150mA.

    Wie kommst du auf das schmale Brett? Wenn ich mir diese Messungen ansehe, dann rechne mal lieber mit ~250mA für den Zero (ohne W und mit ausgeschaltetem HDMI Port) mit Pi Camera. Aus einer anderen Anwendung heraus kann ich bestätigen, dass die ca. 150mA nur für die Pi Camera vollkommen realistisch sind - die braucht sie.

    Einmal editiert, zuletzt von doing (23. März 2017 um 18:42)


  • Wie kommst du auf das schmale Brett? Wenn ich mir diese Messungen ansehe, dann rechne mal lieber mit ~250mA für den Zero (ohne W und mit ausgeschaltetem HDMI Port) mit Pi Camera. Aus einer anderen Anwendung heraus kann ich bestätigen, dass die ca. 150mA nur für die Pi Camera vollkommen realistisch sind - die braucht sie.

    :denker: Weil das zumindest mein "Messgerät" angezeigt hat und ich auch kein FullHD (1080p) Video aufzeichne, 640x480 reichen völlig :)
    (hier unter "Stromverbrauch überwachen")

    Werde das aber noch mal nachmessen

Jetzt mitmachen!

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