diff --git a/.hgignore b/.hgignore deleted file mode 100644 index 774d4b4..0000000 --- a/.hgignore +++ /dev/null @@ -1,2 +0,0 @@ -./main -.*~ diff --git a/effects/effects.go b/effects/effects.go deleted file mode 100644 index 7e0f7c2..0000000 --- a/effects/effects.go +++ /dev/null @@ -1,558 +0,0 @@ -package effects - -import ( - "fmt" - pixarray "github.com/Jon-Bright/ledctl/pixarray" - "log" - "math" - "time" -) - -type Effect interface { - Start(pa *pixarray.PixArray, now time.Time) - NextStep(pa *pixarray.PixArray, now time.Time) time.Duration - Name() string -} - -func abs(i int) int { - if i >= 0 { - return i - } - return -i -} - -func round(f float64) int { - if f < 0 { - return int(f - 0.5) - } - return int(f + 0.5) -} - -func maxP(p pixarray.Pixel) int { - if p.R < p.G { - if p.G < p.B { - if p.B < p.W { - return p.W - } - return p.B - } - if p.G < p.W { - return p.W - } - return p.G - } - if p.R < p.B { - if p.B < p.W { - return p.W - } - return p.B - } - if p.R < p.W { - return p.W - } - return p.R -} - -func max(a, b, c float64) float64 { - if a < b { - if b < c { - return c - } - return b - } - if a < c { - return c - } - return a -} - -func min(a, b, c float64) float64 { - if a > b { - if b > c { - return c - } - return b - } - if a > c { - return c - } - return a -} - -func lcm(p pixarray.Pixel) int { - // TODO: no white support - if p.R == 0 { - p.R = 1 - } - if p.G == 0 { - p.G = 1 - } - if p.B == 0 { - p.B = 1 - } - m := p.R * p.G - for p.G != 0 { - t := p.R % p.G - p.R = p.G - p.G = t - } - p.G = m / p.R - m = p.G * p.B - for p.B != 0 { - t := p.G % p.B - p.G = p.B - p.B = t - } - return m / p.G -} - -type Fade struct { - fadeTime time.Duration - dest pixarray.Pixel - startPix []pixarray.Pixel - diffs []pixarray.Pixel - allSame bool - timeStep time.Duration - start time.Time -} - -func NewFade(fadeTime time.Duration, dest pixarray.Pixel) *Fade { - f := Fade{} - f.fadeTime = fadeTime - f.dest = dest - return &f -} - -func (f *Fade) Start(pa *pixarray.PixArray, now time.Time) { - log.Printf("Starting Fade, dest %v", f.dest) - var lastp pixarray.Pixel - f.allSame = true - f.startPix = pa.GetPixels() - f.diffs = make([]pixarray.Pixel, pa.NumPixels()) - var maxdiff pixarray.Pixel - for i, v := range f.startPix { - f.diffs[i].R = f.dest.R - v.R - f.diffs[i].G = f.dest.G - v.G - f.diffs[i].B = f.dest.B - v.B - f.diffs[i].W = f.dest.W - v.W - if abs(f.diffs[i].R) > maxdiff.R { - maxdiff.R = abs(f.diffs[i].R) - } - if abs(f.diffs[i].G) > maxdiff.G { - maxdiff.G = abs(f.diffs[i].G) - } - if abs(f.diffs[i].B) > maxdiff.B { - maxdiff.B = abs(f.diffs[i].B) - } - if abs(f.diffs[i].W) > maxdiff.W { - maxdiff.W = abs(f.diffs[i].W) - } - if i > 0 && lastp != v { - f.allSame = false - } - lastp = v - } - if maxdiff.R == 0 && maxdiff.G == 0 && maxdiff.B == 0 && maxdiff.W == 0 { - f.timeStep = f.fadeTime - } else { - nsStep := f.fadeTime.Nanoseconds() / int64(lcm(maxdiff)) - if f.allSame { - log.Printf("Starting all-same") - nsStep = nsStep / int64(pa.NumPixels()) - } - f.timeStep = time.Duration(nsStep) - } - log.Printf("Fade md.R %d, md.G %d, md.B %d, md.W %d, timestep %v", maxdiff.R, maxdiff.G, maxdiff.B, maxdiff.W, f.timeStep) - f.start = now -} - -func (f *Fade) NextStep(pa *pixarray.PixArray, now time.Time) time.Duration { - td := now.Sub(f.start) - pct := float64(td.Nanoseconds()) / float64(f.fadeTime.Nanoseconds()) - if pct >= 1.0 { - // We're done - pa.SetAll(f.dest) - return 0 - } - if f.allSame { - var this, next pixarray.Pixel - var trp, tgp, tbp, twp float64 - var nrp, ngp, nbp, nwp float64 - this.R = int(f.startPix[0].R) + int(float64(f.diffs[0].R)*pct) - this.G = int(f.startPix[0].G) + int(float64(f.diffs[0].G)*pct) - this.B = int(f.startPix[0].B) + int(float64(f.diffs[0].B)*pct) - this.W = int(f.startPix[0].W) + int(float64(f.diffs[0].W)*pct) - if f.diffs[0].R != 0 { - trp = float64(this.R-f.startPix[0].R) / float64(f.diffs[0].R) - } else { - trp = 0.0 - } - if f.diffs[0].G != 0 { - tgp = float64(this.G-f.startPix[0].G) / float64(f.diffs[0].G) - } else { - tgp = 0.0 - } - if f.diffs[0].B != 0 { - tbp = float64(this.B-f.startPix[0].B) / float64(f.diffs[0].B) - } else { - tbp = 0.0 - } - if f.diffs[0].W != 0 { - twp = float64(this.W-f.startPix[0].W) / float64(f.diffs[0].W) - } else { - twp = 0.0 - } - if f.diffs[0].R > 0 { - nrp = float64(this.R+1-f.startPix[0].R) / float64(f.diffs[0].R) - } else if f.diffs[0].R < 0 { - nrp = float64(this.R-1-f.startPix[0].R) / float64(f.diffs[0].R) - } else { - nrp = 1.1 - } - if f.diffs[0].G > 0 { - ngp = float64(this.G+1-f.startPix[0].G) / float64(f.diffs[0].G) - } else if f.diffs[0].G < 0 { - ngp = float64(this.G-1-f.startPix[0].G) / float64(f.diffs[0].G) - } else { - ngp = 1.1 - } - if f.diffs[0].B > 0 { - nbp = float64(this.B+1-f.startPix[0].B) / float64(f.diffs[0].B) - } else if f.diffs[0].B < 0 { - nbp = float64(this.B-1-f.startPix[0].B) / float64(f.diffs[0].B) - } else { - nbp = 1.1 - } - if f.diffs[0].W > 0 { - nwp = float64(this.W+1-f.startPix[0].W) / float64(f.diffs[0].W) - } else if f.diffs[0].W < 0 { - nwp = float64(this.W-1-f.startPix[0].W) / float64(f.diffs[0].W) - } else { - nwp = 1.1 - } - if nrp+ngp+nbp+nwp > 4.35 { - log.Printf("Weird, no diffs for r,g,b,w") - return f.timeStep - } - pctThroughStepR := (pct - trp) / (nrp - trp) - pctThroughStepG := (pct - tgp) / (ngp - tgp) - pctThroughStepB := (pct - tbp) / (nbp - tbp) - pctThroughStepW := (pct - twp) / (nwp - twp) - next = this - if f.diffs[0].R != 0 { - next.R = next.R + (f.diffs[0].R / abs(f.diffs[0].R)) - } - if f.diffs[0].G != 0 { - next.G = next.G + (f.diffs[0].G / abs(f.diffs[0].G)) - } - if f.diffs[0].B != 0 { - next.B = next.B + (f.diffs[0].B / abs(f.diffs[0].B)) - } - if f.diffs[0].W != 0 { - next.W = next.W + (f.diffs[0].W / abs(f.diffs[0].W)) - } - np := float64(pa.NumPixels()) - num := pixarray.Pixel{ - int(np * pctThroughStepR), - int(np * pctThroughStepG), - int(np * pctThroughStepB), - int(np * pctThroughStepW), - } - pa.SetPerChanAlternate(num, pa.NumPixels(), this, next) - return f.timeStep - } - - var p pixarray.Pixel - var lastp pixarray.Pixel - for i, v := range f.startPix { - p.R = int(v.R) + int(float64(f.diffs[i].R)*pct) - p.G = int(v.G) + int(float64(f.diffs[i].G)*pct) - p.B = int(v.B) + int(float64(f.diffs[i].B)*pct) - p.W = int(v.W) + int(float64(f.diffs[i].W)*pct) - pa.SetOne(i, p) - if i == 0 { - // Our first time through, we have no last pixel - f.allSame = true - } else if lastp != p { - f.allSame = false - } - lastp = p - } - if f.allSame { - log.Printf("Setting all-same") - f.timeStep = time.Duration(int64(f.timeStep) / int64(pa.NumPixels())) - } - return f.timeStep -} - -func (f *Fade) Name() string { - return "FADE" -} - -type Rainbow struct { - cycleTime time.Duration - start time.Time -} - -func NewRainbow(cycleTime time.Duration) *Rainbow { - r := Rainbow{} - r.cycleTime = cycleTime - return &r -} - -func (r *Rainbow) Start(pa *pixarray.PixArray, now time.Time) { - log.Printf("Starting Rainbow") - r.start = now -} - -func fToPix(f float64, o float64) int { - f -= o - if f < 0.0 { - f += 1.0 - } - if f < 0.166667 { - return 127 - } - if f < 0.333334 { - return 127 - round(127*((f-0.166667)/0.166667)) - } - if f > 0.833333 { - return round(127 * ((f - 0.833333) / 0.166667)) - } - return 0 -} - -func (r *Rainbow) NextStep(pa *pixarray.PixArray, now time.Time) time.Duration { - pos := float64(now.Sub(r.start).Nanoseconds()) / float64(r.cycleTime.Nanoseconds()) - pos -= math.Floor(pos) - offs := round(float64(pa.NumPixels()) * pos) - - for i := 0; i < pa.NumPixels(); i++ { - var p pixarray.Pixel - f := float64(i) / float64(pa.NumPixels()) - p.R = fToPix(f, 0.0) - p.G = fToPix(f, 0.333334) - p.B = fToPix(f, 0.666667) - pa.SetOne((i+offs)%pa.NumPixels(), p) - } - return r.cycleTime / time.Duration(768) -} - -func (r *Rainbow) Name() string { - return "RAINBOW" -} - -// A full cycle goes across 128*6=768 steps: R->R+G->G->G+B->B->B+R->R with each of the six arrows -// representing the 128 increments between 127 and 0 inclusive of the relevant increase or decrease. -type Cycle struct { - cycleTime time.Duration - fadeTime time.Duration - start time.Time - last pixarray.Pixel - fade *Fade -} - -func NewCycle(cycleTime time.Duration) *Cycle { - c := Cycle{} - c.cycleTime = cycleTime - c.fadeTime = cycleTime / time.Duration(768) - return &c -} - -func (c *Cycle) Start(pa *pixarray.PixArray, now time.Time) { - log.Printf("Starting Cycle") - c.start = now - p := pa.GetPixel(0) - c.last = p - m := maxP(c.last) - switch m { - case 0: - // Black, let's fade to red - log.Printf("Black->Red") - c.last.R = 127 - case c.last.R: - // Red max - c.last.R = 127 - if c.last.G > c.last.B { - log.Printf("Red->Red+Green") - c.last.B = 0 - } else { - log.Printf("Blue+Red->Red") - c.last.G = 0 - } - case c.last.G: - // Green max - c.last.G = 127 - if c.last.B > c.last.R { - log.Printf("Green->Green+Blue") - c.last.R = 0 - } else { - log.Printf("Red+Green->Green") - c.last.B = 0 - } - case c.last.B: - // Blue max - c.last.B = 127 - if c.last.G > c.last.R { - log.Printf("Green+Blue->Blue") - c.last.R = 0 - } else { - log.Printf("Blue->Blue+Red") - c.last.G = 0 - } - default: - panic("One of the three colours must ==m") - } - - if c.last != p { - p.R -= c.last.R - p.G -= c.last.G - p.B -= c.last.B - p.R = abs(p.R) - p.G = abs(p.G) - p.B = abs(p.B) - m = maxP(p) - t := c.fadeTime * time.Duration(m) - log.Printf("First fade to %v, max dist %d -> time %s", c.last, m, t) - c.fade = NewFade(t, c.last) - c.fade.Start(pa, now) - } else { - log.Printf("Already in-cycle, no initial fade needed") - c.NextStep(pa, now) - } -} - -func (c *Cycle) NextStep(pa *pixarray.PixArray, now time.Time) time.Duration { - if c.fade != nil { - t := c.fade.NextStep(pa, now) - if t != 0 { - // This fade will continue - return t - } - } - // Time for a new fade - if c.last.R == 127 { - if c.last.B > 0 { - c.last.B-- - } else if c.last.G == 127 { - c.last.R-- - } else { - c.last.G++ - } - } else if c.last.G == 127 { - if c.last.R > 0 { - c.last.R-- - } else if c.last.B == 127 { - c.last.G-- - } else { - c.last.B++ - } - } else if c.last.B == 127 { - if c.last.G > 0 { - c.last.G-- - } else if c.last.R == 127 { - c.last.B-- - } else { - c.last.R++ - } - } else { - panic(fmt.Sprintf("Broken colour %v", c.last)) - } - c.fade = NewFade(c.fadeTime, c.last) - c.fade.Start(pa, now) - return c.cycleTime / time.Duration(768*pa.NumPixels()) -} - -func (f *Cycle) Name() string { - return "CYCLE" -} - -type Zip struct { - zipTime time.Duration - dest pixarray.Pixel - start time.Time - lastSet int -} - -func NewZip(zipTime time.Duration, dest pixarray.Pixel) *Zip { - z := Zip{} - z.zipTime = zipTime - z.dest = dest - z.lastSet = -1 - return &z -} - -func (z *Zip) Start(pa *pixarray.PixArray, now time.Time) { - log.Printf("Starting Zip") - z.start = now -} - -func (z *Zip) NextStep(pa *pixarray.PixArray, now time.Time) time.Duration { - p := int((float64(now.Sub(z.start).Nanoseconds()) / float64(z.zipTime.Nanoseconds())) * float64(pa.NumPixels())) - for i := z.lastSet + 1; i < pa.NumPixels() && i <= p; i++ { - pa.SetOne(i, z.dest) - } - if p >= pa.NumPixels() { - return 0 - } - return time.Duration(z.zipTime.Nanoseconds() / int64(pa.NumPixels())) -} - -func (f *Zip) Name() string { - return "ZIP" -} - -type KnightRider struct { - pulseTime time.Duration - pulseLen int - start time.Time -} - -func NewKnightRider(pulseTime time.Duration, pulseLen int) *KnightRider { - kr := KnightRider{} - kr.pulseTime = pulseTime - kr.pulseLen = pulseLen - return &kr -} - -func (kr *KnightRider) Start(pa *pixarray.PixArray, now time.Time) { - log.Printf("Starting KnightRider") - kr.start = now - pa.SetAll(pixarray.Pixel{0, 0, 0, 0}) -} - -func (kr *KnightRider) NextStep(pa *pixarray.PixArray, now time.Time) time.Duration { - pulse := now.Sub(kr.start).Nanoseconds() / kr.pulseTime.Nanoseconds() - pulseProgress := float64(now.Sub(kr.start).Nanoseconds()-(pulse*kr.pulseTime.Nanoseconds())) / float64(kr.pulseTime.Nanoseconds()) - pulseHead := int(float64(pa.NumPixels()+kr.pulseLen) * pulseProgress) - pulseDir := 0 - if pulse%2 == 0 { - pulseDir = 1 - } else { - pulseDir = -1 - pulseHead = pa.NumPixels() - pulseHead - } - pulseTail := pulseHead + (pulseDir * kr.pulseLen * -1) - if pulseTail < 0 { - pulseTail = 0 - } else if pulseTail >= pa.NumPixels() { - pulseTail = pa.NumPixels() - 1 - } - rangeHead := 0 - if pulseHead < 0 { - rangeHead = 0 - } else if pulseHead >= pa.NumPixels() { - rangeHead = pa.NumPixels() - 1 - } else { - rangeHead = pulseHead - } - for i := pulseTail; i != rangeHead; i = i + pulseDir { - v := int((float64(kr.pulseLen-abs(pulseHead-i))/float64(kr.pulseLen))*126.0) + 1 - pa.SetOne(i, pixarray.Pixel{v, 0, 0, 0}) - } - return time.Millisecond -} - -func (f *KnightRider) Name() string { - return "KNIGHTRIDER" -} diff --git a/effects/effects_test.go b/effects/effects_test.go deleted file mode 100644 index 04cc09c..0000000 --- a/effects/effects_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package effects - -import ( - pixarray "github.com/Jon-Bright/ledctl/pixarray" - "math" - "testing" - "time" -) - -type testLeds struct { - pixels []pixarray.Pixel -} - -func (l *testLeds) GetPixel(i int) pixarray.Pixel { - return l.pixels[i] -} - -func (l *testLeds) SetPixel(i int, p pixarray.Pixel) { - l.pixels[i] = p -} - -func (l *testLeds) Write() error { - return nil -} - -func (l *testLeds) MaxPerChannel() int { - return 160 -} - -func newTestLeds(numPixels int) pixarray.LEDStrip { - return &testLeds{make([]pixarray.Pixel, numPixels)} -} - -func d(s string, tb testing.TB) time.Duration { - d, err := time.ParseDuration(s) - if err != nil { - tb.Fatalf("Couldn't parse duration %s: %v", s, err) - } - return d -} - -func TestAllSameFade(t *testing.T) { - pa := pixarray.NewPixArray(100, 4, newTestLeds(100)) - - tests := []struct { - start pixarray.Pixel - dest pixarray.Pixel - fadeLen time.Duration - len time.Duration - r float64 - g float64 - b float64 - w float64 - }{ - {pixarray.Pixel{0, 0, 0, 0}, pixarray.Pixel{127, 0, 0, 0}, d("1.0s", t), d("0.5s", t), 63.5, 0, 0, 0}, - {pixarray.Pixel{0, 127, 0, 0}, pixarray.Pixel{127, 0, 0, 0}, d("1.0s", t), d("0.5s", t), 63.5, 63.5, 0, 0}, - {pixarray.Pixel{127, 127, 127, 127}, pixarray.Pixel{127, 0, 127, 0}, d("3.0s", t), d("1.0s", t), 127, 84.66666, 127, 84.66666}, - {pixarray.Pixel{127, 127, 127, 127}, pixarray.Pixel{127, 0, 127, 0}, d("3.0s", t), d("2.0s", t), 127, 42.33333, 127, 42.33333}, - {pixarray.Pixel{127, 127, 127, 127}, pixarray.Pixel{0, 0, 0, 0}, d("127.0s", t), d("10.5s", t), 116.5, 116.5, 116.5, 116.5}, - {pixarray.Pixel{127, 127, 0, 0}, pixarray.Pixel{0, 0, 127, 127}, d("127.0s", t), d("10.5s", t), 116.5, 116.5, 10.5, 10.5}, - {pixarray.Pixel{126, 126, 0, 0}, pixarray.Pixel{0, 63, 126, 63}, d("126.0s", t), d("10.5s", t), 115.5, 120.75, 10.5, 5.25}, - {pixarray.Pixel{0, 0, 0, 0}, pixarray.Pixel{120, 10, 0, 5}, d("120.0s", t), d("6.0s", t), 6.0, 0.5, 0, 0.25}, - } - - tm := time.Now() - for _, test := range tests { - pa.SetAll(test.start) - f := NewFade(test.fadeLen, test.dest) - f.Start(pa, tm) - tm = tm.Add(test.len) - f.NextStep(pa, tm) - py := pa.GetPixels() - totR := 0 - totG := 0 - totB := 0 - totW := 0 - rc := int(math.Ceil(test.r)) - rf := int(math.Floor(test.r)) - gc := int(math.Ceil(test.g)) - gf := int(math.Floor(test.g)) - bc := int(math.Ceil(test.b)) - bf := int(math.Floor(test.b)) - wc := int(math.Ceil(test.w)) - wf := int(math.Floor(test.w)) - for i, p := range py { - totR += p.R - totG += p.G - totB += p.B - totW += p.W - if p.R != rc && p.R != rf { - t.Errorf("Wrong red at pixel %d, want %d/%d, got %d", i, rc, rf, p.R) - } - if p.G != gc && p.G != gf { - t.Errorf("Wrong green at pixel %d, want %d/%d, got %d", i, gc, gf, p.G) - } - if p.B != bc && p.B != bf { - t.Errorf("Wrong blue at pixel %d, want %d/%d, got %d", i, bc, bf, p.B) - } - if p.W != wc && p.W != wf { - t.Errorf("Wrong white at pixel %d, want %d/%d, got %d", i, wc, wf, p.W) - } - } - dR := float64(totR) / float64(len(py)) - if math.Abs(dR-test.r) > 0.01 { - t.Errorf("Wrong average red, want %f, got %f", test.r, dR) - } - dG := float64(totG) / float64(len(py)) - if math.Abs(dG-test.g) > 0.01 { - t.Errorf("Wrong average green, want %f, got %f", test.g, dG) - } - dB := float64(totB) / float64(len(py)) - if math.Abs(dB-test.b) > 0.01 { - t.Errorf("Wrong average blue, want %f, got %f", test.b, dB) - } - dW := float64(totW) / float64(len(py)) - if math.Abs(dW-test.w) > 0.01 { - t.Errorf("Wrong average white, want %f, got %f", test.w, dW) - } - } -} - -func BenchmarkFadeStep(b *testing.B) { - pa := pixarray.NewPixArray(100, 4, newTestLeds(100)) - pa.SetAll(pixarray.Pixel{127, 0, 0, 0}) - tm := time.Now() - add := time.Duration((7200 * time.Second).Nanoseconds() / int64(b.N)) - if add == 0 { - b.Fatalf("Zero delay") - } - f := NewFade(d("7200.0s", b), pixarray.Pixel{0, 127, 0, 0}) - f.Start(pa, tm) - for i := 0; i < b.N; i++ { - tm = tm.Add(add) - f.NextStep(pa, tm) - } -} diff --git a/go.mod b/go.mod index 130a4f3..7af233d 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,7 @@ -module github.com/Jon-Bright/ledctl +module github.com/ckzdev/ledctl -go 1.11 +go 1.18 -require ( - github.com/edsrzf/mmap-go v1.0.0 - golang.org/x/sys v0.0.0-20201231184435-2d18734c6014 // indirect -) +require github.com/edsrzf/mmap-go v1.1.0 + +require golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect diff --git a/go.sum b/go.sum index 28342d8..6dfc93e 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,4 @@ -github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -golang.org/x/sys v0.0.0-20201231184435-2d18734c6014 h1:joucsQqXmyBVxViHCPFjG3hx8JzIFSaym3l3MM/Jsdg= -golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= +github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pixarray/ledstrip.go b/pixarray/ledstrip.go index 4813cfc..444d589 100644 --- a/pixarray/ledstrip.go +++ b/pixarray/ledstrip.go @@ -1,7 +1,7 @@ package pixarray import ( - rpi "github.com/Jon-Bright/ledctl/rpi" + "github.com/ckzdev/ledctl/rpi" ) type LEDStrip interface { diff --git a/pixarray/lpd8806.go b/pixarray/lpd8806.go index fab7737..0cb1a42 100644 --- a/pixarray/lpd8806.go +++ b/pixarray/lpd8806.go @@ -2,7 +2,7 @@ package pixarray import ( "fmt" - rpi "github.com/Jon-Bright/ledctl/rpi" + "github.com/ckzdev/ledctl/rpi" ) type LPD8806 struct { diff --git a/pixarray/pixarray.go b/pixarray/pixarray.go index c4a839d..1083456 100644 --- a/pixarray/pixarray.go +++ b/pixarray/pixarray.go @@ -2,7 +2,7 @@ package pixarray import ( "fmt" - rpi "github.com/Jon-Bright/ledctl/rpi" + "github.com/ckzdev/ledctl/rpi" ) const ( @@ -12,24 +12,27 @@ const ( GBR RGB RBG + GRBW ) var StringOrders map[string]int = map[string]int{ - "GRB": GRB, - "BRG": BRG, - "BGR": BGR, - "GBR": GBR, - "RGB": RGB, - "RBG": RBG, + "GRB": GRB, + "BRG": BRG, + "BGR": BGR, + "GBR": GBR, + "RGB": RGB, + "RBG": RBG, + "GRBW": GRBW, } var offsets map[int][]int = map[int][]int{ - GRB: {0, 1, 2, -1}, - BRG: {2, 1, 0, -1}, - BGR: {1, 2, 0, -1}, - GBR: {0, 2, 1, -1}, - RGB: {1, 0, 2, -1}, - RBG: {2, 0, 1, -1}, + GRB: {0, 1, 2, -1}, + BRG: {2, 1, 0, -1}, + BGR: {1, 2, 0, -1}, + GBR: {0, 2, 1, -1}, + RGB: {1, 0, 2, -1}, + RBG: {2, 0, 1, -1}, + GRBW: {0, 1, 2, 3}, } func abs(i int) int { diff --git a/pixarray/ws281x.go b/pixarray/ws281x.go index c916494..81ea8aa 100644 --- a/pixarray/ws281x.go +++ b/pixarray/ws281x.go @@ -2,7 +2,7 @@ package pixarray import ( "fmt" - rpi "github.com/Jon-Bright/ledctl/rpi" + "github.com/ckzdev/ledctl/rpi" ) type WS281x struct { diff --git a/power.go b/power.go deleted file mode 100644 index 4c0b35a..0000000 --- a/power.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "flag" - "fmt" - rpi "github.com/Jon-Bright/ledctl/rpi" - "log" - "time" -) - -var powerCtrlPin = flag.Int("powerCtrlPin", -1, "A GPIO pin which, when set high, turns on power for the LEDs. -1 means no such pin exists.") -var powerStatusPin = flag.Int("powerStatusPin", -1, "A GPIO pin which indicates healthy power to the LEDs. -1 means no such pin exists. Only relevant if powerCtrlPin is specified.") -var powerStatusWait = flag.Duration("powerStatusWait", 2*time.Second, "How long to wait for a healthy power signal. Only relevant if powerStatusPin is specified and relevant.") - -func initPower(rp *rpi.RPi) error { - if *powerCtrlPin < 0 { - return nil - } - err := rp.GPIOSetOutput(*powerCtrlPin, rpi.PullNone) - if err != nil { - return fmt.Errorf("couldn't set power control to output: %v", err) - } - if *powerStatusPin < 0 { - return nil - } - return rp.GPIOSetInput(*powerStatusPin) -} - -func powerOn(rp *rpi.RPi) error { - if *powerCtrlPin < 0 { - return nil - } - log.Printf("Power on") - err := rp.GPIOSetPin(*powerCtrlPin, true) - if err != nil { - return fmt.Errorf("couldn't set power control high: %v", err) - } - if *powerStatusPin < 0 { - return nil - } - start := time.Now() - for { - val, err := rp.GPIOGetPin(*powerStatusPin) - if err != nil { - return fmt.Errorf("couldn't query power status: %v", err) - } - t := time.Now() - if val { - log.Printf("Power stablized after %v", t.Sub(start)) - return nil - } - if t.Sub(start) > *powerStatusWait { - return fmt.Errorf("timed out waiting for power to be healthy, started %v, now %v", start, t) - } - time.Sleep(50 * time.Millisecond) // No point overdoing it - we're not in _that_ much of a rush - } -} - -func powerOff(rp *rpi.RPi) error { - if *powerCtrlPin < 0 { - return nil - } - log.Printf("Power off") - err := rp.GPIOSetPin(*powerCtrlPin, false) - if err != nil { - return fmt.Errorf("couldn't set power control low: %v", err) - } - // We could wait for power status to go low, but that might take a while and doesn't seem to provide any benefit - return nil -} diff --git a/rpi/dma.go b/rpi/dma.go index bfb724c..2f46360 100644 --- a/rpi/dma.go +++ b/rpi/dma.go @@ -2,7 +2,6 @@ package rpi import ( "fmt" - "log" "time" "unsafe" ) @@ -86,7 +85,7 @@ func (rp *RPi) GetDMABuf(bytes uint) (*DMABuf, error) { return nil, fmt.Errorf("couldn't get %d byte phyical buffer for DMA: %v", bytes, err) } d.c = (*dmaControl)(unsafe.Pointer(&d.pb.buf[d.pb.offs])) - log.Printf("dmabuf size %d, calc %d, addr %08X\n", bytes, calcDMABufSize(bytes), uintptr(unsafe.Pointer(d.c))) + //log.Printf("dmabuf size %d, calc %d, addr %08X\n", bytes, calcDMABufSize(bytes), uintptr(unsafe.Pointer(d.c))) return &d, nil } @@ -121,7 +120,7 @@ func (rp *RPi) InitDMA(dma int) error { if err != nil { return fmt.Errorf("couldn't map dmaT at %08X: %v", offset, err) } - log.Printf("Got dmaBuf[%d], offset %d\n", len(rp.dmaBuf), bufOffs) + //log.Printf("Got dmaBuf[%d], offset %d\n", len(rp.dmaBuf), bufOffs) rp.dma = (*dmaT)(unsafe.Pointer(&rp.dmaBuf[bufOffs])) return nil } diff --git a/rpi/gpio.go b/rpi/gpio.go index 4b583f9..c902cd7 100644 --- a/rpi/gpio.go +++ b/rpi/gpio.go @@ -2,7 +2,6 @@ package rpi import ( "fmt" - "log" "time" "unsafe" ) @@ -123,7 +122,7 @@ func (rp *RPi) InitGPIO() error { if err != nil { return fmt.Errorf("couldn't map gpioT at %08X: %v", GPIO_OFFSET+rp.hw.periphBase, err) } - log.Printf("Got gpioBuf[%d], offset %d\n", len(rp.gpioBuf), bufOffs) + //log.Printf("Got gpioBuf[%d], offset %d\n", len(rp.gpioBuf), bufOffs) rp.gpio = (*gpioT)(unsafe.Pointer(&rp.gpioBuf[bufOffs])) return nil } diff --git a/rpi/mbox.go b/rpi/mbox.go index d5d537f..4730c3b 100644 --- a/rpi/mbox.go +++ b/rpi/mbox.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" mmap "github.com/edsrzf/mmap-go" - "log" "os" "path" "reflect" @@ -91,7 +90,7 @@ func (rp *RPi) getPhysBuf(size uint32) (*PhysBuf, error) { rp.freeVCMem(pb.handle) // Ignore error return nil, fmt.Errorf("couldn't map busAddr(%X) of size %v: %v", pb.busAddr, size, err) } - log.Printf("mapped %d bytes, busaddr %08X, offset %d\n", size, pb.busAddr, pb.offs) + //log.Printf("mapped %d bytes, busaddr %08X, offset %d\n", size, pb.busAddr, pb.offs) return &pb, nil } @@ -113,7 +112,7 @@ func (rp *RPi) mapMem(physAddr uintptr, size int) (mmap.MMap, uintptr, error) { pagemask := ^uintptr(PAGE_SIZE - 1) mapAddr := physAddr & pagemask size += int(physAddr - mapAddr) - log.Printf("MapRegion(f, %d, RDWR, 0, %08X), physAddr %08X, mask %08X\n", size, int64(mapAddr), physAddr, pagemask) + //log.Printf("MapRegion(f, %d, RDWR, 0, %08X), physAddr %08X, mask %08X\n", size, int64(mapAddr), physAddr, pagemask) mm, err := mmap.MapRegion(f, size, mmap.RDWR, 0, int64(mapAddr)) if err != nil { return nil, 0, fmt.Errorf("couldn't map region (%v, %v): %v", physAddr, size, err) diff --git a/rpi/pwm.go b/rpi/pwm.go index da0b864..8881206 100644 --- a/rpi/pwm.go +++ b/rpi/pwm.go @@ -2,7 +2,6 @@ package rpi import ( "fmt" - "log" "time" "unsafe" ) @@ -86,7 +85,7 @@ func (rp *RPi) InitPWM(freq uint, buf *DMABuf, bytes uint, pins []int) error { if err != nil { return fmt.Errorf("couldn't map pwmT at %08X: %v", PWM_OFFSET+rp.hw.periphBase, err) } - log.Printf("Got pwmBuf[%d], offset %d\n", len(rp.pwmBuf), bufOffs) + //log.Printf("Got pwmBuf[%d], offset %d\n", len(rp.pwmBuf), bufOffs) rp.pwm = (*pwmT)(unsafe.Pointer(&rp.pwmBuf[bufOffs])) // This could potentially be in a clk.go. Seems not worth it yet, though. @@ -94,7 +93,7 @@ func (rp *RPi) InitPWM(freq uint, buf *DMABuf, bytes uint, pins []int) error { if err != nil { return fmt.Errorf("couldn't map cmClkT at %08X: %v", CM_PWM_OFFSET+rp.hw.periphBase, err) } - log.Printf("Got cmClkBuf[%d], offset %d\n", len(rp.cmClkBuf), bufOffs) + //log.Printf("Got cmClkBuf[%d], offset %d\n", len(rp.cmClkBuf), bufOffs) rp.cmClk = (*cmClkT)(unsafe.Pointer(&rp.cmClkBuf[bufOffs])) } @@ -105,12 +104,12 @@ func (rp *RPi) InitPWM(freq uint, buf *DMABuf, bytes uint, pins []int) error { rp.cmClk.ctl = CM_CLK_CTL_PASSWD | CM_CLK_CTL_SRC_OSC rp.cmClk.ctl = CM_CLK_CTL_PASSWD | CM_CLK_CTL_SRC_OSC | CM_CLK_CTL_ENAB time.Sleep(10 * time.Microsecond) - log.Printf("Waiting for cmClk busy\n") + //log.Printf("Waiting for cmClk busy\n") i := 0 for (rp.cmClk.ctl & CM_CLK_CTL_BUSY) == 0 { i++ } - log.Printf("Done %d\n", i) + //log.Printf("Done %d\n", i) // Set up the PWM, use delays as the block is rumored to lock up without them. Make // sure to use a high enough priority to avoid any FIFO underruns, especially if @@ -136,11 +135,11 @@ func (rp *RPi) InitPWM(freq uint, buf *DMABuf, bytes uint, pins []int) error { RPI_DMA_TI_SRC_INC // Increment src addr buf.c.sourceAd = uint32(buf.pb.busAddr + unsafe.Sizeof(dmaControl{})) - log.Printf("DMA sourceAd %08X\n", buf.c.sourceAd) + //log.Printf("DMA sourceAd %08X\n", buf.c.sourceAd) buf.c.destAd = PWM_PERIPH_PHYS + uint32(unsafe.Offsetof(rp.pwm.fif1)) buf.c.txLen = uint32(bytes) - log.Printf("DMA txLen %d\n", buf.c.txLen) + //log.Printf("DMA txLen %d\n", buf.c.txLen) buf.c.stride = 0 buf.c.nextconbk = 0 @@ -157,10 +156,10 @@ func (rp *RPi) StopPWM() { // Kill the clock if it was already running rp.cmClk.ctl = CM_CLK_CTL_PASSWD | CM_CLK_CTL_KILL time.Sleep(10 * time.Microsecond) - log.Printf("Waiting for cmClk not-busy\n") + //log.Printf("Waiting for cmClk not-busy\n") i := 0 for (rp.cmClk.ctl & CM_CLK_CTL_BUSY) != 0 { i++ } - log.Printf("Done %d\n", i) + //log.Printf("Done %d\n", i) } diff --git a/rpi/rpi.go b/rpi/rpi.go index 662a599..dfde0ee 100644 --- a/rpi/rpi.go +++ b/rpi/rpi.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/binary" "fmt" - mmap "github.com/edsrzf/mmap-go" + "github.com/edsrzf/mmap-go" "os" ) @@ -456,4 +456,19 @@ var rasPiVariants = map[uint32]hw{ vcBase: VIDEOCORE_BASE_RPI2, name: "Compute Module 3+", }, + // + // Pi Compute Module 4 + // + 0xC03140: { + hwType: RPI_HWVER_TYPE_PI4, + periphBase: PERIPH_BASE_RPI4, + vcBase: VIDEOCORE_BASE_RPI2, + name: "Compute Module 4 - 4GB v1.0", + }, + 0xB03141: { + hwType: RPI_HWVER_TYPE_PI4, + periphBase: PERIPH_BASE_RPI4, + vcBase: VIDEOCORE_BASE_RPI2, + name: "Compute Module 4 - 4GB v1.0", + }, } diff --git a/serve.go b/serve.go deleted file mode 100644 index b314b05..0000000 --- a/serve.go +++ /dev/null @@ -1,323 +0,0 @@ -package main - -import ( - "bufio" - "flag" - "fmt" - effects "github.com/Jon-Bright/ledctl/effects" - pixarray "github.com/Jon-Bright/ledctl/pixarray" - "io" - "log" - "net" - "os" - "strings" - "time" -) - -var lpd8806Dev = flag.String("dev", "/dev/spidev0.0", "The SPI device on which LPD8806 LEDs are connected") -var lpd8806SpiSpeed = flag.Uint("spispeed", 1000000, "The speed to send data via SPI to LPD8806s, in Hz") -var ws281xFreq = flag.Uint("ws281xfreq", 800000, "The frequency to send data to WS2801x devices, in Hz") -var ws281xDma = flag.Int("ws281xdma", 10, "The DMA channel to use for sending data to WS281x devices") -var ws281xPin0 = flag.Int("ws281xpin0", 18, "The pin on which channel 0 should be output for WS281x devices") -var ws281xPin1 = flag.Int("ws281xpin1", 13, "The pin on which channel 1 should be output for WS281x devices") -var ledChip = flag.String("ledchip", "ws281x", "The type of LED strip to drive: one of ws281x, lpd8806") -var port = flag.Int("port", 24601, "The port that the server should listen to") -var pixels = flag.Int("pixels", 5*32, "The number of pixels to be controlled") -var pixelOrder = flag.String("order", "GRB", "The color ordering of the pixels") - -type Server struct { - pa *pixarray.PixArray - l net.Listener - c chan effects.Effect - laste effects.Effect - off bool - running bool -} - -func NewServer(port int, pa *pixarray.PixArray) (*Server, error) { - - l, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) - if err != nil { - return nil, err - } - c := make(chan effects.Effect) - log.Printf("Listening on port %d", port) - return &Server{pa, l, c, nil, true, false}, nil -} - -func parseDuration(parms string) (string, time.Duration, error) { - t := strings.SplitN(parms, " ", 2) - d, err := time.ParseDuration(t[0] + "s") - if err != nil { - return "", 0, err - } - if len(t) == 1 { - return "", d, nil - } - return t[1], d, nil -} - -func (s *Server) parseColor(parms string) (string, *pixarray.Pixel, error) { - t := strings.SplitN(parms, " ", 2) - var p pixarray.Pixel - n, err := fmt.Sscanf(t[0], "%02X%02X%02X%02X", &p.R, &p.G, &p.B, &p.W) - if err != nil && err != io.EOF { - return "", nil, err - } - if n != s.pa.NumColors() { - return "", nil, fmt.Errorf("only %d tokens parsed from '%s', wanted %d", n, t[0], s.pa.NumColors()) - } - max := s.pa.MaxPerChannel() - if p.R > max || p.G > max || p.B > max || p.W > max { - return "", nil, fmt.Errorf("invalid color: one or more of %d, %d, %d, %d is >%d, parsed from %s", p.R, p.G, p.B, p.W, max, t[0]) - } - if len(t) == 1 { - return "", &p, nil - } - return t[1], &p, nil -} - -func (s *Server) createEffect(cmd, parms string, w *bufio.Writer) (effects.Effect, error) { - switch { - case cmd == "FADE_ALL": - parms, p, err := s.parseColor(parms) - if err != nil { - return nil, fmt.Errorf("error parsing color: %v", err) - } - _, d, err := parseDuration(parms) - if err != nil { - return nil, fmt.Errorf("error parsing duration: %v", err) - } - return effects.NewFade(d, *p), nil - case cmd == "ZIP_SET_ALL": - parms, p, err := s.parseColor(parms) - if err != nil { - return nil, fmt.Errorf("error parsing color: %v", err) - } - _, d, err := parseDuration(parms) - if err != nil { - return nil, fmt.Errorf("error parsing duration: %v", err) - } - return effects.NewZip(d, *p), nil - case cmd == "CYCLE": - _, d, err := parseDuration(parms) - if err != nil { - return nil, fmt.Errorf("error parsing duration: %v", err) - } - return effects.NewCycle(d), nil - case cmd == "RAINBOW": - _, d, err := parseDuration(parms) - if err != nil { - return nil, fmt.Errorf("error parsing duration: %v", err) - } - return effects.NewRainbow(d), nil - case cmd == "GET": - for _, p := range s.pa.GetPixels() { - if p.R != 0 || p.G != 0 || p.B != 0 { - w.WriteString("1\n") - err := w.Flush() - return nil, err - } - } - w.WriteString("0\n") - err := w.Flush() - return nil, err - case cmd == "COLOUR" || cmd == "COLOR": - p := s.pa.GetPixels()[0] - c := p.String() + "\n" - log.Printf("Returning %s", c) - w.WriteString(c) - err := w.Flush() - return nil, err - case cmd == "MODE": - n := "CONST" - if s.off { - n = "OFF" - } else if s.running { - if s.laste == nil { - return nil, fmt.Errorf("s running, but laste nil!") - } - n = s.laste.Name() - } - log.Printf("Mode '%s'", n) - if parms == "" { - log.Printf("Returning %s", n) - w.WriteString(n + "\n") - err := w.Flush() - return nil, err - } - r := "0\n" - if parms == n { - r = "1\n" - } - log.Printf("Returning %s", r) - w.WriteString(r) - err := w.Flush() - return nil, err - case cmd == "ON": - return s.laste, nil - case cmd == "OFF": - // Hack: we insert this directly into the channel because we don't want to overwrite whatever the last effect was - fb := effects.NewFade(20*time.Second, pixarray.Pixel{R: 0, G: 0, B: 0, W: 0}) - s.off = true - s.c <- fb - return nil, nil - case cmd == "KNIGHTRIDER": - _, d, err := parseDuration(parms) - if err != nil { - return nil, fmt.Errorf("error parsing duration: %v", err) - } - return effects.NewKnightRider(d, s.pa.NumPixels()/4), nil - } - return nil, fmt.Errorf("unknown command: %s", cmd) -} - -func (s *Server) runEffects() { - var laste, e effects.Effect - var d time.Duration - var steps int - var start time.Time - for { - if d == 0 { - e = <-s.c - } else { - select { - case e = <-s.c: - break - case <-time.After(d): - break - } - } - if e == nil { - log.Fatalf("Ready to process effect, but no effect!") - } - if e != laste { - err := powerOn(s.pa.RPi()) - if err != nil { - log.Fatalf("Failed power-on: %v", err) - } - start = time.Now() - e.Start(s.pa, start) - s.running = true - steps = 0 - } - d = e.NextStep(s.pa, time.Now()) - steps++ - s.pa.Write() - if d == 0 { - d := time.Since(start) - ps := time.Duration(d.Nanoseconds() / int64(steps)) - log.Printf("Finished effect, %d steps, %s total, %s/step", steps, d, ps) - laste = nil - e = nil - s.running = false - p := s.pa.GetPixels()[0] - log.Printf("Seeing post-effect pix %v", p) - if p.R <= 0 && p.G <= 0 && p.B <= 0 && p.W <= 0 { - err := powerOff(s.pa.RPi()) - if err != nil { - log.Fatalf("Failed power-off: %v", err) - } - } - } else { - laste = e - } - } -} - -func (s *Server) handleConnection(c net.Conn) { - log.Printf("Handling connection from %v", c.RemoteAddr()) - defer c.Close() - r := bufio.NewReader(c) - w := bufio.NewWriter(c) - for { - l, err := r.ReadString('\n') - if err == io.EOF { - log.Printf("EOF for connection %v", c.RemoteAddr()) - return - } - if err != nil { - log.Printf("Error reading string for connection %v: %v", c.RemoteAddr(), err) - return - } - l = strings.TrimSpace(l) - log.Printf("Got line '%s'", l) - t := strings.SplitN(l, " ", 2) - cmd := strings.ToUpper(t[0]) - parms := "" - if len(t) > 1 { - parms = t[1] - } - if cmd == "QUIT" { - return - } - e, err := s.createEffect(cmd, parms, w) - if err != nil { - es := fmt.Sprintf("Error creating effect: %v", err) - log.Print(es) - w.WriteString("ERR: " + es + "\n") - err = w.Flush() - if err != nil { - log.Printf("error writing error reply: %v", err) - } - return - } - if e != nil { - // Some commands don't result in a new Effect, e.g. status - // those commands write their own reply. - w.WriteString("OK\n") - err = w.Flush() - if err != nil { - log.Printf("error writing reply: %v", err) - } - s.c <- e - s.laste = e - s.off = false - } - } -} - -func (s *Server) handleConnections() { - for { - conn, err := s.l.Accept() - if err != nil { - log.Printf("Error accepting connection: %v", err) - continue - } - go s.handleConnection(conn) - } -} - -func main() { - flag.Parse() - order := pixarray.StringOrders[*pixelOrder] - var leds pixarray.LEDStrip - var err error - switch *ledChip { - case "lpd8806": - dev, err := os.OpenFile(*lpd8806Dev, os.O_RDWR, os.ModePerm) - if err != nil { - log.Fatalf("Failed opening SPI: %v", err) - } - leds, err = pixarray.NewLPD8806(dev, *pixels, 3, uint32(*lpd8806SpiSpeed), order) - if err != nil { - log.Fatalf("Failed creating LPD8806: %v", err) - } - case "ws281x": - leds, err = pixarray.NewWS281x(*pixels, 3, order, *ws281xFreq, *ws281xDma, []int{*ws281xPin0, *ws281xPin1}) - if err != nil { - log.Fatalf("Failed creating WS281x: %v", err) - } - default: - log.Fatalf("Unrecognized LED type: %v", *ledChip) - } - pa := pixarray.NewPixArray(*pixels, 3, leds) // TODO: White - - s, err := NewServer(*port, pa) - if err != nil { - log.Fatalf("Failed creating server: %v", err) - } - - go s.runEffects() - s.handleConnections() -}