From d64c6a707effedcced2b1fe7b405b02bdce11481 Mon Sep 17 00:00:00 2001 From: reverend <reverend@reverend2048.de> Date: Tue, 11 Dec 2018 11:28:30 +0000 Subject: [PATCH] Copied form source repo --- art_net_test.py | 18 ++ packets/__init__.py | 216 ++++++++++++++++++ packets/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 5307 bytes .../__pycache__/output_packet.cpython-37.pyc | Bin 0 -> 1800 bytes .../__pycache__/poll_packet.cpython-37.pyc | Bin 0 -> 4324 bytes packets/ipprog_packet | 3 + packets/ipprog_packet.py | 16 ++ packets/output_packet.py | 44 ++++ packets/poll_packet.py | 120 ++++++++++ 9 files changed, 417 insertions(+) create mode 100644 art_net_test.py create mode 100644 packets/__init__.py create mode 100644 packets/__pycache__/__init__.cpython-37.pyc create mode 100644 packets/__pycache__/output_packet.cpython-37.pyc create mode 100644 packets/__pycache__/poll_packet.cpython-37.pyc create mode 100644 packets/ipprog_packet create mode 100644 packets/ipprog_packet.py create mode 100644 packets/output_packet.py create mode 100644 packets/poll_packet.py diff --git a/art_net_test.py b/art_net_test.py new file mode 100644 index 0000000..611a72a --- /dev/null +++ b/art_net_test.py @@ -0,0 +1,18 @@ +from packets.output_packet import DmxPacket +from packets.poll_packet import PollPacket +pp = PollPacket() +p = DmxPacket([128, 0, 0]) +e = p.encode() +print("bytearray(" + str(e)) +import socket +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +.connect(("10.0.2.22", 6454)) +s.send(e) +#s.send(pp.encode()) + +b = bytearray() +b.extend("Art-Net\x00".encode()) +b.extend([0x00]) +b.extend([0x0050, 0, 14, 0, 0, 0, 0, 0, 0x02, 255, 0, 0]) +print(b) +s.send(b) diff --git a/packets/__init__.py b/packets/__init__.py new file mode 100644 index 0000000..c376ba4 --- /dev/null +++ b/packets/__init__.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import bitstring + +class ArtNetPacket(object): + """ + This is the super class of all art-net packets. It contains basic functionality and + some fields needed for creating packets. + """ + opcode = None + schema = () + + opcode_map = dict() + header = 'Art-Net\0' + protocol_version = 14 + filler1 = 42 + filler2 = 32 + filler4 = 0 + + @classmethod + def register(cls, packet_class): + """ + Registers a packet. It maps the opcode to the given implementation of the packet + """ + cls.opcode_map[packet_class.opcode] = packet_class + return packet_class + + @classmethod + def get_available_packets(cls): + """ + Returns a list of all registered packets that are available for use. + """ + return list(cls.opcode_map.values()) + + @classmethod + def decode(cls, address, data): + """ + Decodes a package + """ + # Retreives the opcode and looks for matching packets. + [opcode] = struct.unpack('!H', data[8:10]) + if(opcode not in cls.opcode_map): + raise NotImplementedError('%x' % opcode) + + # Iterates through the schema of the corresponding class + klass = cls.opcode_map[opcode] + b = bitstring.BitStream(bytes=data) + fields = dict() + for name, fmt in klass.schema: + # If there is a parse function for value the function is called + accessor = getattr(klass, 'parse_%s' % name, None) + if(callable(accessor)): + fields[name] = accessor(b, fmt) + else: + # Else the value is read directly from the bitstream + fields[name] = b.read(fmt) + + # Creating an instance of the packet-class and set the values + p = klass(address=address) + for k,v in fields.items(): + setattr(p, k, v) + + return p + + def __init__(self, address=None, sequence=0, physical=0, universe=0): + self.address = address + self.sequence = sequence + self.physical = physical + self.universe = universe + + for name, fmt in self.schema: + if not(hasattr(self, name)): + setattr(self, name, 0) + + def __str__(self): + return '<%(klass)s from %(address)s:%(universe)s/%(physical)s>' % dict( + klass = self.__class__.__name__, + address = self.address, + universe = self.universe, + physical = self.physical, + ) + + def encode(self): + """ + Encodes a package into a bytearray. + """ + fields = [] + # Iterates through all entries of the schema + for name, fmt in self.schema: + # If there is a function to access the value, the function is called + # else the value is revtreived directly + accessor = getattr(self, 'format_%s' % name, '\0') + if(callable(accessor)): + value = accessor() + else: + value = getattr(self, name) + # Store values in array + fields.append([name, fmt, value]) + + # Builds a bytearray to send as packet + fmt = ', '.join(['='.join([f,n]) for n,f,v in fields]) + data = dict([(n,v) for n,f,v in fields]) + return bitstring.pack(fmt, **data).tobytes() + +STANDARD_PORT = 6454 + +OPCODES = dict( + # This is an ArtPoll packet, no other data is contained in this UDP packet. + OpPoll = 0x0020, + # This is an ArtPollReply Packet. It contains device status information. + OpPollReply = 0x0021, + # Diagnostics and data logging packet. + OpDiagData = 0x0023, + # Used to send text based parameter commands. + OpCommand = 0x0024, + # This is an ArtDmx data packet. It contains zero start code DMX512 information for a single Universe. + OpOutput = 0x0050, + # This is an ArtDmx data packet. It contains zero start code DMX512 information for a single Universe. + OpDmx = 0x0050, + # This is an ArtNzs data packet. It contains non-zero start code (except RDM) DMX512 information for a single Universe. + OpNzs = 0x0051, + # This is an ArtAddress packet. It contains remote programming information for a Node. + OpAddress = 0x0060, + # This is an ArtInput packet. It contains enable disable data for DMX inputs. + OpInput = 0x0070, + # This is an ArtTodRequest packet. It is used to request a Table of Devices (ToD) for RDM discovery. + OpTodRequest = 0x0080, + # This is an ArtTodData packet. It is used to send a Table of Devices (ToD) for RDM discovery. + OpTodData = 0x0081, + # This is an ArtTodControl packet. It is used to send RDM discovery control messages. + OpTodControl = 0x0082, + # This is an ArtRdm packet. It is used to send all non discovery RDM messages. + OpRdm = 0x0083, + # This is an ArtRdmSub packet. It is used to send compressed, RDM Sub-Device data. + OpRdmSub = 0x0084, + # This is an ArtVideoSetup packet. It contains video screen setup information for nodes that implement the extended video features. + OpVideoSetup = 0x10a0, + # This is an ArtVideoPalette packet. It contains colour palette setup information for nodes that implement the extended video features. + OpVideoPalette = 0x20a0, + # This is an ArtVideoData packet. It contains display data for nodes that implement the extended video features. + OpVideoData = 0x40a0, + # This is an ArtMacMaster packet. It is used to program the Node's MAC address, Oem device type and ESTA manufacturer code. + # This is for factory initialisation of a Node. It is not to be used by applications. + OpMacMaster = 0x00f0, + # This is an ArtMacSlave packet. It is returned by the node to acknowledge receipt of an ArtMacMaster packet. + OpMacSlave = 0x00f1, + # This is an ArtFirmwareMaster packet. It is used to upload new firmware or firmware extensions to the Node. + OpFirmwareMaster = 0x00f2, + # This is an ArtFirmwareReply packet. It is returned by the node to acknowledge receipt of an ArtFirmwareMaster packet or ArtFileTnMaster packet. + OpFirmwareReply = 0x00f3, + # Uploads user file to node. + OpFileTnMaster = 0x00f4, + # Downloads user file from node. + OpFileFnMaster = 0x00f5, + # Node acknowledge for downloads. + OpFileFnReply = 0x00f6, + # This is an ArtIpProg packet. It is used to reprogramme the IP, Mask and Port address of the Node. + OpIpProg = 0x00f8, + # This is an ArtIpProgReply packet. It is returned by the node to acknowledge receipt of an ArtIpProg packet. + OpIpProgReply = 0x00f9, + # This is an ArtMedia packet. It is Unicast by a Media Server and acted upon by a Controller. + OpMedia = 0x0090, + # This is an ArtMediaPatch packet. It is Unicast by a Controller and acted upon by a Media Server. + OpMediaPatch = 0x0091, + # This is an ArtMediaControl packet. It is Unicast by a Controller and acted upon by a Media Server. + OpMediaControl = 0x0092, + # This is an ArtMediaControlReply packet. It is Unicast by a Media Server and acted upon by a Controller. + OpMediaContrlReply = 0x0093, + # This is an ArtTimeCode packet. It is used to transport time code over the network. + OpTimeCode = 0x0097, + # Used to synchronise real time date and clock + OpTimeSync = 0x0098, + # Used to send trigger macros + OpTrigger = 0x0099, + # Requests a node's file list + OpDirectory = 0x009a, + # Replies to OpDirectory with file list + OpDirectoryReply = 0x9b00 +) + +NODE_REPORT_CODES = dict( + RcDebug = ('0x0000', "Booted in debug mode"), + RcPowerOk = ('0x0001', "Power On Tests successful"), + RcPowerFail = ('0x0002', "Hardware tests failed at Power On"), + RcSocketWr1 = ('0x0003', "Last UDP from Node failed due to truncated length, Most likely caused by a collision."), + RcParseFail = ('0x0004', "Unable to identify last UDP transmission. Check OpCode and packet length."), + RcUdpFail = ('0x0005', "Unable to open Udp Socket in last transmission attempt"), + RcShNameOk = ('0x0006', "Confirms that Short Name programming via ArtAddress, was successful."), + RcLoNameOk = ('0x0007', "Confirms that Long Name programming via ArtAddress, was successful."), + RcDmxError = ('0x0008', "DMX512 receive errors detected."), + RcDmxUdpFull = ('0x0009', "Ran out of internal DMX transmit buffers."), + RcDmxRxFull = ('0x000a', "Ran out of internal DMX Rx buffers."), + RcSwitchErr = ('0x000b', "Rx Universe switches conflict."), + RcConfigErr = ('0x000c', "Product configuration does not match firmware."), + RcDmxShort = ('0x000d', "DMX output short detected. See GoodOutput field."), + RcFirmwareFail = ('0x000e', "Last attempt to upload new firmware failed."), + RcUserFail = ('0x000f', "User changed switch settings when address locked by remote programming. User changes ignored.") +) + +STYLE_CODES = dict( + # A DMX to / from Art-Net device + StNode = 0x00, + # A lighting console. + StController = 0x01, + # A Media Server. + StMedia = 0x02, + # A network routing device. + StRoute = 0x03, + # A backup device. + StBackup = 0x04, + # A configuration or diagnostic tool. + StConfig = 0x05, + # A visualiser. + StVisual = 0x06 +) \ No newline at end of file diff --git a/packets/__pycache__/__init__.cpython-37.pyc b/packets/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ecdeb1bb0d5b751b2faba7148f743d3714db68c GIT binary patch literal 5307 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 literal 0 HcmV?d00001 diff --git a/packets/__pycache__/output_packet.cpython-37.pyc b/packets/__pycache__/output_packet.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80d644c57511a8e8b826aaa094f308f47bb7617e GIT binary patch literal 1800 zcmaJ?&2Jk;6rb5IubntbLum-&BNdH^r8X`X1W^=`))g0Uq9g}c6^%AKV|&y6%FGzZ zDm^(;xq!rfP>x*qgO($w{tG?v-mL931Q=`H%)XiT-tT?P^P_gVMPU5-$I{c!9YX%X zkJ)fx^94-x2nZ*fCM2Z+rMS0XZv|Fr2X^WNj^Wvf8@Pg=dS4RmaQBjM_?p-g8Z@|l zL^|Fp&_g=3THKSmFZ4keJrlZPSB?FH$NRk}M^*dic(32vJM6K8{lnvG;pq6=ubwb- z){jtlHUO#_2AUuN<s`6#&FLBm9Bu_JUwlCZ)WB#49`|_Tk^l^!H(~X8i!Z?1d_f!% zv;bPWYWC6}Ye1E|U%*Gl?^u;L5+N6|Y@D2Hp|(FmR$fFo7o`_xdi#@4fwNr5T<1}q zu<wObah{dUF&v(V`q)?EY%H=!lrD;(4!;<kt2hdiH(28=MsZOKCsScB5!q0Wu4xRK z0~w}*hdQiW1FRoe(}38p;#`2K-UFhErWa&lomvBOVf_NxyMS+Ia{_MDe#fa?H7<md z9$c<`#^Nm2j8(3&0m*TcNCt8RPrYY9gmS1LgH6VwBvgvA-^uI4?kG=1R|>!{GTuFq z`49l6v5@0z*d2mCh|sBswCWb;dX#4mLa85QLU#*O0;-#jbuor(=5wn!m$$&)ZJ5er zny%1tWuBm|8OVsj4IH>)1RldwSkRhWkW)ILwU1V1Ll}(@9V*{~b9rY@R$*7Q2f0i` z&FW~s4=iOOO3T$Nv)ArivyC8)ZB)zYOL!mLzBCwmWI}&K98SL?b0JiYLPs#wJ3xkT z`Mc%k6oAuWnS;=;)|rFwB{~Nox1*7S(y_K4Ry6(x{@1OYBvv}g(_;57szm)BDYtHb zpI6S-PLe|bs$JO3L_xiCn#cbespV-D=8D#5_6RNC{cn47*U_COOsrD%5C{jSe}Zgr z>!oELt<9ZFU)$ONt~-G&)-mMn&(<BRTCZQ;-RXV(&F04D>}hpWXM25PGa7|iCKA<A zyQOu1eQvW32O*exbsvZc_r2#7EI`25h;%rtoUS4g!x@0BX$q{;+fblhy{c1D7m_9M zc~y;&b14)&iZ==iYf63q<YSn1GVF5WDmaWOEgAdw97ue-fj{@fs~jFj+OXw^a8|i8 z%!Wc@-P|bZ84gp{jj5rJ5Qb^=)G@!7UEaC@2iw5{wtx(}3$%b1V`<LE3GNpeI~#{d zeIhY;5|dbYDjJD2H1AceA$uLk2A;L2O(GIW!q`>G0>roRl8F+w9h_<rh;R9p(^_i! zwr`t;)bWGHv`0uxVr>~;j>Lbbs(BJ?2xXiND_@Es6!Nr_U0JJlCLI1w?awaeQTad? K>1{B%ME?eJ<fx$l literal 0 HcmV?d00001 diff --git a/packets/__pycache__/poll_packet.cpython-37.pyc b/packets/__pycache__/poll_packet.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d27a70c102bb7ba13460bfd94ceaa2ccba2905d9 GIT binary patch literal 4324 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 literal 0 HcmV?d00001 diff --git a/packets/ipprog_packet b/packets/ipprog_packet new file mode 100644 index 0000000..85dedec --- /dev/null +++ b/packets/ipprog_packet @@ -0,0 +1,3 @@ +from packets import ArtNetPacket +from packets import OPCODES, STANDARD_PORT, STYLE_CODES + diff --git a/packets/ipprog_packet.py b/packets/ipprog_packet.py new file mode 100644 index 0000000..7b27192 --- /dev/null +++ b/packets/ipprog_packet.py @@ -0,0 +1,16 @@ +from packets import ArtNetPacket +from packets import OPCODES, STANDARD_PORT, STYLE_CODES + +@ArtNetPacket.register +class IpProgPacket(ArtNetPacket): + opcode = OPCODES['OpIpProg'] + schema = ( + ('header', 'bytes:8'), + ('opcode', 'int:16'), + ('protocol_version', 'uintbe:16'), + ('filler1', 'bytes:8'), + ('filler2', 'bytes:8'), + ('command', 'bytes:8'), + ('filler4', 'bytes:8'), + + ) \ No newline at end of file diff --git a/packets/output_packet.py b/packets/output_packet.py new file mode 100644 index 0000000..8502ac9 --- /dev/null +++ b/packets/output_packet.py @@ -0,0 +1,44 @@ +from packets import ArtNetPacket +from packets import OPCODES, STANDARD_PORT, STYLE_CODES + +import bitstring + +@ArtNetPacket.register +class DmxPacket(ArtNetPacket): + opcode = OPCODES['OpDmx'] + schema = ( + ('header', 'bytes:8'), + ('opcode', 'int:16'), + ('protocol_version', 'uintbe:16'), + ('sequence', 'int:8'), + ('physical', 'int:8'), + ('universe', 'uintle:16'), + ('length', 'uintbe:16'), + ('framedata', 'bytes') + ) + + def __init__(self, frame=None, **kwargs): + super(DmxPacket, self).__init__(**kwargs) + self.frame = frame + + #@classmethod + #def parse_framedata(cls, b, fmt): + #from artnet import dmx + #return dmx.Frame([ord(x) for x in b.read('bytes:512')]) + + def format_length(self): + return len(self.frame) + + def format_framedata(self): + return ''.join([chr(i or 0) for i in self.frame]) + + def __str__(self): + return '<DMX(%(sequence)s): %(channels)s>' % dict( + sequence = self.sequence, + channels = ', '.join([ + '%s: %s' % ( + address + 1, + self.frame[address] + ) for address in range(len(self.frame)) if self.frame[address] + ]) +) \ No newline at end of file diff --git a/packets/poll_packet.py b/packets/poll_packet.py new file mode 100644 index 0000000..fa4a80a --- /dev/null +++ b/packets/poll_packet.py @@ -0,0 +1,120 @@ +import uuid + +from packets import ArtNetPacket +from packets import OPCODES, STANDARD_PORT, STYLE_CODES + +import bitstring + +@ArtNetPacket.register +class PollPacket(ArtNetPacket): + opcode = OPCODES['OpPoll'] + schema = ( + ('header', 'bytes:8'), + ('opcode', 'int:16'), + ('protocol_version', 'uintbe:16'), + ('talktome', 'int:8'), + ('priority', 'int:8') + ) + + def __init__(self, talktome=0x02, priority=0, **kwargs): + super(PollPacket, self).__init__(**kwargs) + self.talktome = talktome + self.priority = priority + +@ArtNetPacket.register +class PollReplyPacket(ArtNetPacket): + opcode = OPCODES['OpPollReply'] + counter = 0 + + port = STANDARD_PORT + + short_name = 'python-artnet' + long_name = 'https://github.com/philchristensen/python-artnet.git' + style = STYLE_CODES['StController'] + esta_manufacturer = 'PA' + version = 1 + universe = 0 + status1 = 2 + status2 = bitstring.Bits('0b0111').int + + num_ports = 0 + port_types = '\0\0\0\0' + good_input = '\0\0\0\0' + good_output = '\0\0\0\0' + + bind_ip = '\0\0\0\0' + mac_address = uuid.getnode() + + schema = ( + ('header', 'bytes:8'), + ('opcode', 'int:16'), + ('ip_address', 'bytes:4'), + ('port', 'int:16'), + ('version', 'uintbe:16'), + ('net_switch', 'int:8'), + ('sub_switch', 'int:8'), + ('oem', 'uintbe:16'), + ('ubea_version', 'int:8'), + ('status1', 'int:8'), + ('esta_manufacturer', 'bytes:2'), + ('short_name', 'bytes:18'), + ('long_name', 'bytes:64'), + ('node_report', 'bytes:64'), + ('num_ports', 'uintbe:16'), + ('port_types', 'bytes:4'), + ('good_input', 'bytes:4'), + ('good_output', 'bytes:4'), + ('switch_in', 'int:8'), + ('switch_out', 'int:8'), + ('switch_video', 'int:8'), + ('switch_macro', 'int:8'), + ('switch_remote', 'int:8'), + ('spare1', 'int:8'), + ('spare2', 'int:8'), + ('spare3', 'int:8'), + ('style', 'int:8'), + ('mac_address', 'uintle:48'), + ('bind_ip', 'bytes:4'), + ('bind_index', 'int:8'), + ('status2', 'int:8'), + ('filler', 'bytes') + ) + + def __init__(self, **kwargs): + super(PollReplyPacket, self).__init__(**kwargs) + PollReplyPacket.counter += 1 + + def format_ip_address(self): + address = socket.gethostbyname(socket.gethostname()) + return bitstring.pack('uint:8, uint:8, uint:8, uint:8', *[int(x) for x in address.split('.')]).bytes + + @classmethod + def parse_ip_address(cls, b, fmt): + b = bitstring.BitStream(bytes=b.read(fmt)) + address = b.readlist(','.join(['uint:8'] * 4)) + return '.'.join([str(x) for x in address]) + + def format_short_name(self): + return self.short_name[0:18].ljust(18) + + @classmethod + def parse_short_name(cls, b, fmt): + short_name = b.read(fmt) + return short_name.strip() + + def format_long_name(self): + return self.long_name[0:64].ljust(64) + + @classmethod + def parse_long_name(cls, b, fmt): + long_name = b.read(fmt) + return long_name.strip() + + def format_node_report(self): + node_report = "#0001 [%s] Power On Tests successful" % PollReplyPacket.counter + return node_report[0:64].ljust(64) + + @classmethod + def parse_node_report(cls, b, fmt): + node_report = b.read(fmt) + return node_report.strip() \ No newline at end of file -- GitLab