mirror of
https://github.com/jwetzell/artnet-go.git
synced 2026-08-20 22:28:59 +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