package main import ( "fmt" "log" "math" "os" "strconv" "time" patterns "git.martyn.berlin/martyn/LEDController/internal/patterns" queue "git.martyn.berlin/martyn/LEDController/internal/queue" remapping "git.martyn.berlin/martyn/LEDController/internal/remapping" webserver "git.martyn.berlin/martyn/LEDController/internal/webserver" "github.com/Hundemeier/go-sacn/sacn" ) type RGBcolor = [3]byte var currentFrame [99][512]byte //TODO make dynamic var channels [99]chan<- [512]byte var sema = make(chan struct{}, 1) // a binary semaphore guarding currentFrame var currentEffect queue.QueueItem var globalEffectChannel = make(chan queue.QueueItem, 1024) var universeCount int = 0 var overrideEffect queue.QueueItem var overrideFlag = make(chan queue.QueueItem, 1) var previousEffect queue.QueueItem var msDelay = 0 func isColorSet(c RGBcolor) bool { // Any color so long as it's NOT black! return c[0] != 0 || c[1] != 0 || c[2] != 0 } func foreverLoop() { for /*ever*/ { time.Sleep(time.Duration(msDelay) * time.Millisecond) //25fps for u := 0; u < universeCount; u++ { sema <- struct{}{} // acquire token channels[u] <- currentFrame[u] <-sema } } } func reduceBrightness(universe [512]byte, percentage int) [512]byte { for i := range universe { universe[i] = byte(float64(universe[i]) * (float64(percentage) / float64(100))) } return universe } func main() { go func() { var err error listenPort := 5353 if os.Getenv("LISTEN_PORT") != "" { listenPort, err = strconv.Atoi(os.Getenv("LISTEN_PORT")) if err != nil { listenPort = 5353 } } fmt.Printf("Starting webserver on port %d\n", listenPort) webserver.HandleHTTP(globalEffectChannel, listenPort, overrideFlag) }() PanelIP := os.Getenv("PANEL_IP") if PanelIP == "" { PanelIP = "127.0.0.1" } PanelWidth, err := strconv.Atoi(os.Getenv("PANEL_WIDTH")) if err != nil { PanelWidth = 60 } PanelHeight, err := strconv.Atoi(os.Getenv("PANEL_HEIGHT")) if err != nil { PanelHeight = 15 } msDelay, err = strconv.Atoi(os.Getenv("MS_DELAY")) if err != nil { msDelay = 40 } universeCount = int(math.Ceil(float64(PanelHeight*PanelWidth*3) / 510)) fmt.Printf("Universe count is %d\n", universeCount) PanelBrightness, err := strconv.Atoi(os.Getenv("PANEL_BRIGHTNESS")) if err != nil { PanelBrightness = 100 } //instead of "" you could provide an ip-address that the socket should bind to trans, err := sacn.NewTransmitter("", [16]byte{1, 2}, "test") if err != nil { log.Fatal(err) } //activates the universes for i := 0; i < universeCount; i++ { channels[i], err = trans.Activate(uint16(i + 1)) //deactivate the channel on exit defer close(channels[i]) trans.SetMulticast(uint16(i+1), false) //this specific setup will not multicast on windows, trans.SetDestinations(uint16(i+1), []string{PanelIP}) } // Plasma for start frame /*rearranged := reduceBrightness(remapping.SliceRearrange(PanelWidth, PanelHeight, true, remapping.XYGridToLinear(PanelWidth, PanelHeight, patterns.PlasmaPanel(PanelWidth, PanelHeight, 40))), PanelBrightness) for i := 0; i < universeCount; i++ { currentFrame[i] = remapping.Slice512(rearranged[i]) }*/ var e queue.QueueItem e.Effect = "red" e.Duration = 40 * 50 globalEffectChannel <- e e.Effect = "colour" e.Duration = 40 * 10 e.SeedColour[0] = 0 e.SeedColour[1] = 255 e.SeedColour[2] = 0 globalEffectChannel <- e e.Effect = "plasma" e.Speed = 40 globalEffectChannel <- e e.Effect = "queue" overrideEffect = e previousEffect.Effect = "queue" go func() { for /*ever*/ { if len(overrideFlag) > 0 { e = <-overrideFlag if e.Effect == "queue" { if previousEffect.Effect != "queue" { fmt.Printf("Returning to queue : %s\n", previousEffect.Effect) overrideEffect = e currentEffect = previousEffect previousEffect.Effect = "queue" currentEffect = <-globalEffectChannel } } else { fmt.Printf("Overriding with : %s\n", e.Effect) if previousEffect.Effect == "queue" { previousEffect = currentEffect } overrideEffect = e currentEffect = e } } if overrideEffect.Effect == "queue" { if currentEffect.Duration > 0 { if currentEffect.Duration%uint64(msDelay) != 0 { currentEffect.Duration = uint64(currentEffect.Duration/uint64(msDelay)) * uint64(msDelay) } currentEffect.Duration -= uint64(msDelay) } else { if len(globalEffectChannel) > 0 { previousEffect.Effect = "queue" lastEffect := currentEffect currentEffect = <-globalEffectChannel if lastEffect != currentEffect { fmt.Printf("New effect selected : %s\n", currentEffect.Effect) } } } } var rearranged [][]byte switch currentEffect.Effect { case "line": rearranged = remapping.SliceRearrange(PanelWidth, PanelHeight, true, remapping.XYGridToLinear(PanelWidth, PanelHeight, patterns.ZigZag(PanelWidth, PanelHeight))) case "plasma": if isColorSet(currentEffect.SeedColour) { rearranged = remapping.SliceRearrange(PanelWidth, PanelHeight, true, remapping.XYGridToLinear(PanelWidth, PanelHeight, patterns.PlasmaPanelSingleColor(PanelWidth, PanelHeight, currentEffect.Speed, currentEffect.SeedColour))) } else { rearranged = remapping.SliceRearrange(PanelWidth, PanelHeight, true, remapping.XYGridToLinear(PanelWidth, PanelHeight, patterns.PlasmaPanel(PanelWidth, PanelHeight, currentEffect.Speed))) } case "red": rearranged = remapping.SliceRearrange(PanelWidth, PanelHeight, false, remapping.XYGridToLinear(PanelWidth, PanelHeight, patterns.RedPanel(PanelWidth, PanelHeight))) case "random": rearranged = remapping.SliceRearrange(PanelWidth, PanelHeight, false, remapping.XYGridToLinear(PanelWidth, PanelHeight, patterns.RandomColourPanel(PanelWidth, PanelHeight, currentEffect.Speed))) case "linearplasma": rearranged = remapping.SliceRearrange(PanelWidth, PanelHeight, false, patterns.LinearPlasma(PanelWidth*PanelHeight)) case "gradientred": rearranged = remapping.SliceRearrange(PanelWidth, PanelHeight, false, patterns.Gradient(255, 0, 0, 0, 0, 255, 0, PanelWidth*PanelHeight)) case "sine": var black patterns.RGBcolor var red patterns.RGBcolor red[0] = 255 rearranged = remapping.SliceRearrange(PanelWidth, PanelHeight, true, remapping.XYGridToLinear(PanelWidth, PanelHeight, patterns.Sinewave(PanelWidth, PanelHeight, black, red, currentEffect.Speed))) case "sinechase": var black patterns.RGBcolor var red patterns.RGBcolor red[0] = 255 rearranged = remapping.SliceRearrange(PanelWidth, PanelHeight, true, remapping.XYGridToLinear(PanelWidth, PanelHeight, patterns.SineChase(PanelWidth, PanelHeight, black, red, currentEffect.Speed))) case "plasmapulse": rearranged = remapping.SliceRearrange(PanelWidth, PanelHeight, false, remapping.XYGridToLinear(PanelWidth, PanelHeight, patterns.PlasmaColourPanel(PanelWidth, PanelHeight, currentEffect.Speed))) case "colour": rearranged = remapping.SliceRearrange(PanelWidth, PanelHeight, false, remapping.XYGridToLinear(PanelWidth, PanelHeight, patterns.FillPanel(PanelWidth, PanelHeight, currentEffect.SeedColour[0], currentEffect.SeedColour[1], currentEffect.SeedColour[2]))) case "fade": rearranged = remapping.SliceRearrange(PanelWidth, PanelHeight, true, remapping.XYGridToLinear(PanelWidth, PanelHeight, patterns.GradientPanel(PanelWidth, PanelHeight, currentEffect.SeedColour, currentEffect.SecondColour))) default: rearranged = remapping.SliceRearrange(PanelWidth, PanelHeight, false, remapping.XYGridToLinear(PanelWidth, PanelHeight, patterns.FillPanel(PanelWidth, PanelHeight, 128, 0, 128))) } //rearranged := remapping.SliceRearrange(PanelWidth,PanelHeight,true,linearPlasma(PanelWidth*PanelHeight)) sema <- struct{}{} // acquire token for i := 0; i < universeCount; i++ { currentFrame[i] = reduceBrightness(remapping.Slice512(rearranged[i]), PanelBrightness) } <-sema time.Sleep(40 * time.Millisecond) } }() foreverLoop() }