diff --git a/art_net_test.py b/art_net_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..611a72a79bdcdf6d83bb53057844e17389d8c249
--- /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 0000000000000000000000000000000000000000..c376ba4232a6c392a132dfe02ab3609c918a930c
--- /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
Binary files /dev/null and b/packets/__pycache__/__init__.cpython-37.pyc differ
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
Binary files /dev/null and b/packets/__pycache__/output_packet.cpython-37.pyc differ
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
Binary files /dev/null and b/packets/__pycache__/poll_packet.cpython-37.pyc differ
diff --git a/packets/ipprog_packet b/packets/ipprog_packet
new file mode 100644
index 0000000000000000000000000000000000000000..85dedec749c350d8ffcaefe82eaa1f7759298e5e
--- /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 0000000000000000000000000000000000000000..7b27192939835d02b1efc1b9011d119685abc049
--- /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 0000000000000000000000000000000000000000..8502ac92cac412bae240e659c98f276ac5fd1c4c
--- /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 0000000000000000000000000000000000000000..fa4a80ad3d9ac1102dd3383f6a2263cc5ad407d3
--- /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