mirror of
https://github.com/jwetzell/artnet-go.git
synced 2026-09-11 16:29:28 +00:00
add basic start on artnet decoding
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
# Art-Net
|
||||||
|
Pure GoLang implementation of the [Art-Net protocol](https://art-net.org.uk). View the [specification here](https://art-net.org.uk/downloads/art-net.pdf).
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package artnet
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
OpPoll uint16 = 0x2000
|
||||||
|
OpPollReply uint16 = 0x2100
|
||||||
|
OpDiagData uint16 = 0x2300
|
||||||
|
OpCommand uint16 = 0x2400
|
||||||
|
OpDataRequest uint16 = 0x2700
|
||||||
|
OpDataReply uint16 = 0x2800
|
||||||
|
OpOutput uint16 = 0x5000
|
||||||
|
OpDmx uint16 = 0x5000
|
||||||
|
OpNzs uint16 = 0x5100
|
||||||
|
OpSync uint16 = 0x5200
|
||||||
|
OpAddress uint16 = 0x6000
|
||||||
|
OpInput uint16 = 0x7000
|
||||||
|
OpTodRequest uint16 = 0x8000
|
||||||
|
OpTodData uint16 = 0x8100
|
||||||
|
OpTodControl uint16 = 0x8200
|
||||||
|
OpRdm uint16 = 0x8300
|
||||||
|
OpRdmSub uint16 = 0x8400
|
||||||
|
OpVideoSetup uint16 = 0xa010
|
||||||
|
OpVideoPalette uint16 = 0xa020
|
||||||
|
OpVideoData uint16 = 0xa040
|
||||||
|
OpMacMaster uint16 = 0xf000
|
||||||
|
OpMacSlave uint16 = 0xf100
|
||||||
|
OpFirmwareMaster uint16 = 0xf200
|
||||||
|
OpFirmwareReply uint16 = 0xf300
|
||||||
|
OpFileTnMaster uint16 = 0xf400
|
||||||
|
OpFileFnMaster uint16 = 0xf500
|
||||||
|
OpFileFnReply uint16 = 0xf600
|
||||||
|
OpIpProg uint16 = 0xf800
|
||||||
|
OpIpProgReply uint16 = 0xf900
|
||||||
|
OpMedia uint16 = 0x9000
|
||||||
|
OpMediaPatch uint16 = 0x9100
|
||||||
|
OpMediaControl uint16 = 0x9200
|
||||||
|
OpMediaContrlReply uint16 = 0x9300
|
||||||
|
OpTimeCode uint16 = 0x9700
|
||||||
|
OpTimeSync uint16 = 0x9800
|
||||||
|
OpTrigger uint16 = 0x9900
|
||||||
|
OpDirectory uint16 = 0x9a00
|
||||||
|
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)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package artnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ArtDmx struct {
|
||||||
|
Header *ArtNetHeader
|
||||||
|
Sequence uint8
|
||||||
|
Physical uint8
|
||||||
|
SubUni uint8
|
||||||
|
Net uint8
|
||||||
|
Length uint16
|
||||||
|
Data []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewArtDmx(header *ArtNetHeader, data []byte) (*ArtDmx, error) {
|
||||||
|
artDmx := ArtDmx{
|
||||||
|
Header: header,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package artnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ArtNetPacket interface {
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package artnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ArtPoll struct {
|
||||||
|
Header *ArtNetHeader
|
||||||
|
Flags uint8
|
||||||
|
DiagPriority uint8
|
||||||
|
AddressTopHi uint8
|
||||||
|
AddressTopLo uint8
|
||||||
|
AddressBottomHi uint8
|
||||||
|
AddressBottomLo uint8
|
||||||
|
EstaManHi uint8
|
||||||
|
EstaManLo uint8
|
||||||
|
OemHi uint8
|
||||||
|
OemLo uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewArtPoll(header *ArtNetHeader, data []byte) (*ArtPoll, error) {
|
||||||
|
artPoll := ArtPoll{
|
||||||
|
Header: header,
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
//TODO(jwetzell): unpack extended poll fields
|
||||||
|
|
||||||
|
return &artPoll, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *ArtPoll) GetOpCode() uint16 {
|
||||||
|
return ap.Header.OpCode
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user