ArtNet Controller / Library

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
T64
User
Beiträge: 16
Registriert: Dienstag 18. Oktober 2011, 16:36

Hallo Leute,
ich wollte mal Fragen ob irgendwer Interesse am Programmieren eines ganz banalen ArtNet Controllers in Python hätte.
Alleine werde ich das wahrscheinlich erstmal nicht schaffen, da ich mich noch nie mit Netzwerkprotkollen beschäftigt habe, aber den kompletten Pseudo-Code hab ich schonmal.
Das ArtNet Netzwerkprotokoll dürfte hier eigentlich für so manchen hier ganz interessant sein, da man mit einem ArtNet Controller direkt aus Python heraus einen Großteil der modernen Bühnentechnik, aber auch z.B. einfache LED Lampen steuern könnte.
Tim
Benutzeravatar
fecub
User
Beiträge: 24
Registriert: Freitag 14. November 2008, 16:53
Kontaktdaten:

hi,

wenn du niemanden finden solltest, empfehle ich dir das hier: http://i2h.de/fQg7
Meines wissens benutzt Artnet als Netzwerkprotokoll UDP. In der anleitung wird gezeigt wie man sowas programmiert!

gruß
T64
User
Beiträge: 16
Registriert: Dienstag 18. Oktober 2011, 16:36

So, ich habe mich inzwischen selbst dran versucht und habe auch schon ein ganzes Stück geschafft.

An einer Stelle komme ich aber nicht weiter:
Es soll in einem UDP Paket zusammen mit anderen Daten eine Adresse gesendet werden. In der Spezifikation steht:

Bit 15: 0
Bit 14-8: Net (Zahl zwischen 1-128, Usereingabe)
Bit 7-4: Subnet (Zahl zwischen 1-16, Usereingabe)
Bit 3-0: Universe (Zahl zwischen 1-16, Usereingabe)
Dann soll das Low Byte zuerst und dann das High Byte jeweils als Int8 (?) gesendet werden.

Wie mache ich daraus jetzt einen String, den ich in einem UDP Paket senden kann?
Vielen Dnak schonmal im Vorraus für eure Hilfe :-)
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

T64 hat geschrieben:So, ich habe mich inzwischen selbst dran versucht und habe auch schon ein ganzes Stück geschafft.

An einer Stelle komme ich aber nicht weiter:
Es soll in einem UDP Paket zusammen mit anderen Daten eine Adresse gesendet werden. In der Spezifikation steht:

Bit 15: 0
Bit 14-8: Net (Zahl zwischen 1-128, Usereingabe)
Bit 7-4: Subnet (Zahl zwischen 1-16, Usereingabe)
Bit 3-0: Universe (Zahl zwischen 1-16, Usereingabe)
Dann soll das Low Byte zuerst und dann das High Byte jeweils als Int8 (?) gesendet werden.

Wie mache ich daraus jetzt einen String, den ich in einem UDP Paket senden kann?
Vielen Dnak schonmal im Vorraus für eure Hilfe :-)
Du solltest dir das struct-Modul anschauen. Allerdings sehe ich da nicht, wie man da festlegt, wieviele Bits benötigt werden … Also erkläre ich mal, wie ich es machen würde:
  • Ich verkette Subnet und Universe als binäre Zahl miteinander, sodass ich einen 8-bit Integer erhalte:

    Code: Alles auswählen

    >>> subnet = 13
    >>> universe = 9
    >>> low_byte = "".join([bin(subnet)[2:], bin(universe)[2:]])
    >>> low_byte
    '11011001'
    >>> int(low_byte, 2)
    217
  • Für Net und das Nullbit ist es einfacher:

    Code: Alles auswählen

    >>> net = 123
    >>> high_byte = '{:0>8}'.format(bin(net)[2:])
    '01111011'
    >>> int(high_byte, 2)
    123
Nachdem du die Zahlen hast, musst du sie nur noch zu einem Buchstaben machen.

Code: Alles auswählen

chr(217); chr(123)
BlackJack

@nomnom: Über die Zeichenkettendarstellung der Binärzahlen zu gehen ist unnötig umständlich und ausserdem funktioniert das so wie Du es zeigst nicht bei allen Zahlen, sondern nur bei solchen wo sowohl `subnet` als auch `universe` mindestens den Wert 8 haben. Sonst sind das nämlich keine 4 Bit in der Zeichenkettendarstellung.

@T64: Die Zahlen können so nicht stimmen, denn man bekommt in vier Bits nur Zahlen zwischen 0 und 15 (inklusive) und in sieben Bits 0 bis 127 (inklusive).

Man könnte das `struct`-Modul aus der Standardbibliothek verwenden:

Code: Alles auswählen

import struct


def main():
    assert struct.calcsize('<H') == 2
    net = 123
    subnet = 13
    universe = 9
    data = struct.pack('<H', net << 8 | subnet << 4 | universe)
    print repr(data)


if __name__ == '__main__':
    main()
Oder wenn es auch etwas externes sein darf, könnte man zum `construct`-Modul greifen.
T64
User
Beiträge: 16
Registriert: Dienstag 18. Oktober 2011, 16:36

Erstmal vielen Dank für die schnelle Hilfe, ich hab es auch gleich mal eingebaut, aber wie sollte es anders sein, es geht noch nicht.
Ein Programm, dass ArtNet empfangen kann (OLA) meldet nur "ArtNet got unknown packet 50".
Der vollständigkeit halber ist hier noch der Code, ich erwarte aber nicht, dass da irgendwer durchblickt, ohne ArtNet zu kennen...

Code: Alles auswählen

import socket
import struct

class ArtNet():
    def __init__(self):
        self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    def ArtDMX_broadcast(self, dmxdata, address):
        net, subnet, universe = address
        #ip = "2.255.255.255"
        ip = "192.168.178.26"
        port = 6454
        content = []
        # Name, 7byte + 0x00
        content.append("lumino" + chr(0x00))
        # OpCode ArtDMX -> 0x5000, Low Byte first
        high, low = self.getByteLength(int(0x5000))
        content.append(chr(low) + chr(high))
        # Protocol Version 14, High Byte first
        high, low = self.getByteLength(14)
        content.append(chr(high) + chr(high))
        # Order -> nope -> 0x00
        content.append(chr(0x00))
        # Eternity Port
        content.append(chr(1))
        # Address
        low = "".join([bin(subnet)[2:], bin(universe)[2:]])
        low = int(low, 2)
        high = '{:0>8}'.format(bin(net)[2:])
        high = int(high, 2)
        content.append(chr(low) + chr(high))
        # Length of DMX Data, High Byte First
        high, low = self.getByteLength(len(dmxdata))
        content.append(chr(high) + chr(low))
        # DMX Data
        for d in dmxdata:
            content.append(chr(d))
        content = "".join(content)
        print content
        self.s.sendto(content, (ip, port))

    def getByteLength(self, length):
        temp = struct.pack('!h', length)
        byteList = [firstLength, secondLength] = struct.unpack('BB', temp)
        return list(byteList)
    
    def close(self):
        self.s.close()

dmx_data = [120] * 512
address = [0, 0, 2]
ArtNet().ArtDMX_broadcast(dmx_data, address)
(Da ist natürlich noch nichts optimiert, alles stur nach den Spezifikationen...)
Für den, den es Interessiert, die Spezifiktaionen gibt es hier: http://tinyurl.com/c6649r8
BlackJack

@T64: Stimmt die Byte-Reihenfolge?

Es ist übrigens extrem unsinnig eine Zahl mit `struct.pack()` umzuwandeln, sie danach mit `struct.unpack()` in zwei Bytewerte zu wandeln, und im nächsten Schritt dann wieder mit `chr()` und ``+`` in eine Zeichenkette umzuwandeln. Ähnlich unsinnig ist so etwas wie ``int(0x5000)``. Eine ganze Zahl braucht man nicht in eine ganze Zahl umwandeln, denn es ist ja schon eine ganze Zahl.

Der Name `getByteLength()` ist ungünstig, denn Du verwendest das nicht nur für Längen, sondern grundsätzlich für 16-Bit-Werte. Das ist auch keine echte Methode. Die ganze Klasse erscheint mir ein wenig fragwürdig.

Das mit `bin()` funktioniert so nicht, wie ich in meinem letzten Beitrag schon erwähnte.

Bei der Version hast Du zweimal das High-Byte genommen.
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

BlackJack hat geschrieben:@nomnom: Über die Zeichenkettendarstellung der Binärzahlen zu gehen ist unnötig umständlich
Ich wusste leider nicht, wie man es sonst hätte machen können. Bin nicht so vertraut mit binären Zahlen …
BlackJack hat geschrieben:und ausserdem funktioniert das so wie Du es zeigst nicht bei allen Zahlen, sondern nur bei solchen wo sowohl `subnet` als auch `universe` mindestens den Wert 8 haben. Sonst sind das nämlich keine 4 Bit in der Zeichenkettendarstellung.
:oops: Du hast natürlich Recht!
T64
User
Beiträge: 16
Registriert: Dienstag 18. Oktober 2011, 16:36

Danke für die ganzen Tips, der Code müsste jetzt schon um einiges schöner aussehen, funktionieren tut er trotzem nicht :-(

Code: Alles auswählen

import socket
import struct

class ArtNet():
    def __init__(self):
        self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    def ArtDMX_broadcast(self, dmxdata, address):
        # broadcast ip
        ip = "2.255.255.255"
        # UDP ArtNet Port
        port = 6454
        content = []
        # Name, 7byte + 0x00
        content.append("lumino" + "\x00")
        # OpCode ArtDMX -> 0x5000, Low Byte first
        content.append(struct.pack('<H', 0x5000))
        # Protocol Version 14, High Byte first
        content.append(struct.pack('>H', 14))
        # Order -> nope -> 0x00
        content.append("\x00")
        # Eternity Port
        content.append(chr(1))
        # Address
        net, subnet, universe = address
        content.append(struct.pack('<H', net << 8 | subnet << 4 | universe))
        # Length of DMX Data, High Byte First
        content.append(struct.pack('>H', len(dmxdata)))
        # DMX Data
        for d in dmxdata:
            content.append(chr(d))
        # stitch together
        content = "".join(content)
        # debug
        print content
        # send
        self.s.sendto(content, (ip, port))
    
    def close(self):
        self.s.close()

# Test
dmx_data = [120] * 512
address = [0, 0, 2]
ArtNet().ArtDMX_broadcast(dmx_data, address)
Vielleicht kann jemand doch mal in die Spezifiktation gucken, ob ich vielleicht was total falsch verstanden habe, es geht um http://tinyurl.com/c6649r8 Seite 22-23.
Tim
T64
User
Beiträge: 16
Registriert: Dienstag 18. Oktober 2011, 16:36

Es geht! Halleluja! Murphy hat recht, es war das falsch, woran ich am wenigsten gedacht habe: der Name muss aus 7 Buchstaben bestehen, "lumino" hat aber nur 6!
Jetzt muss es nur noch in mein Programm eingebaut werden und ich bin glücklich :-D

Funktionierender Code:

Code: Alles auswählen

import socket
import struct

class ArtNet():
    def __init__(self):
        self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    def ArtDMX_broadcast(self, dmxdata, address):
        # broadcast ip
        ip = "2.255.255.255"
        # UDP ArtNet Port
        port = 6454
        content = []
        # Name, 7byte + 0x00
        content.append("luminos" + "\x00")
        # OpCode ArtDMX -> 0x5000, Low Byte first
        content.append(struct.pack('<H', 0x5000))
        # Protocol Version 14, High Byte first
        content.append(struct.pack('>H', 14))
        # Order -> nope -> 0x00
        content.append("\x00")
        # Eternity Port
        content.append(chr(1))
        # Address
        net, subnet, universe = address
        content.append(struct.pack('<H', net << 8 | subnet << 4 | universe))
        # Length of DMX Data, High Byte First
        content.append(struct.pack('>H', len(dmxdata)))
        # DMX Data
        for d in dmxdata:
            content.append(chr(d))
        # stitch together
        content = "".join(content)
        # debug
        print content
        # send
        self.s.sendto(content, (ip, port))
    
    def close(self):
        self.s.close()
(ist noch nicht vollständig getestet, aber für mich reichts erstmal...)
Danke an alle!
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Code: Alles auswählen

"luminos" + "\x00"
Warum so umständlich? Du schreibst ja auch nicht:

Code: Alles auswählen

"l" + "u" + "m" + "i" + "n" + "o" + "s" + "\x00"
sondern einfach

Code: Alles auswählen

"luminos\x00"
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
T64
User
Beiträge: 16
Registriert: Dienstag 18. Oktober 2011, 16:36

Da der komplette erste Teil des Pakets statisch ist, muss ich den auch nicht jedes mal neu berechnen, dass wird später nur in der __init__ gemacht. Das lustige ist aber, dass in der Spezifikatioen wirklich "l" + "u" + "m" + "i" + "n" + "o" + "s" + "\x00" steht :-D.
BlackJack

@T64: In der Spezifikation steht vom Inhalt her eigentlich 'Art-Net\x00' und es sah mir nicht so aus als wenn das variabel wäre.
T64
User
Beiträge: 16
Registriert: Dienstag 18. Oktober 2011, 16:36

Oh, ich hatte das immer als ID des Senders verstanden, aber jetzt wo ich es nochmal gelesen hab, kann es wirklich sein, dass es immer "Art-Net\x00" sein muss, danke für den Hinweis.
T64
User
Beiträge: 16
Registriert: Dienstag 18. Oktober 2011, 16:36

Nur der Vollständigkeit halber:
Unter http://code.google.com/p/luminosus/downloads/list gibt es ab sofort eine Art ArtNet Python Library (naja, eher Skript, es entspricht wo gaar nicht den Standarts einer Library :-S ...) falls irgendwer in dem Bereich mal was suchen sollte.
Es besteht aus einem Sender (Lichtmischpult z.B., oder auch Android Handy) und einem Empfänger (also ein echter ArtNet Node, der alle Universen eines Subnets in ein Array schreibt).
Viel Spaß :-D
Antworten