diff --git a/discovery.go b/discovery.go index 72d0bce..049caf4 100644 --- a/discovery.go +++ b/discovery.go @@ -14,6 +14,7 @@ type Node struct { LongName string Inputs []Universe Outputs []Universe + RDMUIDs map[Universe][]RDMUID LastSeen time.Time } @@ -128,8 +129,9 @@ func (d *Discovery) HandlePollReply(src *net.UDPAddr, pkt *PollReplyPacket) { node, exists := d.nodes[ip] if !exists { node = &Node{ - IP: src.IP, - Port: pkt.Port, + IP: src.IP, + Port: pkt.Port, + RDMUIDs: map[Universe][]RDMUID{}, } d.nodes[ip] = node } @@ -139,6 +141,7 @@ func (d *Discovery) HandlePollReply(src *net.UDPAddr, pkt *PollReplyPacket) { node.MAC = pkt.MACAddr() node.LastSeen = time.Now() + var newOutputs []Universe for _, u := range pkt.InputUniverses() { if !containsUniverse(node.Inputs, u) { node.Inputs = append(node.Inputs, u) @@ -147,12 +150,45 @@ func (d *Discovery) HandlePollReply(src *net.UDPAddr, pkt *PollReplyPacket) { for _, u := range pkt.OutputUniverses() { if !containsUniverse(node.Outputs, u) { node.Outputs = append(node.Outputs, u) + newOutputs = append(newOutputs, u) } } if d.onChange != nil { d.onChange(node) } + + if len(newOutputs) > 0 { + go d.requestTod(src, newOutputs) + } +} + +func (d *Discovery) requestTod(addr *net.UDPAddr, universes []Universe) { + for _, u := range universes { + d.sender.SendTodRequest(addr, u) + time.Sleep(50 * time.Millisecond) + } +} + +func (d *Discovery) HandleTodData(src *net.UDPAddr, pkt *TodDataPacket) { + d.nodesMu.Lock() + defer d.nodesMu.Unlock() + + ip := src.IP.String() + node, exists := d.nodes[ip] + if !exists { + return + } + + if node.RDMUIDs == nil { + node.RDMUIDs = map[Universe][]RDMUID{} + } + + node.RDMUIDs[pkt.Universe] = pkt.UIDs + + if d.onChange != nil { + d.onChange(node) + } } func (d *Discovery) HandlePoll(src *net.UDPAddr) { diff --git a/protocol.go b/protocol.go index 78bc54f..502c306 100644 --- a/protocol.go +++ b/protocol.go @@ -13,15 +13,16 @@ const ( Port = 6454 ProtocolVersion = 14 - OpPoll uint16 = 0x2000 - OpPollReply uint16 = 0x2100 - OpDmx uint16 = 0x5000 - OpSync uint16 = 0x5200 - OpAddress uint16 = 0x6000 - OpInput uint16 = 0x7000 - OpTodData uint16 = 0x8100 + OpPoll uint16 = 0x2000 + OpPollReply uint16 = 0x2100 + OpDmx uint16 = 0x5000 + OpSync uint16 = 0x5200 + OpAddress uint16 = 0x6000 + OpInput uint16 = 0x7000 + OpTodRequest uint16 = 0x8000 + OpTodData uint16 = 0x8100 OpTodControl uint16 = 0x8200 - OpRdm uint16 = 0x8300 + OpRdm uint16 = 0x8300 PortTypeOutput uint8 = 0x80 PortTypeInput uint8 = 0x40 @@ -29,6 +30,8 @@ const ( GoodOutputDataTransmitted uint8 = 0x80 GoodInputDataReceived uint8 = 0x80 + TodCommandFull uint8 = 0x00 + StyleNode uint8 = 0x00 StyleController uint8 = 0x01 StyleMedia uint8 = 0x02 @@ -148,6 +151,33 @@ func (p *PollReplyPacket) OutputUniverses() []Universe { return result } +type RDMUID [6]byte + +func (u RDMUID) Manufacturer() uint16 { + return uint16(u[0])<<8 | uint16(u[1]) +} + +func (u RDMUID) Device() uint32 { + return uint32(u[2])<<24 | uint32(u[3])<<16 | uint32(u[4])<<8 | uint32(u[5]) +} + +func (u RDMUID) String() string { + return fmt.Sprintf("%04x:%08x", u.Manufacturer(), u.Device()) +} + +type TodDataPacket struct { + RdmVer uint8 + Port uint8 + BindIndex uint8 + Net uint8 + Command uint8 + Universe Universe + UidTotal uint16 + BlockCount uint8 + UidCount uint8 + UIDs []RDMUID +} + func ParsePacket(data []byte) (uint16, interface{}, error) { if len(data) < 10 { return 0, nil, ErrPacketTooShort @@ -169,6 +199,9 @@ func ParsePacket(data []byte) (uint16, interface{}, error) { case OpPollReply: pkt, err := parsePollReplyPacket(data) return opCode, pkt, err + case OpTodData: + pkt, err := parseTodDataPacket(data) + return opCode, pkt, err default: return opCode, nil, nil } @@ -279,6 +312,53 @@ func BuildPollPacket() []byte { return buf } +func parseTodDataPacket(data []byte) (*TodDataPacket, error) { + if len(data) < 28 { + return nil, ErrPacketTooShort + } + + pkt := &TodDataPacket{ + RdmVer: data[10], + Port: data[11], + BindIndex: data[20], + Net: data[21], + Command: data[22], + Universe: NewUniverse(data[21], data[23]>>4, data[23]&0x0F), + UidTotal: binary.BigEndian.Uint16(data[24:26]), + BlockCount: data[26], + UidCount: data[27], + } + + uidCount := int(pkt.UidCount) + if uidCount > 200 { + uidCount = 200 + } + + expectedLen := 28 + uidCount*6 + if len(data) < expectedLen { + uidCount = (len(data) - 28) / 6 + } + + pkt.UIDs = make([]RDMUID, uidCount) + for i := 0; i < uidCount; i++ { + copy(pkt.UIDs[i][:], data[28+i*6:28+i*6+6]) + } + + return pkt, nil +} + +func BuildTodRequestPacket(net, subnet, universe uint8) []byte { + buf := make([]byte, 25) + copy(buf[0:8], ID[:]) + binary.LittleEndian.PutUint16(buf[8:10], OpTodRequest) + binary.BigEndian.PutUint16(buf[10:12], ProtocolVersion) + buf[20] = net + buf[21] = TodCommandFull + buf[22] = 1 + buf[23] = subnet<<4 | (universe & 0x0F) + return buf +} + func BuildPollReplyPacket(ip [4]byte, mac [6]byte, shortName, longName string, universes []Universe, isInput bool) []byte { buf := make([]byte, 240) copy(buf[0:8], ID[:]) diff --git a/receiver.go b/receiver.go index e50ae74..ee1e489 100644 --- a/receiver.go +++ b/receiver.go @@ -13,6 +13,10 @@ type Handler interface { HandlePollReply(src *net.UDPAddr, pkt *PollReplyPacket) } +type TodDataHandler interface { + HandleTodData(src *net.UDPAddr, pkt *TodDataPacket) +} + type Receiver struct { conn *net.UDPConn handler Handler @@ -128,5 +132,11 @@ func (r *Receiver) handle(src *net.UDPAddr, data []byte) { if reply, ok := pkt.(*PollReplyPacket); ok { r.handler.HandlePollReply(src, reply) } + case OpTodData: + if todHandler, ok := r.handler.(TodDataHandler); ok { + if tod, ok := pkt.(*TodDataPacket); ok { + todHandler.HandleTodData(src, tod) + } + } } } diff --git a/sender.go b/sender.go index 5f7032c..074c2a1 100644 --- a/sender.go +++ b/sender.go @@ -81,6 +81,12 @@ func (s *Sender) SendPollReply(addr *net.UDPAddr, localIP [4]byte, localMAC [6]b return err } +func (s *Sender) SendTodRequest(addr *net.UDPAddr, universe Universe) error { + pkt := BuildTodRequestPacket(universe.Net(), universe.SubNet(), universe.Universe()) + _, err := s.conn.WriteToUDP(pkt, addr) + return err +} + func (s *Sender) SendRaw(addr *net.UDPAddr, data []byte) error { _, err := s.conn.WriteToUDP(data, addr) return err