Class mit serial Modul erstellt für BMW IBus(Datenbus)

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.
BlackJack

@harryberlin: Eine einfache Lösung an sich gibt es nicht, weil der ganze Ansatz ja schon fragwürdig ist, und keine nackten ``except:``\s mehr zu verwenden, ist doch ein ziemlich konkreter Vorschlag. Gehe Deine ``try``/``execpt``\s durch und schreibe da entweder konkrete Ausnahmen hin die behandelt werden sollen, oder wirf das Konstrukt raus. Das ist nicht gut weil's nicht gut ist? Nein, das ist nicht gut weil es Fehler verschluckt und man die deshalb dann nicht findet. Und spezielle Fehlerwerte machen dem Programmierer mehr Arbeit oder verleiten ihn dazu die nötige Arbeit nicht zu leisten, und damit wird das Programm dann weniger robust bis fehlerhaft.

Was Du anstelle von `SomeError` nehmen solltest, kann man so pauschal nicht sagen. Ausnahmen haben eine Bedeutung und man muss natürlich die passende zur jeweiligen Situation verwenden. Eventuell auch eigene Ausnahmen definieren. Ausserdem vorsichtig mit ``try``/``except`` umgehen, denn Dein Beispielcode zum Aufruf könnte, wenn ``# and fail...`` bedeutet, dass das Programm oder die Funktion in der das steht, abgebrochen werden soll, auch einfach so aussehen:

Code: Alles auswählen

print my_function(args)
# and now continue safely here...
Das ist ja gerade ein Pluspunkt von Ausnahmen gegenüber von Fehlerrückgabewerten: Man muss sich nur an Stellen darum kümmern, wo man das auch wirklich sinnvoll machen kann, ansonsten brechen die automatisch den Programmfluss ab und gehen die Aufrufebenen hinauf, bis sie zu einer Stelle kommen die sich dann darum kümmert. In letzter Instanz die oberste Ebene der Laufzeitumgebung, die dann automatisch, ohne das man etwas tun muss, die Meldung der Ausnahme inklusive Traceback ausgibt, und damit dem Programmierer wertvolle Ansatzpunkte liefert wo der Fehler liegt und wie der Aufrufweg dort hin aussah.

Syntaxfehler bekommt man wenn die Syntax falsch ist, also der Compiler nichts damit anfangen kann. Wenn man `self` oder `read_buffer` falsch schreibt, aber das syntaktisch immer noch gültige Namen sind, dann gibt es erst zur Laufzeit einen `NameError` oder einen `AttributeError`. Und die Methode würde bei *jedem* Aufruf `None` zurückgeben.
harryberlin
User
Beiträge: 227
Registriert: Donnerstag 17. Dezember 2015, 12:17

Mein letzter Beitrag zu try & except über dir sollte ja nun passen, oder?

Wie ist das jetzt mit dem lock? wie weiß der thread, dass er anhalten muss? ich erkenne da keinen zusammenhang?
Oder ist das prinzip des locks?:
Egal was innerhalb des locks für objekte verwendet werden, diese werden blockiert?
Und die andere funktion die darauf zugreifen möchte, wartet solang bis das zu verwendete freigegeben wird?
empty Sig
BlackJack

@harryberlin: Nein der passt nicht. Da ist immer noch ein nacktes ``except:`` wo Du sämtliche Ausnahmen durch einen `IndexError` ersetzt, also auch wenn das gar kein `IndexError` war, der da behandelt wird. Ausserdem ist der Fall ja schon ohne jegliche Ausnahmebehandlung abgedeckt, denn einen `IndexError` bekommst Du an der Stelle ja auch schon, ohne das Du irgendetwas zusätzliches machen muss.

Allerdings sind die `IndexError`\s ja etwas was mit der Implementierung zu tun hat, die würde ich so nicht nach aussen weitergeben. `Warning` auch nicht, denn das gehört eher zum `warnings`-Modul und Warnungen sind etwas was man erwartet abschalten zu können. Das kein Packet gelesen werden konnte ist hier ja immer noch ein ”Normalfall”, was nicht sein sollte. Und bei den tatsächlichen Ausnahmefällen möchte man als Aufrufer an der Stelle spezifischere Informationen haben, also eigentlich eigene Ausnahmen, oder zumindest eine.

Beim Aufruf ist das ignorieren von `Warning` nicht sinnvoll, denn wenn der Thread abgebrochen wurde macht es keinen Sinn weiter zu machen, denn der nächste Aufruf wird zwangsläufig wieder `Warning` auslösen.

Das ``finally`` ist falsch, denn wenn kein `packet` zugewiesen wurde, dann kann man das nicht existierende `packet` nicht protokollieren. Oder man protokolliert an der Stelle einen Wert der bei einem früheren Durchlauf an den Namen gebunden wurde, was keinen Sinn macht.

Es scheint das Du versuchst Dich durch abschreiben von Quelltextschnippseln und raten Themen wie Ausnahmen und nebenläufige Programmierung zu nähern. Bei Ausnahmen funktioniert das so gut — also schlecht — wie grundsätzlich beim Programmieren durch raten. Bei nebenläufiger Programmierung eher gar nicht. Das ist ein komplexes Thema. Da gibt's an Hochschulen eigene Vorlesungen für, die zum Grundstoff gehören, und ganze Bücher die sich nur diesem Thema widmen. Man kann sich hier noch weniger als bei anderen Themen, darauf verlassen, dass man es richtig gemacht hat, nur weil das Programm bei ein paar Tests scheinbar fehlerfrei durchgelaufen ist, weil diese Testläufe nicht deterministisch reproduzierbar sind.
harryberlin
User
Beiträge: 227
Registriert: Donnerstag 17. Dezember 2015, 12:17

Ja, ich rate und versuche Lösungen zu finden, weil du mich im Dunkeln stehen lässt.
Zeit für eine Vorlesung in der Hochschule habe ich ebenfalls nicht, bin mit meinen Job sehr gut ausgelastet und wollte jetzt im Urlaub mal wieder meinem Hobby nachgehen.
Ich weiß, ich hab kein Anrecht auf Lösungen, dennoch würde ich mich über etwas "unter die Arme greifen" freuen.

Wie kann man denn eigene except Errors erstellen? Wenn "Warning", "IndexError" unpassend sind, muss ich wohl eigene kreieren.

Hab ich das mit Lock denn wenigstens richtig verstanden?
empty Sig
BlackJack

@harryberlin: Ich lass Dich nicht im Dunkeln stehen. Du bekommst hier Streichhölzer und den Weg zum Lichtschalter beschrieben. Wenn Du kein Streichholz anzündest und Dich selber auf den Weg zum Lichtschalter machst, dann laste das nicht den Helfern an.

Wenn Du keine Zeit hast Dich mit komplexen Themen auseinander zu setzen, dann kannst Du in dem Bereich halt nichts programmieren. Aber Du hast ja nicht mal Zeit oder Lust Dich mit Grundlagen wie Ausnahmebehandlung zu beschäftigen…
harryberlin
User
Beiträge: 227
Registriert: Donnerstag 17. Dezember 2015, 12:17

Also gut, da muss ich mal noch weiter drüber grübeln.
Werd jetzt erst mal so mein code weiter umschreiben. Exceptions kann man auch am ende rein bringen.

Hab ich das mit dem lock denn richtig verstanden?
empty Sig
harryberlin
User
Beiträge: 227
Registriert: Donnerstag 17. Dezember 2015, 12:17

Dann will ich mal hier versuchen weiter zu machen.
Habe mir nun ein Script geschustert, mit einfach nur die Nachrichten gezeigt oder geloggt werden.
Schreiben ist ebenfalls möglich.

Hierbei benötige ich etwas hilfe,
  1. Das ganze object orientierter zu machen?
    Ich denke, die "IBUS = IBusHandler()", "LOGGER = LoggerClass()", "INPUTTHREAD = InputClass()" sind nicht unbedingt die richtige Lösung.
  2. Wie kann ich ein Thread mit einer Queue ohne timeout, die gerade im .get hängt und kein .put mehr kommt, zum beenden bringen?
  3. Kann man auch besser lösen, dass man nach dem input des Buchstabens nicht noch Enter drücken muss?

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import time
import serial
import logging
from threading import Thread, Lock
from Queue import PriorityQueue, Empty, Queue

TEXT_HIGHLIGHT_COLOR = ['\033[94m','\033[0m','\033[1;33;40m']

__author__ = 'harryberlin'
__version__ = '0.5'


class IBusHandler(object):
    def __init__(self):

        self.serial_port = serial.Serial()
        self.serial_port.baudrate = 9600
        self.serial_port.parity = serial.PARITY_EVEN
        self.serial_port.stopbits = serial.STOPBITS_ONE
        self.serial_port.timeout = 0.001
        self.rts_state = False

        self.read_buffer = []
        self.read_lock = Lock()
        self.read_error_counter = 0
        self.read_error_container = []
        self.cancel_read_thread = False
        self.read_thread = Thread(target=self._reading)
        self.read_thread.setDaemon(True)
        self.packet_buffer = Queue()

        self.cts_counter = 0.0
        self.cts_thread = Thread(target=self._cts_watcher)
        self.cts_thread.setDaemon(True)


        self.write_buffer = PriorityQueue()
        self.write_counter = 0

        self.cancel_write_thread = False
        self.write_thread = Thread(target=self._writing)
        self.write_thread.setDaemon(True)

    @staticmethod
    def _calculate_checksum(packet):
        result = 0
        for value in packet:
            result ^= value

        return result

    def _cut_read_buffer(self, offset=1):
        with self.read_lock:
            self.read_buffer = self.read_buffer[offset:]

    def _wait_free_bus(self, waiting=17, timeout=1000):
        if waiting >= timeout:
            log('Error: Waiting Time (%sms) is bigger than Timeout Time (%sms)' % (waiting,timeout))
            return False
        while timeout > 0:
            if self.cts_counter >= waiting:
                return True
            timeout -= 1
            time.sleep(0.001)

        return False

    def _reading(self):
        while not self.cancel_read_thread:
            input = self.serial_port.read(50)
            if len(input) > 0:
                input = [ord(x) for x in input]
                with self.read_lock:
                    self.read_buffer.extend(input)
            self._read_bus_packet()

        log('Read/Write Thread finished')

    def _writing(self):
        while not self.cancel_read_thread:
            try:
                prio, write_counter, data = self.write_buffer.get(timeout=1)
                try:
                    while self.cts_counter < 7.0:
                        time.sleep(0.001)

                    self.serial_port.write(data)
                    self.cts_counter = 0.0
                    self.serial_port.flush()
                    self.cts_counter = 0.0
                    log('\033[1;33;40mWRITE:\033[0m %s' % ' '.join(['%02X' % i for i in data]))
                except serial.SerialException:
                    self.write_buffer.put((prio, write_counter, data))

            except Empty:
                pass

    def _cts_watcher(self):
        while not self.cancel_read_thread:
            if self.serial_port.getCTS():
                self.cts_counter += 0.1
                time.sleep(0.0001)
            else:
                self.cts_counter = 0.0

        log('CTS Thread finished')

    def set_port(self, device_path):
        self.serial_port.port = device_path

    def connect(self):

        self.serial_port.open()
        # self.serial_port.setDTR(1)
        self.serial_port.setRTS(0)
        self.cts_thread.start()

        if not self._wait_free_bus(120, 3000):
            log('Error: Can not locate free Bus')
            raise

        self.serial_port.flushInput()

        self.read_thread.start()
        self.write_thread.start()


    def disconnect(self):
        self.cancel_write_thread = True
        self.cancel_read_thread = True
        self.ntsc = False
        time.sleep(0.6)
        self.serial_port.close()
        log('disconnected')

    @property
    def ntsc(self):
        return self.rts_state

    @ntsc.setter
    def ntsc(self, value):
        if self.rts_state == value:
            return
        self.serial_port.setRTS(value)
        self.rts_state = value

    def read_bus_packet(self):
        try:
            return self.packet_buffer.get(timeout=1)
        except Empty:
            return None

    def _read_bus_packet(self):
        if self.cancel_read_thread:
            return None

        try:
            data_length = self.read_buffer[1]
        except IndexError:
            return None

        if data_length < 3 or data_length > 37:
            self.read_error_container.append(self.read_buffer[0])
            self._cut_read_buffer()
            return None

        buffer_len = len(self.read_buffer)
        if buffer_len < 5 or buffer_len < data_length + 2:
            return None

        message = self.read_buffer[:data_length + 2]

        if self._calculate_checksum(message) == 0:
            if len(self.read_error_container) > 0:
                error_hex_string = ' '.join(['%02X' % i for i in self.read_error_container])
                log('READ-ERR: %s' % error_hex_string)
                self.read_error_counter += len(self.read_error_container)
                self.read_error_container = []

            self._cut_read_buffer(data_length + 2)

            self.packet_buffer.put_nowait({'src': message[0],
                                           'len': data_length,
                                           'dst': message[2],
                                           'data': message[3:data_length + 1],
                                           'xor': message[-1]})

        else:
            self.read_error_container.append(self.read_buffer[0])
            self._cut_read_buffer()
            return None

    def write_bus_packet(self, src, dst, data, highprio=False, veryhighprio=False, repeat=1):
        try:
            packet = [src, len(data) + 2, dst]
            packet.extend(data)
        except:
            packet = [int(src, 16), len(data) + 2, int(dst, 16)]
            packet.extend([int(s, 16) for s in data])

        packet.append(self._calculate_checksum(packet))
        bytepacket = bytearray(packet)
        while repeat > 0:
            repeat -= 1
            if veryhighprio:
                self.write_buffer.put_nowait((0, 0, bytepacket))
            else:
                self.write_buffer.put_nowait((0 if highprio else 1, self.write_counter, bytepacket))
            self.write_counter += 1

    def write_hex_message(self, hexstring):
        hexstring_tmp = hexstring.upper().split(' ')
        src = int(hexstring_tmp[0], 16)
        dst = int(hexstring_tmp[2], 16)
        data = [int(s, 16) for s in hexstring_tmp[3:-1]]
        self.write_bus_packet(src, dst, data)

class LoggerClass(object):

    def __init__(self):
        self.enable_logfile = False
        self.logger = None

    def set_file(self, filefullpath):
        self.logger = logging
        self.logger.basicConfig(filename=filefullpath, level=logging.INFO, filemode='w', format='%(asctime)-15s: %(message)s')

    def log(self, message):
        if not self.enable_logfile:
            return
        self.logger.info('%s' % message)


class InputClass(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.setDaemon(True)
        self.finished = False
        self.write = False

    def run(self):
        try:
            while not self.finished:

                time.sleep(0.5)
                input = raw_input()

                if input == "q":
                    log('wait for disconnecting IBUS')
                    self.finished = True
                elif input == "w":
                    self.write = True
                    log("\033[1;33;40mType Message for writing to IBus (example:F0 LL 68 01 CK) <- type 'LL' or 'CK' if unknown:\033[0m")
                    while self.write:
                        input = raw_input()
                        if input == '\x1b':
                            pass
                        elif input == 'r':
                            self.write = False
                        elif input == 'q':
                            log('wait for disconnecting IBUS')
                            self.finished = True
                        else:
                            IBUS.write_hex_message(input)

        except (KeyboardInterrupt, SystemExit):
            sys.exit()

def log(string):
    print (' IBUS: %s' % string)

    for color in TEXT_HIGHLIGHT_COLOR:
        string = string.replace(color, '')

    LOGGER.log('IBUS: %s' % string.decode('utf-8').encode("iso-8859-1"))

def help():
    print '\n'
    print ' -h,           show this information'
    print ' -dev,         set device (default:/dev/ttyUSB0)'
    print ' -log,         enables logfile'
    print ' --- when Script is running ---'
    print ' w + enter,    switches to write mode'
    print ' r + enter,    switches back from write to read mode'
    print ' q + enter,    stops the script'

IBUS = IBusHandler()
LOGGER = LoggerClass()
INPUTTHREAD = InputClass()


def start_ibus(device='/dev/ttyUSB0'):

    def read_ibus_packages():
        while not INPUTTHREAD.finished:
            packet = IBUS.read_bus_packet()
            if packet:
                while INPUTTHREAD.write:
                    time.sleep(1)
                log('\033[94mREAD:\033[0m %02X %02X %02X %s %02X' % (packet['src'], packet['len'], packet['dst'], ' '.join(['%02X' % i for i in packet['data']]), packet['xor']))

        log('READ PACKAGES CLOSED')

    log('IBusTesterVersion: %s' % __version__)
    log('SerialModulVersion: %s' % serial.VERSION)
    log('SerialDevice: %s' % device)

    IBUS.set_port(device)

    log('Connecting')
    try:
        IBUS.connect()
    except:
        log('Error: Can not open serial device')
        return

    ibus_st = Thread(target=read_ibus_packages)
    ibus_st.setDaemon(True)
    ibus_st.start()
    log('Connected')

    INPUTTHREAD.start()

    while not INPUTTHREAD.finished:
        try:
            # print("..Script's Thread..")
            time.sleep(2)
        except (KeyboardInterrupt, SystemExit):
            INPUTTHREAD.finished = True

    IBUS.disconnect()


def main():
    count = len(sys.argv) - 1

    given_args = sys.argv

    if '-h' in given_args:
        help()
        sys.exit(0)

    if "-log" in given_args:
        LOGGER.enable_logfile = True
        LOGGER.set_file('ibustester.log')
        count -= 1

    if count > 0:
        if "-dev" in given_args:
            start_ibus(given_args[given_args.index('-dev')+1])
        else:
            print ('\n Unknown Arguments given! %s' % sys.argv[1:])
            help()
            sys.exit(0)

    else:
        start_ibus()


if __name__ == '__main__':
    main()

log('SERVICE COMPLETELY CLOSED')
sys.exit(0)
empty Sig
BlackJack

@harryberlin: Du verwendest Python 2 und ab und zu Klammern bei ``print``. Das ist verwirrend. Entweder schreibt man das ohne Klammern, eben weil es eine Anweisung und keine Funktion ist, oder man macht die Anweisung mit dem entsprechenden `__future__`-Import zu einer Funktion und muss das dann auch entsprechend überall als Funktion aufrufen.

Der Namenszusatz `Class` bei Klassen macht keinen Sinn. Das es sich um eine Klasse handelt sieht man ja bereits an der Schreibweise des Namens.

Was Du da mit `LoggerClass` und der `log()`-Funktion machst ist kein idiomatischer Umgang mit dem `logging`-Rahmenwerk. Du machst da Arbeit die schon von `logging` erledigt werden kann, zum Beispiel die Entscheidung ob, was, und wohin protokolliert wird, und Du schränkst das dann auch noch auf die `log()`-Methode ein, wirfst also die Loglevel komplett weg.

Falls das nicht mehr zu Python 2.6 kompatibel sein muss würde ich die alten, ”javaesquen” Namen der `threading`-API nicht mehr verwenden. `sedDaemon()` ist mittlerweile durch Zuweisung an das `daemon`-Property ersetzbar.

Bei `_wait_free_bus()` passt die Logmeldung inhaltlich nicht zur ``if``-Bedingung.

Die ``while``-Schleife ist eigentlich eine ``for``-Schleife.

In `_reading()` wird die `input()`-Funktion verdeckt.

`_cts_watcher` verbraucht ziemlich viel CPU-Zeit wenn die CTS-Leitung nicht gesetzt ist. Fehlt da eventuell ein `time.sleep()`?

`connect()` enthält ein ”nacktes” ``raise`` das nicht in einem ``except`` steht. Das ist sehr sicher nicht so gewollt.

Aus dem `IBusHandler` würde ich einen „context manager“ machen, damit man den mit der ``with``-Anweisung verwenden kann, und damit sichergestellt ist, das `disconnect()` auf jeden Fall ausgeführt wird wenn der ``with``-Block verlassen wird.

Bei `write_bus_packet()` ist mir bei dem nackten ``except`` gerade nicht klar was denn im ``try``-Block überhaupt schief gehen können soll, damit der Code im ``except``-Zweig Sinn macht‽

Die ``while``-Schleife ist wieder eine verkleidete ``for``-Schleife.

Ab `InputClass` habe ich jetzt erst einmal nicht weitergemacht, das ist aber leicht verwirrend was damit gemacht wird. Insbesondere diese komische Interaktion von `sys.exit()` im Thread, das dann an anderer Stelle mit einem ``except`` behandelt wird indem `finished` gesetzt wird um den Thread zu beenden der `sys.exit()` aufgerufen hat. Das ist ziemlich schräg.

`IBUS` und `INPUTTHREAD` sind auch keine Konstanten. Die beiden Objekte haben auf Modulebene nichts zu suchen.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
import logging
import sys
import time
from threading import Lock, Thread
from Queue import Empty, PriorityQueue, Queue
 
import serial

TEXT_HIGHLIGHT_COLOR = ['\033[94m', '\033[0m', '\033[1;33;40m']
 
__author__ = 'harryberlin'
__version__ = '0.5'

# 
# TODO Use `logging` framework in a more idiomatic way.
# 
class Logger(object):
 
    def __init__(self):
        self.enable_logfile = False
        self.logger = None
 
    def set_file(self, filefullpath):
        self.logger = logging
        self.logger.basicConfig(filename=filefullpath, level=logging.INFO, filemode='w', format='%(asctime)-15s: %(message)s')
 
    def log(self, message):
        if not self.enable_logfile:
            return
        self.logger.info('%s' % message)
 

LOGGER = Logger()


def log(string):
    print(' IBUS:', string)
 
    for color in TEXT_HIGHLIGHT_COLOR:
        string = string.replace(color, '')
 
    LOGGER.log('IBUS: %s' % string.decode('utf-8').encode('iso-8859-1'))
 
 
class IBusHandler(object):

    def __init__(self):
 
        self.serial_port = serial.Serial()
        self.serial_port.baudrate = 9600
        self.serial_port.parity = serial.PARITY_EVEN
        self.serial_port.stopbits = serial.STOPBITS_ONE
        self.serial_port.timeout = 0.001
        self.rts_state = False
 
        self.read_buffer = []
        self.read_lock = Lock()
        self.read_error_counter = 0
        self.read_error_container = []
        self.cancel_read_thread = False
        self.read_thread = Thread(target=self._reading)
        self.read_thread.daemon = True
        self.packet_buffer = Queue()
 
        self.cts_counter = 0.0
        self.cts_thread = Thread(target=self._cts_watcher)
        self.cts_thread.daemon = True
 
        self.write_buffer = PriorityQueue()
        self.write_counter = 0
 
        self.cancel_write_thread = False
        self.write_thread = Thread(target=self._writing)
        self.write_thread.daemon = True

    def __enter__(self):
        self.connect()
        return self

    def __exit__(self, *_args):
        self.disconnect()

    @property
    def ntsc(self):
        return self.rts_state
 
    @ntsc.setter
    def ntsc(self, value):
        if self.rts_state == value:
            return
        self.serial_port.setRTS(value)
        self.rts_state = value
 
    @staticmethod
    def _calculate_checksum(packet):
        result = 0
        for value in packet:
            result ^= value
        return result
 
    def _cut_read_buffer(self, offset=1):
        with self.read_lock:
            self.read_buffer = self.read_buffer[offset:]
 
    def _wait_free_bus(self, waiting=17, timeout=1000):
        # 
        # FIXME Log message doesn't match ``if`` condition.
        # 
        if waiting >= timeout:
            log(
                'Error: Waiting Time (%sms) is bigger than Timeout Time (%sms)'
                    % (waiting, timeout)
            )
            return False

        for _ in xrange(timeout):
            if self.cts_counter >= waiting:
                return True
            time.sleep(0.001)
 
        return False
 
    def _reading(self):
        while not self.cancel_read_thread:
            data = self.serial_port.read(50)
            if data:
                with self.read_lock:
                    self.read_buffer.extend(ord(x) for x in data)
            self._read_bus_packet()
 
        log('Read/Write Thread finished')
 
    def _writing(self):
        while not self.cancel_read_thread:
            try:
                prio, write_counter, data = self.write_buffer.get(timeout=1)
                try:
                    while self.cts_counter < 7.0:
                        time.sleep(0.001)
 
                    self.serial_port.write(data)
                    self.cts_counter = 0.0
                    self.serial_port.flush()
                    self.cts_counter = 0.0
                    log(
                        '\033[1;33;40mWRITE:\033[0m %s'
                            % ' '.join('%02X' % i for i in data)
                    )
                except serial.SerialException:
                    self.write_buffer.put((prio, write_counter, data))
            except Empty:
                pass
 
    def _cts_watcher(self):
        while not self.cancel_read_thread:
            if self.serial_port.getCTS():
                self.cts_counter += 0.1
                time.sleep(0.0001)
            else:
                self.cts_counter = 0.0
                # 
                # TODO Maybe sleeping a little or move the `sleep()`
                #   in the ``if`` branch after the ``if``/``else``?
                # 
 
        log('CTS Thread finished')
 
    def set_port(self, device_path):
        self.serial_port.port = device_path
 
    def connect(self):
        self.serial_port.open()
        self.serial_port.setRTS(0)
        self.cts_thread.start()
 
        if not self._wait_free_bus(120, 3000):
            log('Error: Can not locate free Bus')
            raise RuntimeError('can not locate free bus')
 
        self.serial_port.flushInput()
 
        self.read_thread.start()
        self.write_thread.start()
  
    def disconnect(self):
        self.cancel_write_thread = True
        self.cancel_read_thread = True
        self.ntsc = False
        time.sleep(0.6)
        self.serial_port.close()
        log('disconnected')

    def read_bus_packet(self):
        try:
            return self.packet_buffer.get(timeout=1)
        except Empty:
            return None
 
    def _read_bus_packet(self):
        if self.cancel_read_thread:
            return None
 
        try:
            data_length = self.read_buffer[1]
        except IndexError:
            return None
 
        if not 3 <= data_length <= 37:
            self.read_error_container.append(self.read_buffer[0])
            self._cut_read_buffer()
            return None
 
        buffer_len = len(self.read_buffer)
        if buffer_len < 5 or buffer_len < data_length + 2:
            return None
 
        message = self.read_buffer[:data_length + 2]
 
        if self._calculate_checksum(message) == 0:
            if self.read_error_container:
                error_hex_string = ' '.join(
                    '%02X' % i for i in self.read_error_container
                )
                log('READ-ERR: %s' % error_hex_string)
                self.read_error_counter += len(self.read_error_container)
                self.read_error_container = []
 
            self._cut_read_buffer(data_length + 2)
 
            self.packet_buffer.put_nowait({'src': message[0],
                                           'len': data_length,
                                           'dst': message[2],
                                           'data': message[3:data_length + 1],
                                           'xor': message[-1]})
        else:
            self.read_error_container.append(self.read_buffer[0])
            self._cut_read_buffer()
            return None
 
    def write_bus_packet(
        self, src, dst, data, highprio=False, veryhighprio=False, repeat=1
    ):
        # 
        # FIXME The ``except`` needs explicit exceptions because it is
        #   unclear under what circumstances the ``except`` is a proper
        #   handling of the exception.
        # 
        try:
            packet = [src, len(data) + 2, dst]
            packet.extend(data)
        except:
            packet = [int(src, 16), len(data) + 2, int(dst, 16)]
            packet.extend([int(s, 16) for s in data])
 
        packet.append(self._calculate_checksum(packet))
        for _ in xrange(repeat):
            self.write_buffer.put_nowait(
                (
                    0 if highprio or veryhighprio else 1,
                    0 if veryhighprio else self.write_counter,
                    bytearray(packet)
                )
            )
            self.write_counter += 1
 
    def write_hex_message(self, hexstring):
        hexstring_tmp = hexstring.upper().split(' ')
        src = int(hexstring_tmp[0], 16)
        dst = int(hexstring_tmp[2], 16)
        data = [int(s, 16) for s in hexstring_tmp[3:-1]]
        self.write_bus_packet(src, dst, data)
 
 
class InputThread(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.daemon = True
        self.finished = False
        self.write = False
 
    def run(self):
        try:
            while not self.finished:
 
                time.sleep(0.5)
                input = raw_input()
 
                if input == "q":
                    log('wait for disconnecting IBUS')
                    self.finished = True
                elif input == "w":
                    self.write = True
                    log("\033[1;33;40mType Message for writing to IBus (example:F0 LL 68 01 CK) <- type 'LL' or 'CK' if unknown:\033[0m")
                    while self.write:
                        input = raw_input()
                        if input == '\x1b':
                            pass
                        elif input == 'r':
                            self.write = False
                        elif input == 'q':
                            log('wait for disconnecting IBUS')
                            self.finished = True
                        else:
                            IBUS.write_hex_message(input)
 
        except (KeyboardInterrupt, SystemExit):
            sys.exit()


def help():
    print('\n')
    print(' -h,           show this information')
    print(' -dev,         set device (default:/dev/ttyUSB0)')
    print(' -log,         enables logfile')
    print(' --- when Script is running ---')
    print(' w + enter,    switches to write mode')
    print(' r + enter,    switches back from write to read mode')
    print(' q + enter,    stops the script')
 
IBUS = IBusHandler()
INPUTTHREAD = InputThread()
 
 
def start_ibus(device='/dev/ttyUSB0'):
 
    def read_ibus_packages():
        while not INPUTTHREAD.finished:
            packet = IBUS.read_bus_packet()
            if packet:
                while INPUTTHREAD.write:
                    time.sleep(1)
                log('\033[94mREAD:\033[0m %02X %02X %02X %s %02X' % (packet['src'], packet['len'], packet['dst'], ' '.join(['%02X' % i for i in packet['data']]), packet['xor']))
 
        log('READ PACKAGES CLOSED')
 
    log('IBusTesterVersion: %s' % __version__)
    log('SerialModulVersion: %s' % serial.VERSION)
    log('SerialDevice: %s' % device)
 
    IBUS.set_port(device)
 
    log('Connecting')
    # 
    # TODO Narrow exception handling so the `IBusHandler` instance can be
    #   used with the ``with`` keyword instead of using
    #   ``try``/``finally``.
    # 
    try:
        IBUS.connect()
    except:
        log('Error: Can not open serial device')
        return
    try:
        ibus_st = Thread(target=read_ibus_packages)
        ibus_st.daemon = True
        ibus_st.start()
        log('Connected')
     
        INPUTTHREAD.start()
     
        while not INPUTTHREAD.finished:
            try:
                # print("..Script's Thread..")
                time.sleep(2)
            except (KeyboardInterrupt, SystemExit):
                INPUTTHREAD.finished = True
    finally:    
        IBUS.disconnect()
     
 
def main():
    count = len(sys.argv) - 1
 
    given_args = sys.argv
 
    if '-h' in given_args:
        help()
        sys.exit(0)
 
    if "-log" in given_args:
        LOGGER.enable_logfile = True
        LOGGER.set_file('ibustester.log')
        count -= 1
 
    if count > 0:
        if "-dev" in given_args:
            start_ibus(given_args[given_args.index('-dev')+1])
        else:
            print('\n Unknown Arguments given!', sys.argv[1:])
            help()
            sys.exit(0)
    else:
        start_ibus()
 
    log('SERVICE COMPLETELY CLOSED')
    sys.exit(0)


if __name__ == '__main__':
    main()
Zu den Fragen:

Ad 1.: Datenpakete könnten eine eigene Klasse bekommen.

Für die Tupel die hier und da in Queues gesteckt werden, könnte man `collections.namedtupel` verwenden um den Quelltext verständlicher zu machen.

Ad 2.: Doch noch ein `put()` ausführen mit einem Wert der das Ende signalisiert, so das der Empfänger weiss es wird da auch nichts mehr kommen.

Ad 3.: Statt `raw_input()` könntest Du `curses` verwenden. Das gibt's allerdings unter Windows nicht in der Standardbibliothek.
Antworten