Files
cheesy-arena-lite/led/advatek_controller.go
2019-07-20 22:41:35 -07:00

217 lines
5.2 KiB
Go

// Copyright 2018 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Represents an E1.31 sACN (DMX over Ethernet) LED controller with four outputs.
package led
import (
"fmt"
"net"
)
const (
advatekPort = 5568
advatekSourceName = "Cheesy Arena"
advatekPacketTimeoutSec = 1
advatekNumPixels = 114
advatekPixelDataOffset = 126
advatekNearStripUniverse = 1
advatekFarStripUniverse = 2
)
type AdvatekController struct {
nearStrip advatekStrip
farStrip advatekStrip
conn net.Conn
packet []byte
}
func (controller *AdvatekController) SetAddress(address string) error {
if controller.conn != nil {
controller.conn.Close()
controller.conn = nil
}
if address != "" {
var err error
if controller.conn, err = net.Dial("udp4", fmt.Sprintf("%s:%d", address, advatekPort)); err != nil {
return err
}
}
return nil
}
// Sets the current LED sequence mode and resets the intra-sequence counter to the beginning.
func (controller *AdvatekController) SetMode(nearMode, farMode StripMode) {
if nearMode != controller.nearStrip.currentMode {
controller.nearStrip.currentMode = nearMode
controller.nearStrip.counter = 0
}
if farMode != controller.farStrip.currentMode {
controller.farStrip.currentMode = farMode
controller.farStrip.counter = 0
}
}
// Returns the current mode if both sides are in the same mode, or off otherwise.
func (controller *AdvatekController) GetCurrentMode() StripMode {
if controller.nearStrip.currentMode == controller.farStrip.currentMode {
return controller.nearStrip.currentMode
} else {
return OffMode
}
}
// Advances the pixel values through the current sequence and sends a packet if necessary. Should be called from a timed
// loop.
func (controller *AdvatekController) Update() error {
if controller.conn == nil {
// This controller is not configured; do nothing.
return nil
}
controller.nearStrip.updatePixels()
controller.farStrip.updatePixels()
// Create the template packet if it doesn't already exist.
if len(controller.packet) == 0 {
controller.packet = createBlankAdvatekPacket(advatekNumPixels)
}
// Send packets if the pixel values have changed.
if controller.nearStrip.shouldSendPacket() {
controller.nearStrip.populatePacketPixels(controller.packet[advatekPixelDataOffset:])
controller.sendPacket(advatekNearStripUniverse)
}
if controller.farStrip.shouldSendPacket() {
controller.farStrip.populatePacketPixels(controller.packet[advatekPixelDataOffset:])
controller.sendPacket(advatekFarStripUniverse)
}
return nil
}
// Constructs the structure of an E1.31 data packet that can be re-used indefinitely by updating the pixel data and
// re-sending it.
func createBlankAdvatekPacket(numPixels int) []byte {
size := advatekPixelDataOffset + 3*numPixels
packet := make([]byte, size)
// Preamble size
packet[0] = 0x00
packet[1] = 0x10
// Postamble size
packet[2] = 0x00
packet[3] = 0x00
// ACN packet identifier
packet[4] = 0x41
packet[5] = 0x53
packet[6] = 0x43
packet[7] = 0x2d
packet[8] = 0x45
packet[9] = 0x31
packet[10] = 0x2e
packet[11] = 0x31
packet[12] = 0x37
packet[13] = 0x00
packet[14] = 0x00
packet[15] = 0x00
// Root PDU length and flags
rootPduLength := size - 16
packet[16] = 0x70 | byte(rootPduLength>>8)
packet[17] = byte(rootPduLength & 0xff)
// E1.31 vector indicating that this is a data packet
packet[18] = 0x00
packet[19] = 0x00
packet[20] = 0x00
packet[21] = 0x04
// Component ID
for i, b := range []byte(advatekSourceName) {
packet[22+i] = b
}
// Framing PDU length and flags
framingPduLength := size - 38
packet[38] = 0x70 | byte(framingPduLength>>8)
packet[39] = byte(framingPduLength & 0xff)
// E1.31 vector indicating that this is a data packet
packet[40] = 0x00
packet[41] = 0x00
packet[42] = 0x00
packet[43] = 0x02
// Source name
for i, b := range []byte(advatekSourceName) {
packet[44+i] = b
}
// Priority
packet[108] = 100
// Universe for synchronization packets
packet[109] = 0x00
packet[110] = 0x00
// Sequence number (initial value; will be updated whenever packet is sent)
packet[111] = 0x00
// Options flags
packet[112] = 0x00
// DMX universe (will be populated whenever packet is sent)
packet[113] = 0x00
packet[114] = 0x00
// DMP layer PDU length
dmpPduLength := size - 115
packet[115] = 0x70 | byte(dmpPduLength>>8)
packet[116] = byte(dmpPduLength & 0xff)
// E1.31 vector indicating set property
packet[117] = 0x02
// Address and data type
packet[118] = 0xa1
// First property address
packet[119] = 0x00
packet[120] = 0x00
// Address increment
packet[121] = 0x00
packet[122] = 0x01
// Property value count
count := 1 + 3*numPixels
packet[123] = byte(count >> 8)
packet[124] = byte(count & 0xff)
// DMX start code
packet[125] = 0
// Remainder of packet is pixel data which will be populated whenever packet is sent.
return packet
}
func (controller *AdvatekController) sendPacket(dmxUniverse int) error {
// Update non-static packet fields.
controller.packet[111]++
controller.packet[113] = byte(dmxUniverse >> 8)
controller.packet[114] = byte(dmxUniverse & 0xff)
_, err := controller.conn.Write(controller.packet)
if err != nil {
return err
}
return nil
}