mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 21:56:50 -04:00
536 lines
13 KiB
Go
536 lines
13 KiB
Go
// Copyright 2018 Team 254. All Rights Reserved.
|
|
// Author: pat@patfairbank.com (Patrick Fairbank)
|
|
//
|
|
// Represents a LED strip attached to an E1.31 sACN (DMX over Ethernet) controller.
|
|
|
|
package led
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
controllerPort = 5568
|
|
sourceName = "Cheesy Arena"
|
|
packetTimeoutSec = 1
|
|
)
|
|
|
|
// LED sequence modes
|
|
type Mode int
|
|
|
|
const (
|
|
OffMode Mode = iota
|
|
RedMode
|
|
GreenMode
|
|
BlueMode
|
|
WhiteMode
|
|
ChaseMode
|
|
WarmupMode
|
|
Warmup2Mode
|
|
Warmup3Mode
|
|
Warmup4Mode
|
|
OwnedMode
|
|
ForceMode
|
|
BoostMode
|
|
RandomMode
|
|
FadeMode
|
|
GradientMode
|
|
BlinkMode
|
|
)
|
|
|
|
var ModeNames = map[Mode]string{
|
|
OffMode: "Off",
|
|
RedMode: "Red",
|
|
GreenMode: "Green",
|
|
BlueMode: "Blue",
|
|
WhiteMode: "White",
|
|
ChaseMode: "Chase",
|
|
WarmupMode: "Warmup",
|
|
Warmup2Mode: "Warmup Purple",
|
|
Warmup3Mode: "Warmup Sneaky",
|
|
Warmup4Mode: "Warmup Gradient",
|
|
OwnedMode: "Owned",
|
|
ForceMode: "Force",
|
|
BoostMode: "Boost",
|
|
RandomMode: "Random",
|
|
FadeMode: "Fade",
|
|
GradientMode: "Gradient",
|
|
BlinkMode: "Blink",
|
|
}
|
|
|
|
// Color RGB mappings
|
|
type color int
|
|
|
|
const (
|
|
red color = iota
|
|
orange
|
|
yellow
|
|
green
|
|
teal
|
|
blue
|
|
purple
|
|
white
|
|
black
|
|
purpleRed
|
|
purpleBlue
|
|
dimRed
|
|
dimBlue
|
|
)
|
|
|
|
var colors = map[color][3]byte{
|
|
red: {255, 0, 0},
|
|
orange: {255, 50, 0},
|
|
yellow: {255, 255, 0},
|
|
green: {0, 255, 0},
|
|
teal: {0, 100, 100},
|
|
blue: {0, 0, 255},
|
|
purple: {100, 0, 100},
|
|
white: {255, 255, 255},
|
|
black: {0, 0, 0},
|
|
purpleRed: {200, 0, 50},
|
|
purpleBlue: {50, 0, 200},
|
|
dimRed: {100, 0, 0},
|
|
dimBlue: {0, 0, 100},
|
|
}
|
|
|
|
type LedStrip struct {
|
|
CurrentMode Mode
|
|
conn net.Conn
|
|
pixels [][3]byte
|
|
oldPixels [][3]byte
|
|
packet []byte
|
|
counter int
|
|
lastPacketTime time.Time
|
|
}
|
|
|
|
func NewLedStrip(controllerAddress string, dmxUniverse int, numPixels int) (*LedStrip, error) {
|
|
ledStrip := new(LedStrip)
|
|
|
|
var err error
|
|
ledStrip.conn, err = net.Dial("udp4", fmt.Sprintf("%s:%d", controllerAddress, controllerPort))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ledStrip.pixels = make([][3]byte, numPixels)
|
|
ledStrip.oldPixels = make([][3]byte, numPixels)
|
|
ledStrip.packet = createBlankPacket(dmxUniverse, numPixels)
|
|
|
|
return ledStrip, nil
|
|
}
|
|
|
|
// Sets the current LED sequence mode and resets the intra-sequence counter to the beginning.
|
|
func (strip *LedStrip) SetMode(mode Mode) {
|
|
strip.CurrentMode = mode
|
|
strip.counter = 0
|
|
}
|
|
|
|
// Advances the pixel values through the current sequence and sends a packet if necessary. Should be called from a timed
|
|
// loop.
|
|
func (strip *LedStrip) Update() error {
|
|
// Determine the pixel values.
|
|
switch strip.CurrentMode {
|
|
case RedMode:
|
|
strip.updateSingleColorMode(red)
|
|
case GreenMode:
|
|
strip.updateSingleColorMode(green)
|
|
case BlueMode:
|
|
strip.updateSingleColorMode(blue)
|
|
case WhiteMode:
|
|
strip.updateSingleColorMode(white)
|
|
case ChaseMode:
|
|
strip.updateChaseMode()
|
|
case WarmupMode:
|
|
strip.updateWarmupMode()
|
|
case Warmup2Mode:
|
|
strip.updateWarmup2Mode()
|
|
case Warmup3Mode:
|
|
strip.updateWarmup3Mode()
|
|
case Warmup4Mode:
|
|
strip.updateWarmup4Mode()
|
|
case OwnedMode:
|
|
strip.updateOwnedMode()
|
|
case ForceMode:
|
|
strip.updateForceMode()
|
|
case BoostMode:
|
|
strip.updateBoostMode()
|
|
case RandomMode:
|
|
strip.updateRandomMode()
|
|
case FadeMode:
|
|
strip.updateFadeMode()
|
|
case GradientMode:
|
|
strip.updateGradientMode()
|
|
case BlinkMode:
|
|
strip.updateBlinkMode()
|
|
default:
|
|
strip.updateOffMode()
|
|
}
|
|
strip.counter++
|
|
|
|
// Update non-static packet fields.
|
|
strip.packet[111]++
|
|
for i, pixel := range strip.pixels {
|
|
strip.packet[126+3*i] = pixel[0]
|
|
strip.packet[127+3*i] = pixel[1]
|
|
strip.packet[128+3*i] = pixel[2]
|
|
}
|
|
|
|
// Send the packet if it hasn't changed.
|
|
if strip.shouldSendPacket() {
|
|
_, err := strip.conn.Write(strip.packet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
strip.lastPacketTime = time.Now()
|
|
|
|
// Keep a record of the pixel values in order to detect future changes.
|
|
copy(strip.oldPixels, strip.pixels)
|
|
}
|
|
|
|
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 createBlankPacket(dmxUniverse, numPixels int) []byte {
|
|
size := 126 + 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(sourceName) {
|
|
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(sourceName) {
|
|
packet[44+i] = b
|
|
}
|
|
|
|
// Priority
|
|
packet[108] = 100
|
|
|
|
// Universe for synchronization packets
|
|
packet[109] = 0x00
|
|
packet[110] = 0x00
|
|
|
|
// Sequence number (initial value)
|
|
packet[111] = 0x00
|
|
|
|
// Options flags
|
|
packet[112] = 0x00
|
|
|
|
// Universe
|
|
packet[113] = byte(dmxUniverse >> 8)
|
|
packet[114] = byte(dmxUniverse & 0xff)
|
|
|
|
// 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
|
|
|
|
return packet
|
|
}
|
|
|
|
// Returns true if the pixel data has changed or it has been too long since the last packet was sent.
|
|
func (strip *LedStrip) shouldSendPacket() bool {
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
if strip.pixels[i] != strip.oldPixels[i] {
|
|
return true
|
|
}
|
|
}
|
|
return time.Since(strip.lastPacketTime).Seconds() > packetTimeoutSec
|
|
}
|
|
|
|
func (strip *LedStrip) updateOffMode() {
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
strip.pixels[i] = colors[black]
|
|
}
|
|
}
|
|
|
|
func (strip *LedStrip) updateSingleColorMode(color color) {
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
strip.pixels[i] = colors[color]
|
|
}
|
|
}
|
|
|
|
func (strip *LedStrip) updateChaseMode() {
|
|
if strip.counter == len(colors)*len(strip.pixels) {
|
|
strip.counter = 0
|
|
}
|
|
color := color(strip.counter / len(strip.pixels))
|
|
pixelIndex := strip.counter % len(strip.pixels)
|
|
strip.pixels[pixelIndex] = colors[color]
|
|
}
|
|
|
|
func (strip *LedStrip) updateWarmupMode() {
|
|
endCounter := 250
|
|
if strip.counter == 0 {
|
|
// Show solid white to start.
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
strip.pixels[i] = colors[white]
|
|
}
|
|
} else if strip.counter <= endCounter {
|
|
// Build to the alliance color from each side.
|
|
numLitPixels := len(strip.pixels) / 2 * strip.counter / endCounter
|
|
for i := 0; i < numLitPixels; i++ {
|
|
strip.pixels[i] = colors[red]
|
|
strip.pixels[len(strip.pixels)-i-1] = colors[red]
|
|
}
|
|
} else {
|
|
// MaintainPrevent the counter from rolling over.
|
|
strip.counter = endCounter
|
|
}
|
|
}
|
|
|
|
func (strip *LedStrip) updateWarmup2Mode() {
|
|
startCounter := 100
|
|
endCounter := 250
|
|
if strip.counter < startCounter {
|
|
// Show solid purple to start.
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
strip.pixels[i] = colors[purple]
|
|
}
|
|
} else if strip.counter <= endCounter {
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
strip.pixels[i] = getFadeColor(purple, red, strip.counter-startCounter, endCounter-startCounter)
|
|
}
|
|
} else {
|
|
// MaintainPrevent the counter from rolling over.
|
|
strip.counter = endCounter
|
|
}
|
|
}
|
|
|
|
func (strip *LedStrip) updateWarmup3Mode() {
|
|
startCounter := 50
|
|
middleCounter := 225
|
|
endCounter := 250
|
|
if strip.counter < startCounter {
|
|
// Show solid purple to start.
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
strip.pixels[i] = colors[purple]
|
|
}
|
|
} else if strip.counter < middleCounter {
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
strip.pixels[i] = getFadeColor(purple, purpleBlue, strip.counter-startCounter, middleCounter-startCounter)
|
|
}
|
|
} else if strip.counter <= endCounter {
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
strip.pixels[i] = getFadeColor(purpleBlue, red, strip.counter-middleCounter, endCounter-middleCounter)
|
|
}
|
|
} else {
|
|
// Maintain the current value and prevent the counter from rolling over.
|
|
strip.counter = endCounter
|
|
}
|
|
}
|
|
|
|
func (strip *LedStrip) updateWarmup4Mode() {
|
|
startOffset := 50
|
|
middleCounter := 100
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
strip.pixels[len(strip.pixels)-i-1] = getGradientColor(i+strip.counter+startOffset, 75)
|
|
}
|
|
if strip.counter >= middleCounter {
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
if i < strip.counter-middleCounter {
|
|
strip.pixels[i] = colors[red]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (strip *LedStrip) updateOwnedMode() {
|
|
speedDivisor := 30
|
|
pixelSpacing := 4
|
|
if strip.counter%speedDivisor != 0 {
|
|
return
|
|
}
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
if i%pixelSpacing == strip.counter/speedDivisor%pixelSpacing {
|
|
strip.pixels[i] = colors[red]
|
|
} else {
|
|
strip.pixels[i] = colors[black]
|
|
}
|
|
}
|
|
}
|
|
|
|
func (strip *LedStrip) updateForceMode() {
|
|
speedDivisor := 30
|
|
pixelSpacing := 7
|
|
if strip.counter%speedDivisor != 0 {
|
|
return
|
|
}
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
switch (i + strip.counter/speedDivisor) % pixelSpacing {
|
|
case 2:
|
|
fallthrough
|
|
case 4:
|
|
strip.pixels[i] = colors[red]
|
|
case 3:
|
|
strip.pixels[i] = colors[dimBlue]
|
|
default:
|
|
strip.pixels[i] = colors[black]
|
|
}
|
|
}
|
|
}
|
|
|
|
func (strip *LedStrip) updateBoostMode() {
|
|
speedDivisor := 4
|
|
pixelSpacing := 4
|
|
if strip.counter%speedDivisor != 0 {
|
|
return
|
|
}
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
if i%pixelSpacing == strip.counter/speedDivisor%pixelSpacing {
|
|
strip.pixels[i] = colors[blue]
|
|
} else {
|
|
strip.pixels[i] = colors[black]
|
|
}
|
|
}
|
|
}
|
|
|
|
func (strip *LedStrip) updateRandomMode() {
|
|
if strip.counter%10 != 0 {
|
|
return
|
|
}
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
strip.pixels[i] = colors[color(rand.Intn(int(black)))] // Ignore colors listed after white.
|
|
}
|
|
}
|
|
|
|
func (strip *LedStrip) updateFadeMode() {
|
|
fadeCycles := 40
|
|
holdCycles := 10
|
|
if strip.counter == 4*holdCycles+4*fadeCycles {
|
|
strip.counter = 0
|
|
}
|
|
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
if strip.counter < holdCycles {
|
|
strip.pixels[i] = colors[black]
|
|
} else if strip.counter < holdCycles+fadeCycles {
|
|
strip.pixels[i] = getFadeColor(black, red, strip.counter-holdCycles, fadeCycles)
|
|
} else if strip.counter < 2*holdCycles+fadeCycles {
|
|
strip.pixels[i] = colors[red]
|
|
} else if strip.counter < 2*holdCycles+2*fadeCycles {
|
|
strip.pixels[i] = getFadeColor(red, black, strip.counter-2*holdCycles-fadeCycles, fadeCycles)
|
|
} else if strip.counter < 3*holdCycles+2*fadeCycles {
|
|
strip.pixels[i] = colors[black]
|
|
} else if strip.counter < 3*holdCycles+3*fadeCycles {
|
|
strip.pixels[i] = getFadeColor(black, blue, strip.counter-3*holdCycles-2*fadeCycles, fadeCycles)
|
|
} else if strip.counter < 4*holdCycles+3*fadeCycles {
|
|
strip.pixels[i] = colors[blue]
|
|
} else if strip.counter < 4*holdCycles+4*fadeCycles {
|
|
strip.pixels[i] = getFadeColor(blue, black, strip.counter-4*holdCycles-3*fadeCycles, fadeCycles)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (strip *LedStrip) updateGradientMode() {
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
strip.pixels[len(strip.pixels)-i-1] = getGradientColor(i+strip.counter, 75)
|
|
}
|
|
}
|
|
|
|
func (strip *LedStrip) updateBlinkMode() {
|
|
divisor := 10
|
|
for i := 0; i < len(strip.pixels); i++ {
|
|
if strip.counter%divisor < divisor/2 {
|
|
strip.pixels[i] = colors[white]
|
|
} else {
|
|
strip.pixels[i] = colors[black]
|
|
}
|
|
}
|
|
}
|
|
|
|
// Interpolates between the two colors based on the given fraction.
|
|
func getFadeColor(fromColor, toColor color, numerator, denominator int) [3]byte {
|
|
from := colors[fromColor]
|
|
to := colors[toColor]
|
|
var fadeColor [3]byte
|
|
for i := 0; i < 3; i++ {
|
|
fadeColor[i] = byte(int(from[i]) + numerator*(int(to[i])-int(from[i]))/denominator)
|
|
}
|
|
return fadeColor
|
|
}
|
|
|
|
// Calculates the value of a single pixel in a gradient.
|
|
func getGradientColor(offset, numPixels int) [3]byte {
|
|
offset %= numPixels
|
|
if 3*offset < numPixels {
|
|
return getFadeColor(red, green, 3*offset, numPixels)
|
|
} else if 3*offset < 2*numPixels {
|
|
return getFadeColor(green, blue, 3*offset-numPixels, numPixels)
|
|
} else {
|
|
return getFadeColor(blue, red, 3*offset-2*numPixels, numPixels)
|
|
}
|
|
}
|