miniDMX mit Python senden

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
T64
User
Beiträge: 16
Registriert: Dienstag 18. Oktober 2011, 16:36

Hi Leute,
ich bin neu hier im Forum und hoffe einfach mal, dass sich jemand hier mit DMX und miniDMX auskennt :-D

Es soll mit einem Python Programm über eine virtuelle serielle Schnittstelle das miniDMX Protokoll gesendet werden.
Das Protokoll wird auf der offiziellen Website (http://www.dzionsko.de/elektronic/minidmx/minidmx.htm) so definiert:
Einfach folgenden Datenblock mit 115.200 Bits pro Sekunde, 8 Datenbits, ohne Parität, einem Stoppbit und ohne Software- oder Hardware-Flußkontrolle über die serielle Schnittstelle senden:
[...]
DMX-Out mit 512 Kanälen:
$5A - Blockstart
$A2 - Befehl: DMX-Out mit 512 Kanäle
512 Bytes für die Kanäle 1 bis 512
$A5 - Blockende

Antwort vom MiniDMX bei erfolgreicher Befehlsbearbeitung (wie bei v1.0/v1.1)
Nach max. 100ms sendet MiniDMX folgendes zurück:
$5A - Blockstart
$C1 - Befehl erfolgreich ausgeführt
$A5 - Blockende
Wie man virtuelle COM-Ports erstellt und nutzt weiß ich inzwischen und nutze es auch für ein anderes Protokoll, mich würde aber interessieren, ob der folgende Code funktionieren könnte:

Code: Alles auswählen

import serial, binascii

dmxdict = {}

for kanal in range(1,513):
    dmxdict[kanal] = '0'

try:
    serial = serial.Serial('/dev/ttyACM0', baudrate=115200)
    print "Verbindung zu miniDMX Adapter hergestellt."
except serial.serialutil.SerialException:
    print "Verbindung zu miniDMX Adapter gescheitert."

while True:
    serial.write(binascii.a2b_hex('5A'))
    serial.write(binascii.a2b_hex('A2'))
    for kanal in range(1, 513):
        serial.write(chr(dmxdict[kanal]))
    serial.write(binascii.a2b_hex('A5'))
    succes = serial.read(3)
    if not succes == binascii.a2b_hex('5AC1A5'):
        break
(dmxdict mit den DMX-Werten wird später von einer anderen Funktion geändert)

Ich habe nämlich noch kein miniDMX Gerät, möchte aber die Ausgabe schon jetzt in mein Programm mit einbauen, da es sonst für die meisten anderen unbrauchbar wäre...
Danke schon mal im Vorraus für eure Hilfe,
Tim
deets

Ich denke du willst dein dmxdict mit 0 statt mit "0" initialisieren, oder?

Und ein dict zu verwenden ist auch eher unnoetig, wenn man eh weiss, dass es 512 Kanaele sind - da wuerde ich eine Liste mit 512 Werten nehmen. Das ist aber zu einem guten Teil Geschmackssache.

Und deine Fehlerbehandlung ist nicht korrekt. Laut Spec muss innerhalb von 100ms eine Antwort kommen. Du solltest also einen read-timeout setzten, und den ebenfalls abfangen.
T64
User
Beiträge: 16
Registriert: Dienstag 18. Oktober 2011, 16:36

Danke für die schnelle Antwort :-)
Ich habe mich fürs dict entschieden, da im restlichen Programm auch teilweise nur ein kleiner Teil der 512 Kanäle genutzt wird und dann ein dict einfacher ist.
Das mit der 0 stimmt, war ein Flüchtigkeitsfehler...
Ich werde mir mal read-timout aneignen und hinzufügen (habs noch nie benutzt :-( ), aber der richtige Weg scheints ja zumindestens zu sein :-D
Tim
BlackJack

@T64: Der Name der Datenstruktur gehört eigentlich nicht in den Namen für ein Objekt. Das sollte die Bedeutung reflektieren und nicht den Typ (es sei denn der geht auch als Bedeutung durch).

Das Wörterbuch hätte man mit `dict()` und einem Generatorausdruck oder `dict.fromkeys()` einfacher initialisieren können. Eine Liste mittels ``*``-Operator:

Code: Alles auswählen

dmx_values = dict.fromkeys(xrange(512), 0)
# oder
dmx_values = [0] * 512
Das Programm funktioniert nicht so wirklich bei einer `SerialException`. Du fängst die zwar ab, aber versuchst dann *trotzdem* danach in der Schleife Daten zu senden und zu empfangen. Die Schleife sollte entweder in einen ``else``-Zweig hinter das ``except``, oder vielleicht mit in den ``try``-Block.

Warum greifst Du auf `serial.SerialException` eigentlich über `serial.serialutil.SerialException` zu?

Statt `binascii` zu bemühen, hätte ich die Bytewerte als Escape-Sequenz direkt in die literale Zeichenkette geschrieben. Also ``'\x5a'`` statt ``binascii.a2b_hex('5A')``.

`succes` (sic!) ist ein unpassender Name, denn es ist ja kein Erfolg, sondern die Antwort vom externen Gerät. Statt ``not a == b`` würde man auch eher ``a != b`` als Bedingung formulieren. Dann muss man beim Lesen nicht überlegen ob ``not`` oder ``==`` stärker bindet.
T64
User
Beiträge: 16
Registriert: Dienstag 18. Oktober 2011, 16:36

Ok, ich hoffe ich habe keinen Vorschlag vergessen:

Code: Alles auswählen

import serial, platform, sys

dmx_values = dict.fromkeys(xrange(1, 513), 0)

os_name = platform.system()
if "Windows" in os_name:
    portname = 'COM4'
elif "Darwin" in os_name:
    portname = '/dev/tty.usbmodem621'
elif "Linux" in os_name:
    portname = '/dev/ttyACM0'
else:
    print "Betriebssystem konnte nicht erkannt werden."
    sys.exit()

try:
    serial = serial.Serial(portname, baudrate=115200, timeout=0.1)
    if serial != None:
        serial.close()
        serial.open()
        print "Verbindung zu miniDMX Adapter hergestellt."
    else:
        print "Verbindung zu miniDMX Adapter gescheitert."
        sys.exit()
except serial.SerialException:
    print "Aufbau der seriellen Verbindung gescheitert."
    sys.exit()

while True:
    serial.write('\x5a\xa2')
    values = dmx_values.copy()
    for kanal in xrange(1, 513):
        serial.write(chr(values[kanal]))
    serial.write('\xa5')
    answer = serial.read(3)
    if answer != '\x5a\xc1\xa5':
        break
Die Fehlerbehandlung fiel durch das Kürzen des Codes hier fürs Forum weg, die anderen Vorschläge habe ich auch versucht mit einzubauen.
Danke nochmal für die Hinweise, wenn im Code soweit erstmal keine Fehler mehr drin sind, sollte der nächste Schritt denke ich mal sein, dass ich mir jemanden mit einem miniDMX Gerät zum testen suche :-)
deets

Also zum einen ist der Code so natuerlich nicht in einem Programm verwendbar - sys.exit in etwas ,dass ein Plugin oder Modul darstellt geht natuerlich gar nicht. Du solltest das besser in Funktionen Kapseln, und wegen mir eine main-Funktion machen.

Und das Ergebnis von serial.Serial(...) ist *niemals* None - wenn, fliegt eine Exception.

Last but not least vergleicht man nicht mit dem !=-Operator auf None (oder ==), sondern mit "is".

foo is None

bzw.

foo is not None

Und ich wuerde dir empfehlen, den Timeout etwas groesser zu waehlen - sonst kann es knapp werden, falls ein Adapter wirklich an die Grenzen der Spec geht, und der Timeout sehr praezise kommt. Mach den einfach mal doppelt oder dreifach so gross.
T64
User
Beiträge: 16
Registriert: Dienstag 18. Oktober 2011, 16:36

Ok, den timeout verändere ich nochmal, 300ms sollten reichen, oder?
Mit dem None hast du auch recht, das war noch ein Überrest aus einer Zeit, als ich "serial" als globale Variable mit None initialisiert hatte.
Das der Code nicht exakt so in das Programm kommt ist klar, aber das mit sys.exit() ist ok, da das Programm nichts anderes macht, als DMX zu senden, nur das noch verschiedene andere Protokolle dazukommen.
Aber warum vergleicht man eigentlich nicht mit '!=' ? Ist das nicht das selbe wie 'is not'?
deets

Nein. None ist ein Singleton, und darum vergleicht man das ueber die Objekt-Identitaet - und das ist der is-Operator.

Und es gilt auch nicht, dass "a == b <=> a is b".

ZB so nicht:

Code: Alles auswählen

>>> a = 12312039 
>>> b = 12312039
>>> a == b
True
>>> a is b
False
>>> a = 1
>>> b = 1
>>> a is b
True
Python cached kleine Zahlen, und dann sind sie identisch. Aber grosse nicht, dann vergleichen sie sich nur gleich.
BlackJack

@T64: Ich habe aus dem Quelltext nicht so richtig verstanden, warum Du von `dmx_values()` eine Kopie erstellst!? Und ich hätte das wahrscheinlich auch so geschrieben, dass nur ein ``write()`` für einen kompletten Datenblock verwendet wird. Ungetestet:

Code: Alles auswählen

    data = ''.join(chr(dmx_values[i]) for i in xrange(1, 513))
    serial.write('\x5a\xa2%s\xa5' % data)
T64
User
Beiträge: 16
Registriert: Dienstag 18. Oktober 2011, 16:36

Ich habe es letztendlich auch mit nur einem write() Block gemacht, es war nur erstmal zum Verständnis einfacher mit mehreren. Die Kopie habe ich erstellt, da das ganz in einem Thread läuft und dmx_values() aus einem anderen Thread geändert wird.
BlackJack

@T64: Bei der Kopie verstehe ich nicht so ganz was das bringen soll? Ob Du nun während ein anderer Thread Werte ändert Deine Daten zum Senden zusammenstellst, oder ob Du während ein anderer Thread Werte ändert mittendrin eine Kopie machst, kommt vom Effekt doch eigentlich auf das Gleiche hinaus!? Wenn Du dort „sichere“ Zustände haben willst, kommst Du um explizite Sperren nicht herum. An der Stelle ist übrigens auch die Gefahr gegeben, dass man sich zu sehr darauf verlässt, dass etwas in der aktuellen Implementierung von CPython threadsicher ist, was von der Sprache aber gar nicht garantiert wird. So etwas kann einem dann mit alternativen Python-Implementierungen (IronPython, Jython, PyPy) oder vielleicht auch zukünftigen CPython-Implementierungen auf die Füsse fallen.
Antworten