From 91206f6037cfd843ebd67651c6e09c7b39bb9187 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sat, 30 May 2026 09:06:07 -0500 Subject: [PATCH] add tod control packet --- artnet.go | 10 ++++++ todcontrol.go | 88 ++++++++++++++++++++++++++++++++++++++++++++++ todcontrol_test.go | 30 ++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 todcontrol.go create mode 100644 todcontrol_test.go diff --git a/artnet.go b/artnet.go index f01bb00..d091f6c 100644 --- a/artnet.go +++ b/artnet.go @@ -185,6 +185,16 @@ func Decode(bytes []byte) (ArtNetPacket, error) { } return &artTodRequest, nil + case OpTodControl: + artTodControl := ArtTodControl{} + + err := artTodControl.UnmarshalBinary(bytes) + + if err != nil { + return nil, err + } + + return &artTodControl, nil default: return nil, fmt.Errorf("unhandled opcode: %#x", opCode) } diff --git a/todcontrol.go b/todcontrol.go new file mode 100644 index 0000000..802a096 --- /dev/null +++ b/todcontrol.go @@ -0,0 +1,88 @@ +package artnet + +import ( + "encoding/binary" + "errors" + "slices" +) + +type TodControlCommand uint8 + +const ( + AtcNone TodControlCommand = 0x00 + AtcFlush TodControlCommand = 0x01 + AtcEnd TodControlCommand = 0x02 + AtcIncOn TodControlCommand = 0x03 + AtcIncOff TodControlCommand = 0x04 +) + +type ArtTodControl struct { + filler1 uint8 + filler2 uint8 + spare1 uint8 + spare2 uint8 + spare3 uint8 + spare4 uint8 + spare5 uint8 + spare6 uint8 + spare7 uint8 + Net uint8 + Command TodControlCommand + Address uint8 +} + +func (adr *ArtTodControl) GetOpCode() uint16 { + return OpTodRequest +} + +func (adr *ArtTodControl) UnmarshalBinary(data []byte) error { + + if len(data) < 24 { + return errors.New("ArtTodControl packet must be at least 24 bytes long") + } + + if !slices.Equal(ArtNetID[:], data[0:8]) { + return errors.New("ID does not match Art-Net ID") + } + + opCode := binary.LittleEndian.Uint16(data[8:10]) + if opCode != OpTodRequest { + return errors.New("packet does not have the correct OpCode for an ArtTodControl packet") + } + + 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 = TodControlCommand(data[offset+10]) + adr.Address = data[offset+11] + return nil +} + +func (adr *ArtTodControl) MarshalBinary() ([]byte, error) { + data := make([]byte, 8+16) + copy(data[0:8], ArtNetID[:]) + binary.LittleEndian.PutUint16(data[8:10], OpTodRequest) + data[10] = 0 + data[11] = 14 + 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] = adr.Address + return data, nil +} diff --git a/todcontrol_test.go b/todcontrol_test.go new file mode 100644 index 0000000..b95b633 --- /dev/null +++ b/todcontrol_test.go @@ -0,0 +1,30 @@ +package artnet_test + +import ( + "reflect" + "testing" + + "github.com/jwetzell/artnet-go" +) + +func TestGoodArtTodControlUnmarshal(t *testing.T) { + tests := []struct { + Name string + Data []byte + Expected *artnet.ArtTodControl + }{} + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + got := &artnet.ArtTodControl{} + + err := got.UnmarshalBinary(test.Data) + if err != nil { + t.Fatalf("failed to Unmarshal ArtTodControl: %s", err) + } + if !reflect.DeepEqual(got, test.Expected) { + t.Fatalf("ArtTodControl does not match got: %+v expected: %+v", got, test.Expected) + } + }) + } +}