From eec90ba0c10736350fd428c65a7bf0244197fe8b Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sat, 30 May 2026 07:13:09 -0500 Subject: [PATCH] add tod request and tests from ACT --- artnet.go | 10 ++ todrequest.go | 106 ++++++++++++++++++ todrequest_test.go | 265 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 381 insertions(+) create mode 100644 todrequest.go create mode 100644 todrequest_test.go diff --git a/artnet.go b/artnet.go index 5a5adf1..21b1499 100644 --- a/artnet.go +++ b/artnet.go @@ -155,6 +155,16 @@ func Decode(bytes []byte) (ArtNetPacket, error) { } return &artSync, nil + case OpTodRequest: + artTodRequest := ArtTodRequest{} + + err := artTodRequest.UnmarshalBinary(bytes) + + if err != nil { + return nil, err + } + + return &artTodRequest, nil default: return nil, fmt.Errorf("unhandled opcode: %#x", opCode) } diff --git a/todrequest.go b/todrequest.go new file mode 100644 index 0000000..6dd92d5 --- /dev/null +++ b/todrequest.go @@ -0,0 +1,106 @@ +package artnet + +import ( + "encoding/binary" + "errors" + "slices" +) + +type TodRequestCommand uint8 + +const ( + TodFull TodRequestCommand = 0x00 +) + +type ArtTodRequest struct { + ID [8]uint8 + OpCode uint16 + ProtVerHi uint8 + ProtVerLo uint8 + filler1 uint8 + filler2 uint8 + spare1 uint8 + spare2 uint8 + spare3 uint8 + spare4 uint8 + spare5 uint8 + spare6 uint8 + spare7 uint8 + Net uint8 + Command TodRequestCommand + Address []uint8 +} + +func (adr *ArtTodRequest) GetOpCode() uint16 { + return adr.OpCode +} + +func (adr *ArtTodRequest) GetProtVer() uint16 { + return uint16(adr.ProtVerHi)<<8 + uint16(adr.ProtVerLo) +} + +func (adr *ArtTodRequest) GetID() [8]uint8 { + return adr.ID +} + +func (adr *ArtTodRequest) UnmarshalBinary(data []byte) error { + + if len(data) < 32 { + return errors.New("ArtTodRequest packet must be at least 32 bytes long") + } + + copy(adr.ID[:], data[0:8]) + + if !slices.Equal(ArtNetID[:], adr.ID[:]) { + return errors.New("ID does not match Art-Net ID") + } + + adr.OpCode = binary.LittleEndian.Uint16(data[8:10]) + adr.ProtVerHi = data[10] + adr.ProtVerLo = data[11] + + offset := 12 + adr.filler1 = data[offset] + adr.filler2 = data[offset+1] + adr.spare1 = data[offset+2] + adr.spare2 = data[offset+3] + adr.spare3 = data[offset+4] + adr.spare4 = data[offset+5] + adr.spare5 = data[offset+6] + adr.spare6 = data[offset+7] + adr.spare7 = data[offset+8] + adr.Net = data[offset+9] + adr.Command = TodRequestCommand(data[offset+10]) + addCount := data[offset+11] + adr.Address = make([]uint8, addCount) + if len(data[offset+12:]) < int(addCount) { + return errors.New("[]byte length not long enough to contain address length specified in packet") + } + copy(adr.Address, data[offset+12:offset+12+int(addCount)]) + return nil +} + +func (adr *ArtTodRequest) MarshalBinary() ([]byte, error) { + if len(adr.Address) > 32 { + return nil, errors.New("address count must not be greater than 32") + } + data := make([]byte, 8+16+len(adr.Address)) + copy(data[0:8], adr.ID[:]) + binary.LittleEndian.PutUint16(data[8:10], adr.OpCode) + data[10] = adr.ProtVerHi + data[11] = adr.ProtVerLo + data[12] = adr.filler1 + data[13] = adr.filler2 + data[14] = adr.spare1 + data[15] = adr.spare2 + data[16] = adr.spare3 + data[17] = adr.spare4 + data[18] = adr.spare5 + data[19] = adr.spare6 + data[20] = adr.spare7 + data[21] = adr.Net + data[22] = uint8(adr.Command) + data[23] = uint8(len(adr.Address)) + copy(data[24:], adr.Address) + return data, nil +} diff --git a/todrequest_test.go b/todrequest_test.go new file mode 100644 index 0000000..3e999c1 --- /dev/null +++ b/todrequest_test.go @@ -0,0 +1,265 @@ +package artnet_test + +import ( + "reflect" + "testing" + + "github.com/jwetzell/artnet-go" +) + +func TestGoodArtTodRequestUnmarshal(t *testing.T) { + tests := []struct { + Name string + Data []byte + Expected *artnet.ArtTodRequest + }{ + { + Name: "ACT Packet 1", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x00}, + }, + }, + { + Name: "ACT Packet 2", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 3", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 4", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 5", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 6", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 7", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 8", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 9", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 10", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 11", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 12", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 13", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 14", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 15", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 16", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 17", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + { + Name: "ACT Packet 18", + Data: []byte{0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, 0x00, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Expected: &artnet.ArtTodRequest{ + ID: artnet.ArtNetID, + OpCode: artnet.OpTodRequest, + ProtVerHi: 0x00, + ProtVerLo: 0x0e, + Command: artnet.TodFull, + Net: 0x00, + Address: []uint8{0x10}, + }, + }, + } + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + got := &artnet.ArtTodRequest{} + + err := got.UnmarshalBinary(test.Data) + if err != nil { + t.Fatalf("failed to Unmarshal ArtTodRequest: %s", err) + } + if !reflect.DeepEqual(got, test.Expected) { + t.Fatalf("ArtTodRequest does not match got: %+v expected: %+v", got, test.Expected) + } + }) + } +}