http://mbed.org/users/okini3939/notebook/art-net/

Dependents:   ArtNode ArtNode DMXStation ArtNodeLED ... more

Revision:
2:0753f1ed1dec
Parent:
1:c59dc374fc64
Child:
3:89aa639c946a
--- a/DmxArtNet.cpp	Thu Sep 29 16:42:34 2011 +0000
+++ b/DmxArtNet.cpp	Fri Sep 13 06:16:58 2019 +0000
@@ -7,20 +7,26 @@
  *
  * http://members.westnet.com.au/rowanmac/
  *
- * for mbed port by Suga 2011
+ * for mbed ported by Suga 2011, 2017
  */
 
 /** @file
  */
 
 #include "mbed.h"
-#include "EthernetNetIf.h"
-#include "UDPSocket.h"
+#include "EthernetInterface.h"
 #include "DmxArtNet.h"
-#include "dbg.h"
 #include <stdio.h>
 #include <string.h>
 
+//#define DEBUG
+#ifdef DEBUG 
+#define DBG(...) printf("" __VA_ARGS__) 
+#else 
+#define DBG(...) 
+#endif 
+
+/*
 // host to network short
 #define htons( x ) ( (( (x) << 8 ) & 0xFF00) | (( (x) >> 8 ) & 0x00FF) )
 #define ntohs( x ) htons(x)
@@ -30,11 +36,18 @@
                    | (( (x) >>  8 ) & 0x0000FF00)  \
                    | (( (x) >> 24 ) & 0x000000FF)  )
 #define ntohl( x ) htonl(x)
-
+*/
 
 extern "C" void mbed_mac_address(char *s);
 
 
+DmxArtNet::DmxArtNet () {
+    BindIpAddress[0] = 0;
+    BCastAddress[0] = 0;
+    Init_ArtDMX();
+    InitArtPollReplyDefaults();
+}
+
 // function to make a word (16bit) from two bytes (2 x 8bit)
 int DmxArtNet::makeword16 (int lsb, int msb) {
     return (msb << 8) + lsb;
@@ -54,112 +67,244 @@
 
 int DmxArtNet::Init () {
    int i;
-   UDPSocketErr err;
+   int err;
 
+   rxlen = 0;
+   cb_ArtParser = NULL;
    LError = 0; // no error yet :)
-   Init_ArtDMX(); // initialize ArtDmx structure
-//   art = new UDPSocket; // create socket
-   net_loopback = 0; // dont listen to ourself, default
-   rxlen = 0;
+   LastRecievedUniverse = 0;
+
+//   Init_ArtDMX(); // initialize ArtDmx structure
 
-   if (BindIpAddress == IpAddr(0,0,0,0)) {
-       BindIpAddress = IpAddr(2,0,0,1); // default to 2.0.0.1
+   if (BindIpAddress[0] == 0) {
+       char mac[6];
+       mbed_mac_address(mac);
+       sprintf(BindIpAddress, "%d.%d.%d.%d", 2, mac[3], mac[4], mac[5]);
+       strcpy(SubNetMask, "255.0.0.0");
    }
-   err = _art.bind(Host(BindIpAddress, ArtUDPPort, NULL));
+   if (BCastAddress[0] == 0) {
+       bcast(BCastAddress, BindIpAddress, SubNetMask);
+   }
+   RemoteSin.set_address(BCastAddress, ArtUDPPort);
+
+   err = _art.bind(ArtUDPPort);
    if (err) {
        SocketErrorOccured("Bind error");
        return -1;
    }
-   if (BCastAddress == IpAddr(0,0,0,0)) {
-       BCastAddress = IpAddr(2,255,255,255); // default
-   }
-   RemoteSin = Host(BCastAddress, ArtUDPPort, NULL);
+//   _art.set_broadcasting(true);
 
-   for (i = 0; i < ArtMaxUniv; i ++) {
-       DmxIn[i] = new unsigned char[512];
-   }
+    int ip[4];
+    sscanf(BindIpAddress, "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3]);
+    for (i = 0; i < 4; i ++) {
+        localaddr.IP[i] = ip[i];
+    }
+    localaddr.Port = ArtUDPPort;
+    ArtPollReply.Addr = localaddr;
 
-   _art.setOnEvent(this, &DmxArtNet::on_UDPSocketEvent);
+    for (i = 0; i < ArtMaxUniv; i ++) {
+//        DmxIn[i] = new unsigned char[DMX_SIZE];
+        DmxIn[i] = (unsigned char*)malloc(DMX_SIZE);
+        memset(DmxIn[i], 0, DMX_SIZE);
+    }
+
+    thread = new Thread(&DmxArtNet::task_UDPSocket, (void*)this, osPriorityNormal, 1024*4);
 
    return 0;
 }
 
-void DmxArtNet::on_UDPSocketEvent (UDPSocketEvent e) {
-    if (e == UDPSOCKET_READABLE) {
-        rxlen = _art.recvfrom((char*)buf, sizeof(buf), &RemoteSin);
+void DmxArtNet::task_UDPSocket (void const *arg) {
+    DmxArtNet *inst = (DmxArtNet*)arg;
+
+    inst->on_UDPSocketEvent();
+}
+
+void DmxArtNet::on_UDPSocketEvent () {
+    int n;
+
+    for (;;) {
+        Work();
+
+        n = _art.receiveFrom(RemoteSin, (char*)buf, sizeof(buf));
+        if (n < 0) break;
+        if (n == 0) continue;
+        rxlen = n;
     }
 }
 
 int DmxArtNet::Work() {
-//   int rxlen, pos;
-   int pos;
-   int x, y;
-   unsigned char *RecvByte;
+   int i, n;
+   struct ArtPacketHeader *ArtHead = (ArtPacketHeader*)buf;
 
    if (rxlen >= sizeof(ArtPacketHeader)) {
       // retreive the packet header
-//      err = art.Recv(buf, sizeof(ArtPacketHeader), 10);
 
      // if looback disabled
-     if (!net_loopback && RemoteSin.getIp() == BindIpAddress) {
+     if (strcmp(RemoteSin.get_address(), BindIpAddress) == NULL) {
          DBG("ArtNet> loopback detected\r\n");
 
-//         art.purge; // dump the data
          rxlen = 0;
          return 0; // don't read packets
                    // which we sent!
      }
 
-     DBG("ArtNet> RX: %d\r\n", rxlen);
-     memcpy(&ArtHead, buf, sizeof(ArtPacketHeader));
+     // confirm is vaild art-net packet
+     if (strncmp(ArtHead->ID, ArtHeaderID, 8) != 0) {
+         rxlen = 0;
+         return 0;
+     }
+
+     DBG("ArtNet> RX: %d OpCode: %04x\r\n", rxlen, ArtHead->OpCode);
 
-     // confirm is vaild art-net packet
-     if (strncmp(ArtHead.ID, ArtHeaderID, 8) == 0) {
+       switch (ArtHead->OpCode) { // determine packet type
 
-       RecvByte = &buf[10];
-
-       switch (ArtHead.OpCode) { // determine packet type
+        case OP_Output:  // 0x5000  An ArtDMX Packet, containts dmx universe
+            if (rxlen >= sizeof(struct ArtDMX_Packet) - (DMX_SIZE - 2)) {
+                struct ArtDMX_Packet *artdmx = (struct ArtDMX_Packet*)buf;
+            
+                // check data length
+                if (ntohs(artdmx->Length) > DMX_SIZE || (artdmx->Universes & 0x7ff0) != (NetSwitch & 0x7ff0)) {
+                    break;
+                }
 
-       case OP_Output: // An ArtDMX Packet, containts dmx universe
-           ArtDMX.VersionH = RecvByte[0];
-           ArtDMX.Version  = RecvByte[1];
-           ArtDMX.Sequence = RecvByte[2];
-           ArtDMX.Physical = RecvByte[3];
+                for (i = 0; i < ArtMaxUniv; i ++) {
+                  if ( (ArtMaxUniv <= 4 && ArtPollReply.Swout[i] == (artdmx->Universes & 0x0f)) ||
+                   (ArtMaxUniv > 4 && ArtPorts.Swout[i] == (artdmx->Universes & 0x0f)) ) {
+                    LastRecievedUniverse |= (1<<i);
+                    // get the dmx data
+                    memcpy(DmxIn[i], artdmx->Data, ntohs(artdmx->Length));
+                    DBG(" <Art-Dmx> Univ: %d  Length: %d  Ch1: %d\r\n", artdmx->Universes, ntohs(artdmx->Length), DmxIn[i][0]);
+                    DBG(" from %s\r\n", RemoteSin.get_address());
+                    rxlen = 0;
+                    return 1; // something happened!
+                  }
+                }
+            }
+            break; // OP_Output
+
+        case OP_Poll:  // 0x2000
+            if (rxlen == sizeof(struct ArtPoll_Packet)) {
+                struct ArtPoll_Packet *artpoll = (struct ArtPoll_Packet*)buf;
 
-           x = RecvByte[4];
-           y = RecvByte[5];
-           ArtDMX.Universes = makeword16(x,y);
-           LastRecievedUniverse = ArtDMX.Universes;
+                if (!(artpoll->TalkToMe & 1)) {
+                    RemoteSin.set_address(BCastAddress, ArtUDPPort);
+                }
+                DBG(" <Art-Poll> Ver: %d TalkToMe: %d\r\n", ArtPoll.Version, ArtPoll.TalkToMe);
+                SendArtPollReply(); // send a reply to the ArtPoll
+            }
+            break; // OP_Poll
 
-           x = RecvByte[6];
-           y = RecvByte[7];
-           ArtDMX.Length = makeword16(y,x);
+        case OP_PollReply:  // 0x2100
+            {
+                struct ArtPollReply_Packet *pollreply = (struct ArtPollReply_Packet*)buf;
+
+                DBG(" <Art-PollReply> Startus: %02x Name: %s\r\n", pollreply->Status, pollreply->ShortName);
+            }
+            break; // OP_PollReply
+
+        case OP_Address:  // 0x6000
+            if (rxlen == sizeof(struct ArtAddress_Packet)) {
+                struct ArtAddress_Packet *address = (struct ArtAddress_Packet *)buf;
 
-           // check data length
-           if (ArtDMX.Length <= 512) {
-           // get the dmx data
-               for (pos = 0; pos < ArtDMX.Length; pos ++) {
-                   DmxIn[ArtDMX.Universes][pos] = RecvByte[8 + pos];
-               }
-               DBG(" <Art-Dmx> Size: %d  Univ: %d  Ch1: %d\r\n", ArtDMX.Length, ArtDMX.Universes, DmxIn[ArtDMX.Universes][0]);
-               rxlen = 0;
-               return 1; // something happened!
-           }
-           break; // op_output
+                if ((address->NetSwitch & 0x80) && (address->SubSwitch & 0x80)) {
+                    NetSwitch = ((address->NetSwitch & 0x7f) << 8) | ((address->SubSwitch & 0x0f) << 4);
+                    ArtPollReply.NetSwitch = (NetSwitch >> 8) & 0x7f;
+                    ArtPollReply.SubSwitch  = (NetSwitch >> 4) & 0x0f;
+                }
+                if (address->ShortName[0]) {
+                    strncpy(ArtPollReply.ShortName, address->ShortName, 18);
+                }
+                if (address->LongName[0]) {
+                    strncpy(ArtPollReply.LongName, address->LongName, 64);
+                }
+                if (address->BindIndex) {
+                    n = (address->BindIndex - 1) * 4;
+                } else {
+                    n = 0;
+                }
+                for (i = 0; i < 4; i ++) {
+                    if (address->Swin[i] != 0x7f) {
+                        ArtPollReply.Swin[i] = address->Swin[i] & 0x0f;
+                        ArtPorts.Swin[n + i] = address->Swin[i] & 0x0f;
+                        if (address->Swin[i] & 0x80) {
+                            ArtPollReply.PortType[i] |= 0x40;
+                            ArtPorts.PortType[n + i] |= 0x40;
+                        } else {
+                            ArtPollReply.PortType[i] &= ~0x40;
+                            ArtPorts.PortType[n + i] &= ~0x40;
+                        }
+                    }
+                    if (address->Swout[i] != 0x7f) {
+                        ArtPollReply.Swout[i] = address->Swout[i] & 0x0f;
+                        ArtPorts.Swout[n + i] = address->Swout[i] & 0x0f;
+                        if (address->Swout[i] & 0x80) {
+                            ArtPollReply.PortType[i] |= 0x80;
+                            ArtPorts.PortType[n + i] |= 0x80;
+                        } else {
+                            ArtPollReply.PortType[i] &= ~0x80;
+                            ArtPorts.PortType[n + i] &= ~0x80;
+                        }
+                    }
+                }
+
+                if (cb_ArtParser) cb_ArtParser(ArtHead, rxlen);
+                SendArtPollReply(); // send a reply to the ArtPoll
+                DBG(" <Art-Address> NetSwitch: %02x Command: %02x\r\n", NetSwitch, address->Command);
+            }
+            break;
 
-       case OP_Poll:
-           ArtPoll.VersionH = RecvByte[0];
-           ArtPoll.Version  = RecvByte[1];
-           ArtPoll.TalkToMe = RecvByte[2];
-           DBG(" <Art-Poll> Ver: %d TalkToMe: %d\r\n", ArtPoll.Version, ArtPoll.TalkToMe);
-           SendArtPollReply(); // send a reply to the ArtPoll
-           break;
+        case OP_IpProg:  // 0xf800
+            if (rxlen == sizeof(struct ArtIpProg_Packet)) {
+                struct ArtIpProg_Packet *ipprog = (struct ArtIpProg_Packet *)buf;
+
+                if (ipprog->Command & (1<<7)) {
+                  if (ipprog->Command & (1<<6)) { // dhcp
+                    BindIpAddress[0] = 0;
+                  }
+                  if (ipprog->Command & (1<<2)) { // ip addrress
+                    sprintf(BindIpAddress, "%d.%d.%d.%d", ipprog->ProgIp[0], ipprog->ProgIp[1], ipprog->ProgIp[2], ipprog->ProgIp[3]);
+//                    sprintf(DGateWay, "%d.%d.%d.%d", ipprog->Padding[0], ipprog->Padding[1], ipprog->Padding[2], ipprog->Padding[3]);
+                  }
+                  if (ipprog->Command & (1<<1)) { // subnet mask
+                    sprintf(SubNetMask, "%d.%d.%d.%d", ipprog->ProgSm[0], ipprog->ProgSm[1], ipprog->ProgSm[2], ipprog->ProgSm[3]);
+                  }
+                  bcast(BCastAddress, BindIpAddress, SubNetMask);
+                }
+
+//                if (cb_ArtParser) cb_ArtParser(ArtHead, rxlen);
+                SendArtIpProgReply();
+                DBG(" <Art-IpProg> NetSwitch: %02x Command: %02x\r\n", NetSwitch, ipprog->Command);
+            }
+                if (cb_ArtParser) cb_ArtParser(ArtHead, rxlen);
+            break;
 
-       default:
-           // NYI Packet Type
-           break;
+        case OP_OpTodRequest:  // 0x8000
+            if (rxlen == sizeof(struct ArtTodRequest_Packet)) {
+                struct ArtTodRequest_Packet *todreq = (struct ArtTodRequest_Packet *)buf;
+                if (cb_ArtParser) cb_ArtParser(ArtHead, rxlen);
+                for (i = 0; i < todreq->AddCount; i ++) {
+                    SendArtTodData(todreq->Address[i]);
+                }
+            }
+            break;
+
+        case OP_OpTodControl:  // 0x8200
+            if (rxlen == sizeof(struct ArtTodControl_Packet)) {
+                struct ArtTodControl_Packet *tod = (struct ArtTodControl_Packet *)buf;
+                if (cb_ArtParser) cb_ArtParser(ArtHead, rxlen);
+                if (tod->Command == 0x01) {
+                    SendArtTodData(tod->Address);
+                }
+            }
+            break;
+
+        case OP_OpRdm:  // 0x8300
+        case OP_OpRdmSub:  // 0x8400
+        default:
+            if (cb_ArtParser) cb_ArtParser(ArtHead, rxlen);
+            DBG(" <Unknown> OpCode: %04x\r\n", ArtHead->OpCode);
+            break;
        } // case packet type
-     } // waiting data
    }
 
    rxlen = 0;
@@ -168,50 +313,56 @@
 
 // socket shutdown
 void DmxArtNet::Done () {
-    _art.resetOnEvent();
     _art.close();
 }
 
 // init ArtDMX packet
 void DmxArtNet::Init_ArtDMX () {
    // header stuff
+   memset(&ArtDMX, 0, sizeof(ArtDMX));
    memcpy(ArtDMX.ID, ArtHeaderID, 8);
    ArtDMX.OpCode = OP_Output;
    ArtDMX.Version = ArtVersion;
    // dmx stuff
    ArtDMX.Universes = 0; // this is the destination for the dmx
    ArtDMX.Sequence = 0;
-   ArtDMX.Length = htons(512);
+   ArtDMX.Length = htons(DMX_SIZE);
 }
 
 // send ArtDmx Packet
 int DmxArtNet::Send_ArtDmx(int univ, int physical, char *data, int length) {
-   Host Remote;
+   Endpoint Remote;
 
    // build a artdmx packet
    memcpy(ArtDMX.Data, data, length);
-   ArtDMX.Universes = univ;
+   ArtDMX.Universes = (NetSwitch & 0x7ff0) | univ;
    ArtDMX.Length = htons(length);
    ArtDMX.Physical = physical;
    ArtDMX.Sequence ++;
 
    // set place to send
-   Remote = Host(BCastAddress, ArtUDPPort, NULL);
+   Remote.set_address(BCastAddress, ArtUDPPort);
 
    // send it off
-   _art.sendto((char*)&ArtDMX, sizeof(ArtDMX), &Remote);
+   _art.sendTo(Remote, (char*)&ArtDMX, sizeof(ArtDMX));
 
    return 0;
 }
 
 
-
 void DmxArtNet::InitArtPollReplyDefaults () {
+   memset(&ArtPollReply, 0, sizeof(ArtPollReply));
+   memset(&ArtPorts, 0, sizeof(ArtPorts));
    memcpy(ArtPollReply.ID, ArtHeaderID, 8);
    ArtPollReply.OpCode = OP_PollReply; // reply packet
    ArtPollReply.Version = ArtVersion;
 
-   memcpy(&ArtPollReply.Addr, &localaddr, sizeof(localaddr));
+   ArtPollReply.NetSwitch = (NetSwitch >> 8) & 0x7f;
+   ArtPollReply.SubSwitch = (NetSwitch >> 4) & 0x0f;
+   ArtPollReply.OEM = htons(OemId);
+   ArtPollReply.Status = (3<<6)|(2<<4); // Normal mode, Universe programmed by network
+
+   ArtPollReply.Addr = localaddr;
    strncpy(ArtPollReply.ShortName, STR_ShortName, 18);
    strncpy(ArtPollReply.LongName, STR_LongName, 64);
    strncpy(ArtPollReply.NodeReport, "OK", 64);
@@ -219,32 +370,114 @@
 
    mbed_mac_address((char*)&ArtPollReply.Mac);
 
-   ArtPollReply.NumPortsH = 0;
-   ArtPollReply.NumPorts = 0;
-   ArtPollReply.Swout[0] = 0;
-   ArtPollReply.Swout[1] = 0;
-   ArtPollReply.Swout[2] = 0;
-   ArtPollReply.Swout[3] = 0;
-   ArtPollReply.Swin[0] = 0;
-   ArtPollReply.Swin[1] = 0;
-   ArtPollReply.Swin[2] = 0;
-   ArtPollReply.Swin[3] = 0;
-
-   ArtPollReply.GoodOutput[0] = 0;
-   ArtPollReply.GoodOutput[1] = 0;
-   ArtPollReply.GoodOutput[2] = 0;
-   ArtPollReply.GoodOutput[3] = 0;
-   ArtPollReply.PortType[0] = 255;
-   ArtPollReply.PortType[1] = 255;
-   ArtPollReply.PortType[2] = 255;
-   ArtPollReply.PortType[3] = 255;
+   ArtPollReply.PortType[0] = 0;
+   ArtPollReply.PortType[1] = 0;
+   ArtPollReply.PortType[2] = 0;
+   ArtPollReply.PortType[3] = 0;
 }
 
 // send ArtPoll Reply
 int DmxArtNet::SendArtPollReply () {
+    int i, j, n;
 
    // send the packet
-   _art.sendto((char*)&ArtPollReply, sizeof(ArtPollReply), &RemoteSin);
+//    DBG("SendArtPollReply> %s:%d\r\n", RemoteSin.get_address(), RemoteSin.get_port());
+    if (ArtMaxUniv <= 4) {
+        _art.sendTo(RemoteSin, (char*)&ArtPollReply, sizeof(ArtPollReply));
+    } else {
+        for (i = 0; i < (ArtMaxUniv + 3) / 4; i ++) {
+            n = i * 4;
+            ArtPollReply.BindIndex = 1 + i;
+            ArtPollReply.NumPorts  = ArtMaxUniv - n < 4 ? ArtMaxUniv - n : 4;
+            for (j = 0; j < ArtPollReply.NumPorts; j ++) {
+                ArtPollReply.PortType[j]   = ArtPorts.PortType[n + j];
+                ArtPollReply.GoodInput[j]  = ArtPorts.GoodInput[n + j];
+                ArtPollReply.GoodOutput[j] = ArtPorts.GoodOutput[n + j];
+                ArtPollReply.Swin[j]       = ArtPorts.Swin[n + j];
+                ArtPollReply.Swout[j]      = ArtPorts.Swout[n + j];
+            }
+            for (; j < 4; j ++) {
+                ArtPollReply.PortType[j]   = 0;
+                ArtPollReply.GoodInput[j]  = 0;
+                ArtPollReply.GoodOutput[j] = 0;
+                ArtPollReply.Swin[j]       = 0;
+                ArtPollReply.Swout[j]      = 0;
+            }
+            _art.sendTo(RemoteSin, (char*)&ArtPollReply, sizeof(ArtPollReply));
+        }
+    }
+   return 0;
+}
+
+// send ArtIpProg Reply
+int DmxArtNet::SendArtIpProgReply () {
+    ArtIpProgReply_Packet ArtIpProgReply;
+    int ip[4];
+
+    memset(&ArtIpProgReply, 0, sizeof(ArtIpProgReply));
+    memcpy(ArtIpProgReply.ID, ArtHeaderID, 8);
+    ArtIpProgReply.OpCode = OP_IpProgReply;
+    ArtIpProgReply.Version = ArtVersion;
+
+    sscanf(BindIpAddress, "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3]);
+    ArtIpProgReply.ProgIp[0] = ip[0];
+    ArtIpProgReply.ProgIp[1] = ip[1];
+    ArtIpProgReply.ProgIp[2] = ip[2];
+    ArtIpProgReply.ProgIp[3] = ip[3];
+    sscanf(SubNetMask, "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3]);
+    ArtIpProgReply.ProgSm[0] = ip[0];
+    ArtIpProgReply.ProgSm[1] = ip[1];
+    ArtIpProgReply.ProgSm[2] = ip[2];
+    ArtIpProgReply.ProgSm[3] = ip[3];
+    ArtIpProgReply.ProgPortH = ArtUDPPort >> 8;
+    ArtIpProgReply.ProgPort = ArtUDPPort & 0xff;
+
+    if (BindIpAddress[0] == '0') {
+        ArtIpProgReply.Status = 0x40; // dhcp
+    }
+
+   _art.sendTo(RemoteSin, (char*)&ArtIpProgReply, sizeof(ArtIpProgReply));
 
    return 0;
 }
+
+// send SendArtTod
+int DmxArtNet::SendArtTodData (int n) {
+    ArtTodData_Packet ArtTodData;
+
+    memset(&ArtTodData, 0, sizeof(ArtTodData));
+    memcpy(ArtTodData.ID, ArtHeaderID, 8);
+    ArtTodData.OpCode = OP_OpTodData;
+    ArtTodData.Version = ArtVersion;
+
+    ArtTodData.RdmVersion = 1;
+    ArtTodData.Port = n + 1;
+
+    ArtTodData.UidTotalH = 0;
+    ArtTodData.UidTotalL = 1;
+    ArtTodData.BlockCount = 0;
+    ArtTodData.UidCount = 2;
+    ArtTodData.ToD[0] = 0x11;
+    ArtTodData.ToD[1] = 0x22;
+
+   _art.sendTo(RemoteSin, (char*)&ArtTodData, sizeof(ArtTodData));
+
+   return 0;
+}
+
+void DmxArtNet::bcast(char *dest, char *ipaddr, char *netmask) {
+    unsigned char ip[4];
+    int a[4], n[4];
+
+    sscanf(ipaddr, "%d.%d.%d.%d", &a[0], &a[1], &a[2], &a[3]);
+    sscanf(netmask, "%d.%d.%d.%d", &n[0], &n[1], &n[2], &n[3]);
+    ip[0] = (a[0] & n[0]) | ~n[0];
+    ip[1] = (a[1] & n[1]) | ~n[1];
+    ip[2] = (a[2] & n[2]) | ~n[2];
+    ip[3] = (a[3] & n[3]) | ~n[3];
+    sprintf(dest, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
+}
+
+void DmxArtNet::attach (void (*handler)(struct ArtPacketHeader *, int)) {
+    cb_ArtParser = handler;
+}