Bluetooth Kommunikation Arduino / Python mit Pybluez

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
wing354
User
Beiträge: 7
Registriert: Dienstag 30. Juli 2019, 13:04

Moin,
ich bin ein absoluter Anfänger in Python, aber da es recht ähnlich zu Matlab ist, dachte ich, dass man sich schnell reinfuchsen kann.
Allerdings habe ich gerade einen kleinen Hänger und keine Idee.
Ich möchte eine reibungslose Bluetooth Kommunikation zwischen meinem Laptop mit Python zu meinem Bluetooth Modul HC-05 und dem Arduino herstellen.
Ich kann ohne Probleme eine Verbindung herstellen und auch Befehle an den Arduino schicken.
Wenn ich einen bestimmten Befehl ausführe, so stellt der Arduino durchgehend Datenpakete an der seriellen Schnittstelle bereit.
Ich kann mit s.receive(1024) {beispielsweise} auch diese Daten empfangen nur kommen diese leider in einem sehr unübersichtlichen und für mich nicht verarbeitungswürdigen Zustand raus.
Vielleicht erstmal der Code zum Verbinden.

Code: Alles auswählen

import bluetooth, subprocess 
name = "HC-05"      # Device name
addr = "98:D3:91:FD:42:7E"      # Device Address
port = 1         # RFCOMM port
passkey = "1234" # passkey of the device you want to connect

# kill any "bluetooth-agent" process that is already running
subprocess.call("kill -9 `pidof bluetooth-agent`",shell=True)

# Start a new "bluetooth-agent" process where XXXX is the passkey
status = subprocess.call("bluetooth-agent " + passkey + " &",shell=True)

# Now, connect in the same way as always with PyBlueZ
try:
    s = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
    s.connect((addr,port))
except bluetooth.btcommon.BluetoothError as err:
    # Error handler
    pass
beim auslesen:

Code: Alles auswählen

 s.recv(1024)
Out[41]: b'-32768\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n13859\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n12476\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-19533\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n12347\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n10649\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-11639\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-17067\r\n-32768\r\n8125\r\n-32768\r\n-32768\r\n-7434\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n4628\r\n-32768\r\n-32768\r\n3221\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-674\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-4431\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-10096\r\n-32768\r\n3513\r\n-32768\r\n-32768\r\n5507\r\n-32768\r\n-32768\r\n32\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n3638\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-2954\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-12455\r\n-32768\r\n2042\r\n-32768\r\n-32768\r\n-12167\r\n-32768\r\n248\r\n-32768\r\n-32768\r\n-4070\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n17228\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n14583\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n6446\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n6854\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n12624\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n17442\r\n-32768\r'
kommt das heraus... Ich würde gerne nur die Zahlen zwischen "\r\n XYZ \r\n übergeben bekommen. Am besten einzeln, bei jeder Abfrage (würde gerne mit einer Schleife arbeiten).
Hat jemand eine Idee, wie ich die Daten gut auslesen kann?
Ich habe was von readline() gelesen, aber nicht ganz die Funktionalität durchblickt, da ich als Fehlermeldung folgendes bekomme: AttributeError: 'BluetoothSocket' object has no attribute 'readline'
Dann dachte ich man müsse das Objekt mit makefile() beispielsweise erst erstellen, aber wie läuft das? Mit makefile wird mir angezeigt, dass ich zwei Attribute übergebe, obwohl ich nur eins angebe?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

makefile() ist schon das Ticket. Es sollte eigentlich mit

Code: Alles auswählen

f  = s.makefile()
for line in f:
     print(line)
klappen. Wenn es das nicht tut, dann bitte mal konkreten Code und Fehlermeldungen posten, die dann auftreten.
wing354
User
Beiträge: 7
Registriert: Dienstag 30. Juli 2019, 13:04

Code: Alles auswählen

f  = s.makefile()
for line in f:
     print(line)
     
Traceback (most recent call last):

  File "<ipython-input-4-4f641c2ea508>", line 1, in <module>
    f  = s.makefile()

  File "C:\Users\hendr\Anaconda3\lib\site-packages\bluetooth\msbt.py", line 130, in makefile
    raise Exception("Not yet implemented")
    Exception: Not yet implemented 
Wird mir dann in der Konsole angezeigt.
Muss ich noch eine Bibliothek vorher hinzufügen?

Wenn ich das ganze außerhalb der Konsole ausführe kommt folgendes dabei raus:

Code: Alles auswählen

f  = s.makefile()
for line in f:

print(line)
  File "<ipython-input-4-f7f1653da7f3>", line 4
    print(line)
        ^
IndentationError: expected an indented block
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Noe, das klingt danach, als ob das halt mit Bluetooth nicht geht, weil es jemand nicht implementiert hat.

Damit bleibt dir nichts anderes uebrig, als das aufsammeln und zerlegen in Zeilen selbst zu machen. Problematisch dabei ist, dass es keine Garantie gibt, dass die Daten immer sauber an einer Zeilen-Grenze rausfallen. Man kann nur davon ausgehen, dass nichts verschluckt wird.

Womit man sich einen kleinen Parser schreiben muss, der das tut:

Code: Alles auswählen


TESTDATA = b'8\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n13859\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n12476\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-19533\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n12347\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n10649\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-11639\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-17067\r\n-32768\r\n8125\r\n-32768\r\n-32768\r\n-7434\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n4628\r\n-32768\r\n-32768\r\n3221\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-674\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-4431\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-10096\r\n-32768\r\n3513\r\n-32768\r\n-32768\r\n5507\r\n-32768\r\n-32768\r\n32\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n3638\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-2954\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n-12455\r\n-32768\r\n2042\r\n-32768\r\n-32768\r\n-12167\r\n-32768\r\n248\r\n-32768\r\n-32768\r\n-4070\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n17228\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n14583\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n6446\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n6854\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n12624\r\n-32768\r\n-32768\r\n-32768\r\n-32768\r\n17442\r\n-32768\r\n'


class LineParser:

    def __init__(self, separator=b"\r\n"):
        self._data = b''
        self._sep = separator

    def feed(self, data):
        self._data += data
        parts = self._data.split(self._sep)
        if parts[-1]:
            self._data = parts[-1]
        else:
            self._data = b''
        return parts[:-1]

lp = LineParser()
all_lines = lp.feed(TESTDATA)

lp = LineParser()
bytewise_lines = []
for c in TESTDATA:
    bytewise_lines.extend(lp.feed(bytes([c])))

assert(bytewise_lines == all_lines)
Das geht bestimmt schoener, aber auf die Schnelle tut er was er soll.
wing354
User
Beiträge: 7
Registriert: Dienstag 30. Juli 2019, 13:04

Ich habe das mal getestet... an sich funktioniert deine Idee Prima! Danke dafür schon mal!
Allerdings habe ich das Gefühl, dass die Umsetzung mir oft Werte abschneidet, wenn ich einzelne Werte abfragen will...
Ich habe TESTDATA = s.reicv(8) gemacht beispielsweise und dabei entstehen oft "Wortlücken"... gibt es ein Äquivalent zur "fscanf" - Funktion von Matlab in Python? Weiß das jemand?

Könnte ich die Kommunikation auch anders angehen? Alles was ich bisher gefunden habe verweist immer auf Pybluez.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich verstehe nicht was du meinst. Was soll die Wortluecke sein? Der Parser traegt Sorge dafuer, dass ueberhaengende Zeichen verwahrt werden, bis die naechsten Daten ankommen. Nur ohne die geht's halt nicht. Und scanf hat doch nichts mit der Menge der empfangenen Daten zu tun? Wenn du Werte wandeln willst, dann macht man das in Python ueblicherweise durch einfaches konvertieren mittels der Typen: int("32710").
wing354
User
Beiträge: 7
Registriert: Dienstag 30. Juli 2019, 13:04

Ich glaube der Parser schneidet einige Zahlen ab bzw. bekommt nur noch den Rest von einer Zahl mit dann weiß ich nicht, ob ich den Wert auswerten kann.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Kannst du das belegen? Mit echten Daten? So dass man das nachvollziehen kann? ZB indem du alle Datenpakete die der Parser verfuettert bekommt dokumentierst und dann was er ausgibt? Denn eigentlich tut er genau das NICHT.
Benutzeravatar
__blackjack__
User
Beiträge: 14045
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@wing354: Glauben heisst nicht wissen. Also solltest Du zunächst einmal sicherstellen dass das wirklich der Fall ist. Und dann den Fehler suchen.

Alternative, ungetestet, und geht sicher auch schöner:

Code: Alles auswählen

def read_byte_lines(socket, separator=b'\r\n'):
    buffer = b''
    while True:
        data = socket.recv(2048)
        if not data:
            break
        buffer += data
        line, sep, buffer = buffer.partition(separator)
        if sep:
            yield line
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

@__blackjack__: da wird aber immer nur eine Zeile / recv geliefert, das reicht IMHO nicht. Da musst du noch rum-whilen.
Benutzeravatar
__blackjack__
User
Beiträge: 14045
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Upsi, stimmt. 😱

Code: Alles auswählen

def read_byte_lines(socket, separator=b'\r\n'):
    buffer = b''
    while True:
        data = socket.recv(2048)
        if not data:
            break
        buffer += data
        while True:
            line, sep, buffer = buffer.partition(separator)
            if not sep:
                break
            yield line
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
wing354
User
Beiträge: 7
Registriert: Dienstag 30. Juli 2019, 13:04

Moin!
Danke euch, es funktioniert... ich habe das nochmal ausführlicher getestet.
Ich habe noch eine andere Frage, und zwar bekomme ich ja eine Liste mit Bytes raus.
Allerdings sind das ja eigentlich keine Bytes, da stehen ja Werte wie 758, -12394 usw. drin...
Wie bekomme ich die daraus?
Ich habe folgendes probiert bekomme, dann aber totalen Quatsch raus... (Beispielwert ['768'])

Code: Alles auswählen

int.from_bytes(bytewise_lines[0], byteorder='little')
Out[201]: 3683895
Python interpretiert diese Zahl komplett anders. Ich brauche aber exakt den Wert 768 abgespeichert als Integer, damit ich damit weiterrechnen kann...
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Habe ich dir doch schon geschrieben.

int(“12345678”)

Natürliche statt dem String deine extrahierte Zeile.
Benutzeravatar
__blackjack__
User
Beiträge: 14045
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@wing354: Noch eine Anmerkung zu externen Prozessen: ``shell=True`` sollte man nicht verwenden wenn das nicht nötig ist. Das beenden von laufenden `bluetooth-agent`-Prozessen würde ich mit `psutils` von Python aus erledigen, und den neuen Prozess kann man auch ohne Shell starten wenn man nicht `call()` nehmen würde, was ja per Definition auf die Rückkehr des Aufrufs wartet. Ungetestet:

Code: Alles auswählen

import psutil
from psutil import Popen, process_iter


def main():
    # ...
    passkey = '1234'

    for process in process_iter():
        try:
            if process.name == 'bluetooth-agent':
                process.terminate()
        except psutil.Error:
            pass  # Intentionally ignored.

    bluetooth_agent = Popen(['bluetooth-agent', passkey])
    try:
        # ...
    finally:
        bluetooth_agent.terminate()
        bluetooth_agent.wait()
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Antworten