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