def __init__(self, **kwargs):
        super(ArtnetManager, self).__init__(**kwargs)
        self.register_signal('new_node', 'del_node')
        self.comm = kwargs.get('comm')
        self.hostaddr = BaseIO.detect_usable_address()
        self.hostname = socket.gethostname()
        self.hostport = UDP_PORT
        self.mac_addr = BaseIO.get_mac_address()
        self.subnet = kwargs.get('subnet', 0)
        self.artpoll = messages.ArtPoll(TalkToMe=2)
        self.artpoll_reply = messages.ArtPollReply(IPAddress=self.hostaddr, 
                                                   Port=UDP_PORT, 
                                                   Oem=0xFF, 
                                                   ShortName=self.hostname, 
                                                   LongName=self.hostname, 
                                                   MAC=self.mac_addr, 
                                                   Style=1, 
                                                   NumPorts=4, 
                                                   PortTypes=[0xC0]*4, 
                                                   Swin=[1, 2, 3, 4])
                                                   #Swout=[1, 2, 3, 4])
        
        self.Nodes = {}
        self.NodesByUniverse = {}
        self.NodePortInfo = {'InputPorts':{}, 'OutputPorts':{}}
        for i, style in self.artpoll_reply.Fields['Style'].codes.iteritems():
            self.Nodes[style] = {}
        
        self.Universes = {}
        self.ds_universes = {}

        self.artnet_io = ArtnetIO(hostaddr=PRIMARY_ADDR, hostport=UDP_PORT, manager=self)
        self.poller = None
        
        if getattr(self.comm, 'MainController', None):
            self.on_comm_MainController_set()
        else:
            self.comm.bind(MainController_set=self.on_comm_MainController_set)
class ArtnetManager(BaseIO.BaseIO):
    ui_name = 'Artnet'
    def __init__(self, **kwargs):
        super(ArtnetManager, self).__init__(**kwargs)
        self.register_signal('new_node', 'del_node')
        self.comm = kwargs.get('comm')
        self.hostaddr = BaseIO.detect_usable_address()
        self.hostname = socket.gethostname()
        self.hostport = UDP_PORT
        self.mac_addr = BaseIO.get_mac_address()
        self.subnet = kwargs.get('subnet', 0)
        self.artpoll = messages.ArtPoll(TalkToMe=2)
        self.artpoll_reply = messages.ArtPollReply(IPAddress=self.hostaddr, 
                                                   Port=UDP_PORT, 
                                                   Oem=0xFF, 
                                                   ShortName=self.hostname, 
                                                   LongName=self.hostname, 
                                                   MAC=self.mac_addr, 
                                                   Style=1, 
                                                   NumPorts=4, 
                                                   PortTypes=[0xC0]*4, 
                                                   Swin=[1, 2, 3, 4])
                                                   #Swout=[1, 2, 3, 4])
        
        self.Nodes = {}
        self.NodesByUniverse = {}
        self.NodePortInfo = {'InputPorts':{}, 'OutputPorts':{}}
        for i, style in self.artpoll_reply.Fields['Style'].codes.iteritems():
            self.Nodes[style] = {}
        
        self.Universes = {}
        self.ds_universes = {}

        self.artnet_io = ArtnetIO(hostaddr=PRIMARY_ADDR, hostport=UDP_PORT, manager=self)
        self.poller = None
        
        if getattr(self.comm, 'MainController', None):
            self.on_comm_MainController_set()
        else:
            self.comm.bind(MainController_set=self.on_comm_MainController_set)
        
    def unlink(self):
        self.comm.unbind(self)
        if type(self.ds_universes) != dict:
            self.ds_universes.unbind(self)
        super(ArtnetManager, self).unlink()
        
    def on_comm_MainController_set(self, **kwargs):
        self.ds_universes = self.comm.MainController.DeviceSystem.universes
        self.update_ds_universes()
        self.ds_universes.bind(child_added=self.on_ds_universes_child_added, 
                               child_removed=self.on_ds_universes_child_removed)
        
    def on_ds_universes_child_added(self, **kwargs):
        obj = kwargs.get('obj')
        self.update_ds_universes(univ_obj=obj)
        
    def on_ds_universes_child_removed(self, **kwargs):
        obj = kwargs.get('obj')
        self.detach_universe(univ_obj=obj)
        
    def update_ds_universes(self, **kwargs):
        univ_obj = kwargs.get('univ_obj')
        if univ_obj is not None:
            universes = [univ_obj]
        else:
            universes = self.ds_universes.values()
        for univ_obj in universes:
            sub = univ_obj.Artnet_Subnet
            univ = univ_obj.Artnet_Universe
            self.detach_universe(univ_obj=univ_obj)
            if sub is not None and univ is not None:
                self.attach_universe(universe_obj=univ_obj, 
                                     universe_index=univ, 
                                     subnet=sub)
            univ_obj.bind(property_changed=self.on_ds_universe_obj_property_changed)
            
    def on_ds_universe_obj_property_changed(self, **kwargs):
        prop = kwargs.get('Property')
        obj = kwargs.get('obj')
        if prop.name in ['Artnet_Subnet', 'Artnet_Universe']:
            self.update_ds_universes(univ_obj=obj)
        
    def do_connect(self):
        if self.connected:
            self.do_disconnect(blocking=True)
        self.artnet_io.do_connect()
#        clslist = [communication.MulticastSender, communication.MulticastReceiver]
#        for key, cls in zip(['sender', 'receiver'], clslist):
#            obj = cls(hostaddr=self.hostaddr, hostport=self.hostport, 
#                      mcastaddr=PRIMARY_ADDR, mcastport=UDP_PORT, 
#                      manager=self)
#            self.multicast_io[key] = obj
#            obj.do_connect()
        self.poller = Poller(manager=self)
        self.poller.start()
        #self.poller.stop()
        self.connected = True
        self.update_ds_universes()
        
    def do_disconnect(self, blocking=False):
        if self.poller is not None:
            self.poller.stop(blocking=blocking)
            self.poller = None
        #for univ in self.Universes.itervalues():
        #    univ.stop()
        for univ_obj in self.ds_universes.itervalues():
            self.detach_universe(univ_obj=univ_obj, blocking=blocking)
        self.artnet_io.do_disconnect(blocking=blocking)
#        for key in ['sender', 'reciever']:
#            obj = self.multicast_io.get(key)
#            if obj:
#                obj.do_disconnect()
#                del self.multicast_io[key]
        self.connected = False
        
    def attach_universe(self, **kwargs):
        '''
        :Parameters:
            universe_obj :
            universe_index :
            subnet :
        '''
        kwargs.setdefault('subnet', self.subnet)
        kwargs.setdefault('manager', self)
        univ = UniverseThread(**kwargs)
        self.Universes[(univ.subnet, univ.universe_index)] = univ
        if self.connected:
            univ.start()
        keys = ['subnet', 'universe_index', '_thread_id']
        d = dict(zip(keys, [getattr(univ, key) for key in keys]))
        nodes = self.NodePortInfo['OutputPorts'].get((univ.subnet, univ.universe_index), [])
        d['nodes'] = [str(n) for n in nodes]
        self.LOG.info('Artnet attached universe: ', d)
            
    def detach_universe(self, **kwargs):
        univ_obj = kwargs.get('univ_obj')
        blocking = kwargs.get('blocking', True)
        univ_obj.unbind(self)
        univ_obj.unbind(self)
        key = (univ_obj.Artnet_Subnet, univ_obj.Artnet_Universe)
        for ukey, uval in self.Universes.iteritems():
            if uval.universe_obj == univ_obj:
                key = ukey
        univ_thread = self.Universes.get(key)
        if univ_thread:
            self.LOG.info('Artnet detach universe: ', key, univ_thread._thread_id)
            univ_thread.stop(blocking=blocking)
            del self.Universes[key]
        
    def add_node(self, **kwargs):
        msg = kwargs.get('artpoll_reply')
        if msg is None:
            return
        node = Node(artpoll_reply=msg, manager=self)
        nid = node.id
        style = node.style
        if nid not in self.Nodes[style]:
            self.Nodes[style][nid] = node
            #print 'new node: id=%s, style=%s, data=%s' % (node.id, node.style, node.data)
            #keys = node.Properties.keys()
            #self.LOG.info('Artnet new node: ', dict(zip(keys, [getattr(node, key) for key in keys])))
            self.LOG.info('Artnet new node: ', str(node))
            self.update_node_port_info(node=node)
            node.bind(**dict(zip(['InputPorts', 'OutputPorts'], [self.on_node_io_port_update]*2)))
            self.emit('new_node', style=style, id=nid, node=node)
        else:
            self.Nodes[style][nid].set_data(msg)
            
    def update_node_port_info(self, **kwargs):
        node = kwargs.get('node')
        if node:
            if node.style != 'StNode':
                return
            nodes = [node]
        else:
            nodes = self.Nodes['StNode'].values()
        for node in nodes:
            for iokey, iodict in self.NodePortInfo.iteritems():
                for i, sub_univ in enumerate(getattr(node, iokey)):
                    if sub_univ is not None:
                        if sub_univ not in iodict:
                            iodict[sub_univ] = set()
                        iodict[sub_univ].add(node)
                        if sub_univ in self.Universes:
                            self.Universes[sub_univ].add_node(node=node, port=i)
        #print self.NodePortInfo
        
    def on_node_io_port_update(self, **kwargs):
        node = kwargs.get('obj')
        key = kwargs.get('name')
        old = kwargs.get('old')
        value = kwargs.get('value')
        if node.style != 'StNode':
            return
        if old is not None:
            for i, subuniv in enumerate(old):
                if value[i] != subuniv:
                    nodeset = self.NodePortInfo[key].get(subuniv, set())
                    nodeset.discard(node)
        self.update_node_port_info(node=node)
    
    def del_node(self, **kwargs):
        nstyle = kwargs.get('style')
        nid = kwargs.get('id')
        node = kwargs.get('node')
        if node is not None:
            nstyle = node.style
            nid = node.id
        elif nid is not None:
            node = self.Nodes.get(nstyle, {}).get(nid)
        if node is None:
            return
        node.unbind(self)
        node.unlink()
        del self.Nodes[nstyle][nid]
        self.emit('del_node', style=nstyle, id=nid, node=node)
        if nstyle != 'StNode':
            return
        for iokey, iodict in self.NodePortInfo.iteritems():
            for sub_univ in getattr(node, iokey):
                if sub_univ is not None:
                    nodeset = iodict.get(sub_univ, set())
                    nodeset.discard(node)
                    if sub_univ in iodict and not len(nodeset):
                        del iodict[sub_univ]
                    if sub_univ in self.Universes:
                        self.Universes[sub_univ].del_node(node=node)
        #print self.NodePortInfo
            
    def send_dmx(self, **kwargs):
        if not self.comm.osc_io.isMaster:
            return
        seq = kwargs.get('sequence', 0)
        sub = kwargs.get('subnet', self.subnet)
        univ = kwargs.get('universe', 1)
        data = kwargs.get('data')
        nodes = self.NodePortInfo['OutputPorts'].get((sub, univ))
        if not nodes:
            return
        clients = [(n.data['IPAddress'], n.data['Port']) for n in nodes]
        #clients = [None]
        msg = messages.ArtDmx(Sequence=seq, Universe=(sub * 0x10) + univ, Data=data, Physical=univ)
        #if len(data) < 512:
        #    print 'dmxlen: msg=%s, data=%s, msglen=%s' % (msg.Fields['Length'].value, len(data), len(msg.get_data()[0]))
        #print 'sending dmx to univ ', univ
        self.send_msg(msg=msg, clients=clients)
        
    def send_msg(self, **kwargs):
        msg = kwargs.get('msg')
        clients = kwargs.get('clients', [None])
        s = msg.build_string()
        for client in clients:
            self.artnet_io.send(s, client)
        
    def parse_message(self, **kwargs):
        data = kwargs.get('data')
        client = kwargs.get('client')
        msg = messages.parse_message(data)
        if not msg:
            return
        #print msg.__class__.__name__, [[f.id, f.value] for f in msg.Fields.indexed_items.values()]
        if msg.msg_type == 'ArtPoll':
            self.artnet_io.send(self.artpoll_reply.build_string())
        elif msg.msg_type == 'ArtPollReply':# and msg.Fields['MAC'].value != self.mac_addr:
            self.add_node(artpoll_reply=msg)
        elif msg.msg_type == 'ArtDmx':
            #print 'ArtDmx from :', client
            u = msg.Fields['Universe'].value
            subuniv = (u / 0x10, u % 0x10)
            u = self.Universes.get(subuniv)
            if u is None:
                return
            u.merge_dmx_msg(msg=msg, client=client)
            
        #print 'artnet gc: ', gc.collect(0)
        self.collect_garbage()