Compare commits

...

23 Commits

Author SHA1 Message Date
Martyn b9d8f7de92 Newer Drone requires kind: kubernetes
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2021-01-03 11:55:29 +01:00
Martyn 5655ddd56e Trigger CI
continuous-integration/drone/push Build was killed Details
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2021-01-03 11:53:31 +01:00
Martyn b898c0c669 Merge branch 'master' of ssh://git-ssh.martyn.berlin:2222/martyn/LEDController
continuous-integration/drone/push Build was killed Details
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2021-01-03 11:48:48 +01:00
Martyn 568654d88e Trigger CI
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2021-01-03 11:48:41 +01:00
Martyn 2709d98125 moarpatterns (#1)
Make the comparison work

Allow color setting

types.... yeah... I know about them.

single-colour plasma, I hope

Reviewed-on: #1
Co-Authored-By: Martyn <m@rtyn.berlin>
Co-Committed-By: Martyn <m@rtyn.berlin>
2021-01-03 10:46:39 +00:00
Martyn 10cef66e13 zero-length override
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-07-13 18:35:53 +02:00
Martyn ffec6c8260 Rest of the uint64 stuff
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-07-13 18:16:50 +02:00
Martyn 4d72b04cc1 Uint64 because time...
continuous-integration/drone/push Build is failing Details
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-07-13 18:13:44 +02:00
Martyn 7a5bb1108a Ensure we have a multiple of the delay in duration, and tick by the delay
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-07-13 17:46:11 +02:00
Martyn 8c3df1dfa2 Vary framerate by env
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-07-13 17:14:02 +02:00
Martyn 8edab5ba5e New defaults and return to queue
continuous-integration/drone/tag Build is passing Details
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-07-13 16:37:14 +02:00
Martyn c346052895 Overrides!
continuous-integration/drone/tag Build was killed Details
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-06-28 18:36:33 +02:00
Martyn d534eb36cd Fix centering of plasma
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-06-28 18:33:42 +02:00
Martyn 688084094a Foolish failure
continuous-integration/drone/tag Build was killed Details
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-05-19 13:05:13 +02:00
Martyn fda7e29175 typo in Dockerfile
continuous-integration/drone/tag Build was killed Details
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-05-19 13:01:46 +02:00
Martyn a9e465f8b3 Fixup Dockerfile to new project structure
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-05-19 12:56:40 +02:00
Martyn 990e30eb2f Use the makefile for deps also for non-publish builds
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-05-19 12:55:26 +02:00
Martyn c853a07743 Use the makefile for deps
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-05-19 12:53:33 +02:00
Martyn 5f8e68662f Working plasma pattern
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-05-19 12:48:47 +02:00
Martyn 34bb137e7b Go fmt
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-05-19 12:48:24 +02:00
Martyn c41fca9c5a Fix the universes bug
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-05-19 12:47:56 +02:00
Martyn 209bebe51d refactor dynamic universes
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-05-19 10:45:42 +02:00
Martyn 3e04b69b97 More refactor plus start for the emulator
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-05-18 11:47:30 +02:00
13 changed files with 660 additions and 216 deletions

View File

@ -5,10 +5,17 @@ LDFLAGS=-ldflags "-X main.buildDate=${BUILD}"
.PHONY: build deps static
build:
go build ${LDFLAGS}
go build ${LDFLAGS} -o ledcontroller-server cmd/ledcontroller-server/main.go
emulator:
go build ${LDFLAGS} -o emulator cmd/emulator/main.go
deps:
go get
cd cmd/ledcontroller-server ; go get ; cd ../..
deps-emulator:
cd cmd/emulator ; go get ; cd ../..
# Static only makes sense for the server
static:
CGO_ENABLED=0 GOOS=linux go build ${LDFLAGS} -a -installsuffix cgo -o LEDController .
CGO_ENABLED=0 GOOS=linux go build ${LDFLAGS} -a -installsuffix cgo -o ledcontroller-server cmd/ledcontroller-server/main.go

View File

@ -1,3 +1,7 @@
# LEDController
Testing out sending e1.31 sACM packets to strings of ws2812b
Testing out sending e1.31 sACM packets to strings of ws2812b
Seems to be working nicely.
Used for my streaming setup on https://twitch.tv/iMartynOnTwitch

View File

@ -1,5 +1,5 @@
kind: pipeline
type: docker
type: kubernetes
name: linux-amd64-taggedver
platform:
@ -14,8 +14,8 @@ steps:
- mkdir -p /go/src/git.martyn.berlin/martyn
- ln -s /drone/src /go/src/git.martyn.berlin/martyn/LEDController
- cd /go/src/git.martyn.berlin/martyn/LEDController
- go get
- go build
- make deps
- make
- name: publish
image: plugins/docker:18
@ -39,7 +39,7 @@ trigger:
---
kind: pipeline
type: docker
type: kubernetes
name: linux-amd64-devel-master
platform:
@ -54,10 +54,10 @@ steps:
- mkdir -p /go/src/git.martyn.berlin/martyn
- ln -s /drone/src /go/src/git.martyn.berlin/martyn/LEDController
- cd /go/src/git.martyn.berlin/martyn/LEDController
- go get
- go build
- make deps
- make
trigger:
ref:
- refs/heads/devel
- refs/heads/master
- refs/heads/master

View File

@ -1,12 +1,12 @@
FROM golang@sha256:cee6f4b901543e8e3f20da3a4f7caac6ea643fd5a46201c3c2387183a332d989 AS builder
RUN apk update && apk add --no-cache git make ca-certificates && update-ca-certificates
COPY main.go /go/src/git.martyn.berlin/martyn/LEDController/
COPY cmd /go/src/git.martyn.berlin/martyn/LEDController/cmd/
COPY internal/ /go/src/git.martyn.berlin/martyn/LEDController/internal/
COPY Makefile /go/src/git.martyn.berlin/martyn/LEDController/
RUN cd /go/src/git.martyn.berlin/martyn/LEDController/; make deps ; make static
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /go/src/git.martyn.berlin/martyn/LEDController /app/
COPY --from=builder /go/src/git.martyn.berlin/martyn/LEDController/ledcontroller-server /app/
WORKDIR /app
CMD ["/app/LEDController"]
CMD ["/app/ledcontroller-server"]

48
cmd/emulator/main.go Executable file
View File

@ -0,0 +1,48 @@
package main
import (
"image/color"
"fyne.io/fyne"
"fyne.io/fyne/app"
"fyne.io/fyne/theme"
"fyne.io/fyne/widget"
fynewidget "git.martyn.berlin/martyn/LEDController/internal/fynewidget"
"log"
)
type tappableIcon struct {
widget.Icon
}
func newTappableIcon(res fyne.Resource) *tappableIcon {
icon := &tappableIcon{}
icon.ExtendBaseWidget(icon)
icon.SetResource(res)
return icon
}
func (t *tappableIcon) Tapped(_ *fyne.PointEvent) {
log.Println("I have been tapped")
}
func (t *tappableIcon) TappedSecondary(_ *fyne.PointEvent) {
}
func main() {
app := app.New()
w := app.NewWindow("Hello")
w.SetContent(widget.NewVBox(
widget.NewLabel("Hello Fyne!"),
newTappableIcon(theme.FyneLogo()),
fynewidget.NewOutputWidget(color.Black),
widget.NewButton("Quit", func() {
app.Quit()
}),
))
w.ShowAndRun()
}

219
cmd/ledcontroller-server/main.go Executable file
View File

@ -0,0 +1,219 @@
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()
}

View File

@ -35,7 +35,7 @@ spec:
- name: PANEL_BRIGHTNESS
value: "10"
image: imartyn/ledcontroller:0.0-linux-amd64
imagePullPolicy: IfNotPresent
imagePullPolicy: Always
name: ledcontroller
ports:
- name: web

View File

@ -0,0 +1,45 @@
package fynewidget
import (
"image/color"
"fyne.io/fyne/widget"
"fyne.io/fyne"
)
// OutputWidget describes a coloured rectangle primitive in a Fyne canvas
type OutputWidget struct {
widget.BaseWidget
CreateRenderer() WidgetRenderer
}
// NewOutputWidget returns a new OutputWidget instance
func NewOutputWidget(color color.Color) *OutputWidget {
return &OutputWidget{}
}
// MinSize of panel
func (l *OutputWidget) MinSize() fyne.Size {
return fyne.NewSize(320, 200)
}
// CreateRenderer gets the widget renderer
func (t *Table) CreateRenderer() fyne.WidgetRenderer {
return widget.new
}
/*
// NewOutputWidget Creates a new widget for outputting the patterns recieved over UDP
func NewOutputWidget() *fyne.Container {
theBox := NewOutputWidget(theme.PrimaryColor())
theBox.Resize(fyne.NewSize(320, 200))
c := fyne.NewContainer(theBox)
c.Resize(fyne.NewSize(320, 200))
return c
}
*/

138
internal/patterns/font.go Executable file
View File

@ -0,0 +1,138 @@
package patterns
var FontData = [126][]byte{{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00}, // 32 ' '
{0x5f}, // 33 '!'
{0x07, 0x00, 0x07}, // 34 '"'
{0x14, 0x3e, 0x14, 0x3e, 0x14}, // 35 '#'
{0x24, 0x4a, 0xcb, 0x52, 0x24}, // 36 '$'
{0x22, 0x15, 0x2a, 0x54, 0x22}, // 37 '%'
{0x36, 0x49, 0x55, 0x22, 0x50}, // 38 '&'
{0x07}, // 39 '''
{0x1c, 0x22, 0x41}, // 40 '('
{0x41, 0x22, 0x1c}, // 41 ')'
{0x14, 0x08, 0x3e, 0x08, 0x14}, // 42 '*'
{0x08, 0x08, 0x3e, 0x08, 0x08}, // 43 '+'
{0xc0, 0x60}, // 44 ','
{0x08, 0x08, 0x08}, // 45 '-'
{0x60, 0x60}, // 46 '.'
{0x20, 0x10, 0x08, 0x04, 0x02}, // 47 '/'
{0x3e, 0x51, 0x49, 0x45, 0x3e}, // 48 '0'
{0x42, 0x42, 0x7f, 0x40, 0x40}, // 49 '1'
{0x42, 0x61, 0x51, 0x49, 0x46}, // 50 '2'
{0x22, 0x41, 0x49, 0x49, 0x36}, // 51 '3'
{0x18, 0x14, 0x52, 0x7f, 0x50}, // 52 '4'
{0x27, 0x45, 0x45, 0x45, 0x39}, // 53 '5'
{0x3e, 0x45, 0x45, 0x45, 0x38}, // 54 '6'
{0x01, 0x01, 0x71, 0x09, 0x07}, // 55 '7'
{0x36, 0x49, 0x49, 0x49, 0x36}, // 56 '8'
{0x0e, 0x51, 0x51, 0x51, 0x3e}, // 57 '9'
{0x66, 0x66}, // 58 ':'
{0xc6, 0x66}, // 59 ';'
{0x08, 0x14, 0x22, 0x41}, // 60 '<'
{0x14, 0x14, 0x14, 0x14, 0x14}, // 61 '='
{0x41, 0x22, 0x14, 0x08}, // 62 '>'
{0x02, 0x01, 0x51, 0x09, 0x06}, // 63 '?'
{0x3e, 0x41, 0x5d, 0x55, 0x5e}, // 64 '@'
{0x7c, 0x12, 0x11, 0x12, 0x7c}, // 65 'A'
{0x7f, 0x49, 0x49, 0x49, 0x36}, // 66 'B'
{0x3e, 0x41, 0x41, 0x41, 0x22}, // 67 'C'
{0x7f, 0x41, 0x41, 0x41, 0x3e}, // 68 'D'
{0x7f, 0x49, 0x49, 0x49, 0x41}, // 69 'E'
{0x7f, 0x09, 0x09, 0x09, 0x01}, // 70 'F'
{0x3e, 0x41, 0x41, 0x49, 0x3a}, // 71 'G'
{0x7f, 0x08, 0x08, 0x08, 0x7f}, // 72 'H'
{0x41, 0x41, 0x7f, 0x41, 0x41}, // 73 'I'
{0x30, 0x40, 0x40, 0x40, 0x3f}, // 74 'J'
{0x7f, 0x08, 0x14, 0x22, 0x41}, // 75 'K'
{0x7f, 0x40, 0x40, 0x40, 0x40}, // 76 'L'
{0x7f, 0x02, 0x0c, 0x02, 0x7f}, // 77 'M'
{0x7f, 0x02, 0x04, 0x08, 0x7f}, // 78 'N'
{0x3e, 0x41, 0x41, 0x41, 0x3e}, // 79 'O'
{0x7f, 0x09, 0x09, 0x09, 0x06}, // 80 'P'
{0x3e, 0x41, 0x61, 0x41, 0xbe}, // 81 'Q'
{0x7f, 0x09, 0x09, 0x09, 0x76}, // 82 'R'
{0x26, 0x49, 0x49, 0x49, 0x32}, // 83 'S'
{0x01, 0x01, 0x7f, 0x01, 0x01}, // 84 'T'
{0x3f, 0x40, 0x40, 0x40, 0x3f}, // 85 'U'
{0x07, 0x18, 0x60, 0x18, 0x07}, // 86 'V'
{0x7f, 0x20, 0x18, 0x20, 0x7f}, // 87 'W'
{0x63, 0x14, 0x08, 0x14, 0x63}, // 88 'X'
{0x07, 0x08, 0x70, 0x08, 0x07}, // 89 'Y'
{0x61, 0x51, 0x49, 0x45, 0x43}, // 90 'Z'
{0x7f, 0x41}, // 91 '['
{0x02, 0x04, 0x08, 0x10, 0x20}, // 92 '\'
{0x41, 0x7f}, // 93 ']'
{0x04, 0x02, 0x01, 0x02, 0x04}, // 94 '^'
{0x80, 0x80, 0x80, 0x80, 0x80}, // 95 '_'
{0x01, 0x02}, // 96 '`'
{0x20, 0x54, 0x54, 0x78}, // 97 'a'
{0x7f, 0x44, 0x44, 0x38}, // 98 'b'
{0x38, 0x44, 0x44, 0x28}, // 99 'c'
{0x38, 0x44, 0x44, 0x7f}, // 100 'd'
{0x38, 0x54, 0x54, 0x58}, // 101 'e'
{0x08, 0x7e, 0x09, 0x02}, // 102 'f'
{0x18, 0xa4, 0xa4, 0x7c}, // 103 'g'
{0x7f, 0x04, 0x04, 0x78}, // 104 'h'
{0x44, 0x7d, 0x40}, // 105 'i'
{0x84, 0x7d}, // 106 'j'
{0x7f, 0x10, 0x28, 0x44}, // 107 'k'
{0x41, 0x7f, 0x40}, // 108 'l'
{0x7c, 0x04, 0x7c, 0x04, 0x78}, // 109 'm'
{0x7c, 0x08, 0x04, 0x78}, // 110 'n'
{0x38, 0x44, 0x44, 0x38}, // 111 'o'
{0xfc, 0x24, 0x24, 0x18}, // 112 'p'
{0x18, 0x24, 0x24, 0xfc}, // 113 'q'
{0x7c, 0x08, 0x04, 0x08}, // 114 'r'
{0x48, 0x54, 0x54, 0x24}, // 115 's'
{0x04, 0x3f, 0x44}, // 116 't'
{0x3c, 0x40, 0x20, 0x7c}, // 117 'u'
{0x3c, 0x40, 0x3c}, // 118 'v'
{0x3c, 0x40, 0x3c, 0x40, 0x3c}, // 119 'w'
{0x6c, 0x10, 0x6c}, // 120 'x'
{0x1c, 0xa0, 0xa0, 0x7c}, // 121 'y'
{0x64, 0x54, 0x4c}, // 122 'z'
{0x08, 0x36, 0x41}, // 123 '{'
{0x7f}, // 124 '|'
{0x41, 0x36, 0x08}, // 125 '}'
{0x08, 0x04, 0x08, 0x04}} // 126 '~'
func Letter(whichLetter rune) [][]RGBcolor {
w := 8
h := 8
grid := make([][]RGBcolor, w)
for i := 0; i < w; i++ {
grid[i] = make([]RGBcolor, h)
}
//index := int(whichLetter)
return grid
}

View File

@ -1,9 +1,15 @@
package patterns
import "math"
import (
"math"
)
type RGBcolor = [3]byte
func floatColorToIntColor(val float64) byte {
return byte(val*256 - 0.5)
}
func plasmaRGBFromVal(val byte) (byte, byte, byte) {
var r byte
var g byte
@ -24,7 +30,6 @@ func plasmaRGBFromVal(val byte) (byte, byte, byte) {
return r, g, b
}
func LinearPlasma(l int) []byte {
line := make([]byte, l*3)
for i := 0; i < l*3; i += 3 {
@ -37,26 +42,67 @@ func LinearPlasma(l int) []byte {
return line
}
var offset float64 = 0.5
func linearFromVal(val byte) (byte, byte, byte) {
var r byte = val
var g byte = 0
var b byte = 0
return r, g, b
}
func positiveModF(val float64) float64 {
_, val = math.Modf(val)
if val < 0 {
return val + 1.0
}
return val
}
func colorPlasmaFromFloatVal(val float64) (byte, byte, byte) {
var r byte
var g byte
var b byte
val = positiveModF(val) * 3.0
if val < 1.0 {
r = floatColorToIntColor(val)
g = floatColorToIntColor(1.0 - val)
b = 0
} else if val < 2.0 {
b = floatColorToIntColor(val - 1.0)
r = floatColorToIntColor(1.0 - (val - 1.0))
g = 0.0
} else {
g = floatColorToIntColor(val - 2.0)
b = floatColorToIntColor(1.0 - (val - 2.0))
r = 0.0
}
return r, g, b
}
var palletteOffset float64 = 0.0
func PlasmaPanel(w int, h int, speed uint16) [][]RGBcolor {
grid := make([][]RGBcolor, w)
for i := 0; i < w; i++ {
grid[i] = make([]RGBcolor, h)
}
scale := math.Pi * 2.0 / float64(w)
step := float64(5 * speed / 40)
offset -= step
if offset < 0 {
offset += 1.0
// pbrook didn't say what DT is for... so it resolves to 0.05
palletteOffset -= 0.05 / 1.0
if palletteOffset < 0 {
palletteOffset += 1.0
}
offset := 0.5 // center offset
scaleW := math.Pi * 2.0 / float64(w)
scaleH := math.Pi * 2.0 / float64(h)
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
u := math.Cos((float64(x) + offset) * scale)
v := math.Cos((float64(y) + offset) * scale)
w := math.Cos((float64(w) + offset) * scale)
e := (u + v + w + 3.0) / 6.0
r, g, b := plasmaRGBFromVal(byte((offset + e) * 255))
u := math.Cos((float64(x) + offset) * scaleW)
v := math.Cos((float64(y) + offset) * scaleH)
j := math.Cos(offset * scaleW) // 2D - No Z
e := (u + v + j + 3.0) / 6.0
r, g, b := colorPlasmaFromFloatVal(palletteOffset + e)
var rgb [3]byte
rgb[0] = r
rgb[1] = g
@ -65,4 +111,47 @@ func PlasmaPanel(w int, h int, speed uint16) [][]RGBcolor {
}
}
return grid
}
}
func singleColorPlasmaFromFloatVal(val float64, baseColor RGBcolor) (byte, byte, byte) {
var r byte
var g byte
var b byte
val = positiveModF(val)
r = byte(math.Trunc(float64(baseColor[0]) * val))
g = byte(math.Trunc(float64(baseColor[1]) * val))
b = byte(math.Trunc(float64(baseColor[2]) * val))
return r, g, b
}
func PlasmaPanelSingleColor(w int, h int, speed uint16, baseColor RGBcolor) [][]RGBcolor {
grid := make([][]RGBcolor, w)
for i := 0; i < w; i++ {
grid[i] = make([]RGBcolor, h)
}
palletteOffset -= 0.05 / 1.0
if palletteOffset < 0 {
palletteOffset += 1.0
}
offset := 0.5 // center offset
scaleW := math.Pi * 2.0 / float64(w)
scaleH := math.Pi * 2.0 / float64(h)
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
u := math.Cos((float64(x) + offset) * scaleW)
v := math.Cos((float64(y) + offset) * scaleH)
j := math.Cos(offset * scaleW) // 2D - No Z
e := (u + v + j + 3.0) / 6.0
r, g, b := singleColorPlasmaFromFloatVal(palletteOffset+e, baseColor)
var rgb [3]byte
rgb[0] = r
rgb[1] = g
rgb[2] = b
grid[x][y] = rgb
}
}
return grid
}

View File

@ -3,9 +3,9 @@ package queue
type RGBcolor = [3]byte
type QueueItem struct {
Effect string
Duration uint16
Speed uint16 //only used by some patterns
SeedColour RGBcolor // only used by some patterns
Effect string
Duration uint64
Speed uint16 //only used by some patterns
SeedColour RGBcolor // only used by some patterns
SecondColour RGBcolor // only used by some patterns
}
}

View File

@ -1,18 +1,17 @@
package webserver
import (
queue "git.martyn.berlin/martyn/LEDController/internal/queue"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
queue "git.martyn.berlin/martyn/LEDController/internal/queue"
"golang.org/x/image/colornames"
"fmt"
"net/http"
"os"
"time"
"strconv"
"strings"
"time"
)
func HealthHandler(response http.ResponseWriter, request *http.Request) {
@ -30,7 +29,8 @@ func RootHandler(response http.ResponseWriter, request *http.Request) {
fmt.Fprint(response, "Not implemented")
}
var globalQueue chan queue.QueueItem;
var globalQueue chan queue.QueueItem
var overrideFlag chan queue.QueueItem
func PatternHandler(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
@ -39,10 +39,14 @@ func PatternHandler(response http.ResponseWriter, request *http.Request) {
e.Effect = strings.ToLower(vars["pattern"])
_, found := vars["duration"]
if found {
i, _ := strconv.Atoi(vars["duration"])
e.Duration = uint16(i)
i, _ := strconv.ParseUint(vars["duration"], 10, 64)
e.Duration = i
} else {
e.Duration = 5000
if vars["override"] == "true" {
e.Duration = 0
} else {
e.Duration = 5000
}
}
_, found = vars["speed"]
if found {
@ -51,24 +55,44 @@ func PatternHandler(response http.ResponseWriter, request *http.Request) {
} else {
e.Speed = 40
}
globalQueue <- e
if vars["color"] != "" {
var c queue.RGBcolor
for cn, cv := range colornames.Map {
if cn == strings.ToLower(vars["color"]) {
c[0] = cv.R
c[1] = cv.G
c[2] = cv.B
}
}
e.SeedColour = c
}
if vars["override"] == "true" {
overrideFlag <- e
} else {
globalQueue <- e
}
fmt.Fprint(response, "OKAY")
}
func ColourHandler(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
fmt.Printf("Vars : %v\n", vars)
response.Header().Add("Content-type", "text/plain")
var e queue.QueueItem
e.Effect = "colour"
_, found := vars["duration"]
if found {
i, _ := strconv.Atoi(vars["duration"])
e.Duration = uint16(i)
i, _ := strconv.ParseUint(vars["duration"], 10, 64)
e.Duration = i
} else {
e.Duration = 5000
if vars["override"] == "true" {
e.Duration = 0
} else {
e.Duration = 5000
}
}
var c queue.RGBcolor
for cn, cv := range(colornames.Map) {
for cn, cv := range colornames.Map {
if cn == strings.ToLower(vars["name"]) {
c[0] = cv.R
c[1] = cv.G
@ -77,6 +101,11 @@ func ColourHandler(response http.ResponseWriter, request *http.Request) {
}
e.SeedColour = c
globalQueue <- e
if vars["override"] == "true" {
overrideFlag <- e
} else {
globalQueue <- e
}
fmt.Fprint(response, "OKAY")
}
@ -87,13 +116,17 @@ func FadeHandler(response http.ResponseWriter, request *http.Request) {
e.Effect = "fade"
_, found := vars["duration"]
if found {
i, _ := strconv.Atoi(vars["duration"])
e.Duration = uint16(i)
i, _ := strconv.ParseUint(vars["duration"], 10, 64)
e.Duration = i
} else {
e.Duration = 5000
if vars["override"] == "true" {
e.Duration = 0
} else {
e.Duration = 5000
}
}
var c queue.RGBcolor
for cn, cv := range(colornames.Map) {
for cn, cv := range colornames.Map {
if cn == strings.ToLower(vars["namefrom"]) {
c[0] = cv.R
c[1] = cv.G
@ -101,7 +134,7 @@ func FadeHandler(response http.ResponseWriter, request *http.Request) {
}
}
e.SeedColour = c
for cn, cv := range(colornames.Map) {
for cn, cv := range colornames.Map {
if cn == strings.ToLower(vars["nameto"]) {
c[0] = cv.R
c[1] = cv.G
@ -109,30 +142,52 @@ func FadeHandler(response http.ResponseWriter, request *http.Request) {
}
}
e.SecondColour = c
globalQueue <- e
if vars["override"] == "true" {
overrideFlag <- e
} else {
globalQueue <- e
}
fmt.Fprint(response, "OKAY")
}
func HandleHTTP(queueChannel chan queue.QueueItem, Port int) {
func ResumeHandler(response http.ResponseWriter, request *http.Request) {
response.Header().Add("Content-type", "text/plain")
var e queue.QueueItem
e.Effect = "queue"
overrideFlag <- e
fmt.Fprint(response, "OKAY")
}
func HandleHTTP(queueChannel chan queue.QueueItem, Port int, overrideChannel chan queue.QueueItem) {
globalQueue = queueChannel
overrideFlag = overrideChannel
r := mux.NewRouter()
loggedRouter := handlers.LoggingHandler(os.Stdout, r)
r.NotFoundHandler = http.HandlerFunc(NotFoundHandler)
r.HandleFunc("/", RootHandler)
r.HandleFunc("/healthz", HealthHandler)
r.HandleFunc("/pattern/{pattern}", PatternHandler)
r.HandleFunc("/pattern/{pattern}/override={override}", PatternHandler)
r.HandleFunc("/pattern/{pattern}/", PatternHandler)
r.HandleFunc("/pattern/{pattern}/{duration}", PatternHandler)
r.HandleFunc("/pattern/{pattern}/{duration}/{speed}", PatternHandler)
r.HandleFunc("/colorpattern/{color}/{pattern}/override={override}", PatternHandler)
r.HandleFunc("/colorpattern/{color}/{pattern}/", PatternHandler)
r.HandleFunc("/colorpattern/{color}/{pattern}/{duration}", PatternHandler)
r.HandleFunc("/colorpattern/{color}/{pattern}/{duration}/{speed}", PatternHandler)
r.HandleFunc("/colour/{name}", ColourHandler)
r.HandleFunc("/colour/{name}/override={override}", ColourHandler)
r.HandleFunc("/colour/{name}/{duration}", ColourHandler)
r.HandleFunc("/color/{name}", ColourHandler)
r.HandleFunc("/color/{name}/override={override}", ColourHandler)
r.HandleFunc("/color/{name}/{duration}", ColourHandler)
r.HandleFunc("/fade/{namefrom}/{nameto}", FadeHandler)
r.HandleFunc("/fade/{namefrom}/{nameto}/override={override}", FadeHandler)
r.HandleFunc("/fade/{namefrom}/{nameto}/{duration}", FadeHandler)
r.HandleFunc("/resumequeue", ResumeHandler)
http.Handle("/", r)
srv := &http.Server{
Handler: loggedRouter,
Addr: "0.0.0.0:"+strconv.Itoa(Port),
Addr: "0.0.0.0:" + strconv.Itoa(Port),
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}

161
main.go
View File

@ -1,161 +0,0 @@
package main
import (
"fmt"
"log"
"time"
"strconv"
"os"
webserver "git.martyn.berlin/martyn/LEDController/internal/webserver"
queue "git.martyn.berlin/martyn/LEDController/internal/queue"
remapping "git.martyn.berlin/martyn/LEDController/internal/remapping"
patterns "git.martyn.berlin/martyn/LEDController/internal/patterns"
"github.com/Hundemeier/go-sacn/sacn"
)
type RGBcolor = [3]byte
var currentFrame [2][512]byte
var channels [2]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)
func foreverLoop() {
for /*ever*/ {
time.Sleep(40 * time.Millisecond) //25fps
for u := 0; u < 2; 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)
}()
PanelIP := os.Getenv("PANEL_IP")
if PanelIP == "" {
PanelIP = "127.0.0.1"
}
PanelWidth, err := strconv.Atoi(os.Getenv("PANEL_WIDTH"))
if err != nil {
PanelWidth = 68
}
PanelHeight, err := strconv.Atoi(os.Getenv("PANEL_HEIGHT"))
if err != nil {
PanelHeight = 4
}
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 < 2; 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 := remapping.SliceRearrange(PanelWidth, PanelHeight, true, remapping.XYGridToLinear(PanelWidth, PanelHeight, patterns.PlasmaPanel(PanelWidth, PanelHeight, 40)))
currentFrame[0] = remapping.Slice512(rearranged[0])
currentFrame[1] = remapping.Slice512(rearranged[1])
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 = "default"
globalEffectChannel <- e
go func() {
for /*ever*/ {
if currentEffect.Duration > 0 {
currentEffect.Duration -= 40
} else {
if len(globalEffectChannel) > 0 {
currentEffect = <- globalEffectChannel
}
}
var rearranged [][]byte
switch currentEffect.Effect {
case "line":
rearranged = remapping.SliceRearrange(PanelWidth, PanelHeight, true, remapping.XYGridToLinear(PanelWidth, PanelHeight, patterns.ZigZag(PanelWidth, PanelHeight)))
case "plasma":
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
currentFrame[0] = reduceBrightness(remapping.Slice512(rearranged[0]),PanelBrightness)
currentFrame[1] = reduceBrightness(remapping.Slice512(rearranged[1]),PanelBrightness)
<- sema
time.Sleep(40 * time.Millisecond)
}
}()
foreverLoop();
}