From 5097901aa9db6b45e2f94f416c86a7c8d6d6304f Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Tue, 23 Dec 2025 14:10:31 -0600 Subject: [PATCH] switch to unmarshal marshal --- artnet.go | 33 +++++++++-------- command.go | 43 ---------------------- dmx.go | 104 +++++++++++++++++++++++++++++++++++------------------ go.mod | 2 +- packet.go | 34 +++--------------- poll.go | 62 +++++++++++++++++++++++++------- 6 files changed, 143 insertions(+), 135 deletions(-) delete mode 100644 command.go diff --git a/artnet.go b/artnet.go index 1d94b41..a7fb4f7 100644 --- a/artnet.go +++ b/artnet.go @@ -1,6 +1,10 @@ package artnet -import "fmt" +import ( + "encoding/binary" + "errors" + "fmt" +) var ( OpPoll uint16 = 0x2000 @@ -43,20 +47,19 @@ var ( OpDirectoryReply uint16 = 0x9b00 ) -func Decode(bytes []byte) (ArtNetPacket, error) { - header, err := NewHeader(bytes) - if err != nil { - return nil, err - } - switch header.OpCode { - case OpPoll: - return NewArtPoll(header, bytes) - case OpCommand: - return NewArtCommand(header, bytes) - case OpDmx: - return NewArtDmx(header, bytes) - default: - return nil, fmt.Errorf("unhandled opcode: %#x", header.OpCode) +var ArtNetID []uint8 = []uint8{'A', 'r', 't', '-', 'N', 'e', 't', 0x00} +func Decode(bytes []byte) (ArtNetPacket, error) { + if len(bytes) < 12 { + return nil, errors.New("ArtNet packet must be at least 12 bytes") + } + opCode := binary.LittleEndian.Uint16(bytes[8:10]) + switch opCode { + case OpPoll: + return NewArtPoll(bytes) + case OpDmx: + return NewArtDmx(bytes) + default: + return nil, fmt.Errorf("unhandled opcode: %#x", opCode) } } diff --git a/command.go b/command.go deleted file mode 100644 index f538cc6..0000000 --- a/command.go +++ /dev/null @@ -1,43 +0,0 @@ -package artnet - -import ( - "errors" -) - -type ArtCommand struct { - Header *ArtNetHeader - EstaManHi uint8 - EstaManLo uint8 - Length uint16 - Data string -} - -func NewArtCommand(header *ArtNetHeader, data []byte) (*ArtCommand, error) { - artCommand := ArtCommand{ - Header: header, - } - - if len(data) < 18 { - return nil, errors.New("ArtCommand packet must be at least 14 bytes long") - } - offset := 12 - - artCommand.EstaManHi = data[offset] - artCommand.EstaManLo = data[offset+1] - - artCommand.Length = uint16(data[offset+2])<<8 + uint16(data[offset+3]) - - commandDataOffset := offset + 4 - - if len(data[commandDataOffset:]) < int(artCommand.Length) { - return nil, errors.New("ArtCommand packet length mismatch") - } - - artCommand.Data = string(data[commandDataOffset : commandDataOffset+int(artCommand.Length)-1]) - - return &artCommand, nil -} - -func (ap *ArtCommand) GetOpCode() uint16 { - return ap.Header.OpCode -} diff --git a/dmx.go b/dmx.go index 33b52c7..267ea14 100644 --- a/dmx.go +++ b/dmx.go @@ -1,49 +1,85 @@ package artnet import ( + "encoding/binary" "errors" + "slices" ) type ArtDmx struct { - Header *ArtNetHeader - Sequence uint8 - Physical uint8 - SubUni uint8 - Net uint8 - Length uint16 - Data []uint8 + ID []uint8 + OpCode uint16 + ProtVerHi uint8 + ProtVerLo uint8 + Sequence uint8 + Physical uint8 + SubUni uint8 + Net uint8 + Length uint16 + Data []uint8 } -func NewArtDmx(header *ArtNetHeader, data []byte) (*ArtDmx, error) { - artDmx := ArtDmx{ - Header: header, +func NewArtDmx(data []byte) (*ArtDmx, error) { + artDmx := ArtDmx{} + + err := artDmx.UnmarshalBinary(data) + + if err != nil { + return nil, err } - if len(data) < 18 { - return nil, errors.New("ArtDmx packet must be at least 14 bytes long") - } - offset := 12 - - artDmx.Sequence = data[offset] - artDmx.Physical = data[offset+1] - artDmx.SubUni = data[offset+2] - artDmx.Net = data[offset+3] - - artDmx.Length = uint16(data[offset+4])<<8 + uint16(data[offset+5]) - - dmxDataOffset := offset + 6 - - if len(data[dmxDataOffset:]) < int(artDmx.Length) { - return nil, errors.New("ArtDmx packet length mismatch") - } - - artDmx.Data = make([]uint8, artDmx.Length) - - copy(artDmx.Data, data[dmxDataOffset:dmxDataOffset+int(artDmx.Length)]) - return &artDmx, nil } -func (ap *ArtDmx) GetOpCode() uint16 { - return ap.Header.OpCode +func (ad *ArtDmx) GetOpCode() uint16 { + return ad.OpCode +} + +func (ad *ArtDmx) GetProtVer() uint16 { + return uint16(ad.ProtVerHi)<<8 + uint16(ad.ProtVerLo) +} + +func (ad *ArtDmx) GetID() []uint8 { + return ad.ID +} + +func (ad *ArtDmx) UnmarshalBinary(data []byte) error { + if len(data) < 18 { + return errors.New("ArtDmx packet must be at least 18 bytes long") + } + + if !slices.Equal(ArtNetID, data[0:8]) { + return errors.New("ID does not match Art-Net ID") + } + + ad.OpCode = binary.LittleEndian.Uint16(data[8:10]) + ad.ProtVerHi = data[10] + ad.ProtVerLo = data[11] + + offset := 12 + + ad.Sequence = data[offset] + ad.Physical = data[offset+1] + ad.SubUni = data[offset+2] + ad.Net = data[offset+3] + + ad.Length = uint16(data[offset+4])<<8 + uint16(data[offset+5]) + + dmxDataOffset := offset + 6 + + if len(data[dmxDataOffset:]) < int(ad.Length) { + return errors.New("ArtDmx packet length mismatch") + } + + ad.Data = make([]uint8, ad.Length) + + copy(ad.Data, data[dmxDataOffset:dmxDataOffset+int(ad.Length)]) + return nil +} + +func (ad *ArtDmx) MarshalBinary() ([]byte, error) { + data := []byte(ArtNetID) + data = append(data, byte(ad.OpCode), byte(ad.OpCode>>8), ad.ProtVerHi, ad.ProtVerLo, ad.Sequence, ad.Physical, ad.SubUni, ad.Net, byte(ad.Length>>8), byte(ad.Length)) + data = append(data, ad.Data...) + return data, nil } diff --git a/go.mod b/go.mod index b36e665..922bad8 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/jwetzell/artnet-go -go 1.23.1 +go 1.25.5 diff --git a/packet.go b/packet.go index 1e18c32..f24f061 100644 --- a/packet.go +++ b/packet.go @@ -1,37 +1,13 @@ package artnet import ( - "encoding/binary" - "errors" - "slices" + "encoding" ) type ArtNetPacket interface { + encoding.BinaryUnmarshaler + encoding.BinaryMarshaler GetOpCode() uint16 -} - -var ArtNetID []uint8 = []uint8{'A', 'r', 't', '-', 'N', 'e', 't', 0x00} - -type ArtNetHeader struct { - ID []uint8 - OpCode uint16 - ProtVerHi uint8 - ProtVerLo uint8 -} - -func NewHeader(data []byte) (*ArtNetHeader, error) { - if len(data) < 12 { - return nil, errors.New("header must be at least 12 bytes") - } - - if !slices.Equal(ArtNetID, data[0:8]) { - return nil, errors.New("header id does not match Art-Net ID") - } - - return &ArtNetHeader{ - OpCode: binary.LittleEndian.Uint16(data[8:10]), - ProtVerHi: data[10], - ProtVerLo: data[11], - }, nil - + GetProtVer() uint16 + GetID() []uint8 } diff --git a/poll.go b/poll.go index dd3636a..9bb64a3 100644 --- a/poll.go +++ b/poll.go @@ -1,11 +1,16 @@ package artnet import ( + "encoding/binary" "errors" + "slices" ) type ArtPoll struct { - Header *ArtNetHeader + ID []uint8 + OpCode uint16 + ProtVerHi uint8 + ProtVerLo uint8 Flags uint8 DiagPriority uint8 AddressTopHi uint8 @@ -18,23 +23,54 @@ type ArtPoll struct { OemLo uint8 } -func NewArtPoll(header *ArtNetHeader, data []byte) (*ArtPoll, error) { - artPoll := ArtPoll{ - Header: header, - } +func NewArtPoll(data []byte) (*ArtPoll, error) { + artPoll := ArtPoll{} - if len(data) < 14 { - return nil, errors.New("ArtPoll packet must be at least 14 bytes long") - } - offset := 12 - artPoll.Flags = data[offset] - artPoll.DiagPriority = data[offset+1] + err := artPoll.UnmarshalBinary(data) - //TODO(jwetzell): unpack extended poll fields + if err != nil { + return nil, err + } return &artPoll, nil } func (ap *ArtPoll) GetOpCode() uint16 { - return ap.Header.OpCode + return ap.OpCode +} + +func (ap *ArtPoll) GetProtVer() uint16 { + return uint16(ap.ProtVerHi)<<8 + uint16(ap.ProtVerLo) +} + +func (ap *ArtPoll) GetID() []uint8 { + return ap.ID +} + +func (ap *ArtPoll) UnmarshalBinary(data []byte) error { + + if len(data) < 14 { + return errors.New("ArtPoll packet must be at least 14 bytes long") + } + + if !slices.Equal(ArtNetID, data[0:8]) { + return errors.New("ID does not match Art-Net ID") + } + + ap.OpCode = binary.LittleEndian.Uint16(data[8:10]) + ap.ProtVerHi = data[10] + ap.ProtVerLo = data[11] + + offset := 12 + ap.Flags = data[offset] + ap.DiagPriority = data[offset+1] + //TODO(jwetzell): unpack extended poll fields + return nil +} + +func (ap *ArtPoll) MarshalBinary() ([]byte, error) { + data := []byte(ArtNetID) + data = append(data, byte(ap.OpCode), byte(ap.OpCode>>8), ap.ProtVerHi, ap.ProtVerLo, ap.Flags, ap.DiagPriority) + //TODO(jwetzell): pack extended poll fields + return data, nil }