From 0f9300c79346b14e67647eb4a8154290997dd54b Mon Sep 17 00:00:00 2001 From: Marc-Antoine Ruel Date: Sun, 4 Nov 2018 18:31:14 -0500 Subject: [PATCH] gpioutil: move to experimental/conn/gpio/gpioutil This mirrors the end location. --- experimental/devices/gpioutil/debounce.go | 90 --------- .../devices/gpioutil/debounce_test.go | 180 ------------------ experimental/devices/gpioutil/doc.go | 6 - experimental/devices/gpioutil/example_test.go | 91 --------- experimental/devices/gpioutil/polledge.go | 117 ------------ .../devices/gpioutil/polledge_test.go | 159 ---------------- 6 files changed, 643 deletions(-) delete mode 100644 experimental/devices/gpioutil/debounce.go delete mode 100644 experimental/devices/gpioutil/debounce_test.go delete mode 100644 experimental/devices/gpioutil/doc.go delete mode 100644 experimental/devices/gpioutil/example_test.go delete mode 100644 experimental/devices/gpioutil/polledge.go delete mode 100644 experimental/devices/gpioutil/polledge_test.go diff --git a/experimental/devices/gpioutil/debounce.go b/experimental/devices/gpioutil/debounce.go deleted file mode 100644 index e00aaa7..0000000 --- a/experimental/devices/gpioutil/debounce.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2018 The Periph Authors. All rights reserved. -// Use of this source code is governed under the Apache License, Version 2.0 -// that can be found in the LICENSE file. - -package gpioutil - -import ( - "time" - - "periph.io/x/periph/conn/gpio" -) - -// debounced is a gpio.PinIO where reading and edge detection pass through a -// debouncing algorithm. -type debounced struct { - // Immutable. - gpio.PinIO - // denoise delays state changes. It waits for this amount before reporting it. - denoise time.Duration - // debounce locks on after a steady state change. Once a state change - // happened, don't change again for this amount of time. - debounce time.Duration - - // Mutable. -} - -// Debounce returns a debounced gpio.PinIO from a gpio.PinIO source. Only the -// PinIn behavior is mutated. -// -// denoise is a noise filter, which waits a pin to be steady for this amount -// of time BEFORE reporting the new level. -// -// debounce will lock on a level for this amount of time AFTER the pin changed -// state, ignoring following state changes. -// -// Either value can be 0. -func Debounce(p gpio.PinIO, denoise, debounce time.Duration, edge gpio.Edge) (gpio.PinIO, error) { - if denoise == 0 && debounce == 0 { - return p, nil - } - if err := p.In(gpio.PullNoChange, gpio.BothEdges); err != nil { - return nil, err - } - return &debounced{ - // Immutable. - PinIO: p, - denoise: denoise, - debounce: debounce, - // Mutable. - }, nil -} - -// In implements gpio.PinIO. -func (d *debounced) In(pull gpio.Pull, edge gpio.Edge) error { - err := d.PinIO.In(pull, gpio.BothEdges) - return err -} - -// Read implements gpio.PinIO. -// -// It is the smoothed out value from the underlying gpio.PinIO. -func (d *debounced) Read() gpio.Level { - return d.PinIO.Read() -} - -// WaitForEdge implements gpio.PinIO. -// -// It is the smoothed out value from the underlying gpio.PinIO. -func (d *debounced) WaitForEdge(timeout time.Duration) bool { - if !d.PinIO.WaitForEdge(timeout) { - return false - } - return true -} - -// Halt implements gpio.PinIO. -func (d *debounced) Halt() error { - return nil -} - -// Real implements gpio.RealPin. -func (d *debounced) Real() gpio.PinIO { - if r, ok := d.PinIO.(gpio.RealPin); ok { - return r.Real() - } - return d.PinIO -} - -var now = time.Now -var _ gpio.PinIO = &debounced{} diff --git a/experimental/devices/gpioutil/debounce_test.go b/experimental/devices/gpioutil/debounce_test.go deleted file mode 100644 index 75a18ae..0000000 --- a/experimental/devices/gpioutil/debounce_test.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2018 The Periph Authors. All rights reserved. -// Use of this source code is governed under the Apache License, Version 2.0 -// that can be found in the LICENSE file. - -package gpioutil - -import ( - "testing" - "time" - - "periph.io/x/periph/conn/gpio" - "periph.io/x/periph/conn/gpio/gpiotest" -) - -func TestDebounce_Err(t *testing.T) { - defer mocktime(t, nil)() - f := gpiotest.Pin{} - if _, err := Debounce(&f, time.Second, 0, gpio.BothEdges); err == nil { - t.Fatal("expected error") - } -} - -func TestDebounce_Zero(t *testing.T) { - defer mocktime(t, nil)() - f := gpiotest.Pin{} - p, err := Debounce(&f, 0, 0, gpio.BothEdges) - if err != nil { - t.Fatal("expected error") - } - if p1, ok := p.(*gpiotest.Pin); !ok || p1 != &f { - t.Fatal("expected the pin to be returned as-is") - } -} - -func TestDebounce_In(t *testing.T) { - defer mocktime(t, nil)() - f := gpiotest.Pin{EdgesChan: make(chan gpio.Level)} - p, err := Debounce(&f, time.Second, 0, gpio.BothEdges) - if err != nil { - t.Fatal(err) - } - if err := p.In(gpio.PullNoChange, gpio.BothEdges); err != nil { - t.Fatal(err) - } - if p.Halt() != nil { - t.Fatal(err) - } -} - -func TestDebounce_Read_Low(t *testing.T) { - defer mocktime(t, nil)() - f := gpiotest.Pin{EdgesChan: make(chan gpio.Level)} - p, err := Debounce(&f, time.Second, time.Second, gpio.BothEdges) - if err != nil { - t.Fatal(err) - } - if p.Read() != gpio.Low { - t.Fatal("expected level") - } - if p.Read() != gpio.Low { - t.Fatal("expected level") - } -} - -func TestDebounce_Read_High(t *testing.T) { - defer mocktime(t, nil)() - f := gpiotest.Pin{L: gpio.High, EdgesChan: make(chan gpio.Level)} - p, err := Debounce(&f, time.Second, time.Second, gpio.BothEdges) - if err != nil { - t.Fatal(err) - } - if p.Read() != gpio.High { - t.Fatal("expected level") - } - if p.Read() != gpio.High { - t.Fatal("expected level") - } -} - -func TestDebounce_WaitForEdge_Got(t *testing.T) { - offsets := []time.Duration{} - defer mocktime(t, offsets)() - f := gpiotest.Pin{EdgesChan: make(chan gpio.Level, 1)} - p, err := Debounce(&f, time.Second, 0, gpio.BothEdges) - if err != nil { - t.Fatal(err) - } - f.EdgesChan <- gpio.Low - if !p.WaitForEdge(-1) { - t.Fatal("expected edge") - } -} - -func TestDebounce_WaitForEdge_Timeout(t *testing.T) { - offsets := []time.Duration{} - defer mocktime(t, offsets)() - f := gpiotest.Pin{EdgesChan: make(chan gpio.Level)} - p, err := Debounce(&f, time.Second, 0, gpio.BothEdges) - if err != nil { - t.Fatal(err) - } - if p.WaitForEdge(0) { - t.Fatal("expected no edge") - } -} - -func TestDebounce_RealPin(t *testing.T) { - defer mocktime(t, []time.Duration{})() - f := gpiotest.Pin{EdgesChan: make(chan gpio.Level)} - p, err := Debounce(&f, time.Second, 0, gpio.BothEdges) - if err != nil { - t.Fatal(err) - } - r, ok := p.(gpio.RealPin) - if !ok { - t.Fatal("expected gpio.RealPin") - } - a, ok := r.Real().(*gpiotest.Pin) - if !ok { - t.Fatal("expected gpiotest.Pin") - } - if a != &f { - t.Fatal("expected actual pin") - } -} - -func TestDebounce_RealPin_Deep(t *testing.T) { - defer mocktime(t, []time.Duration{})() - f := gpiotest.Pin{EdgesChan: make(chan gpio.Level)} - p, err := Debounce(&f, time.Second, 0, gpio.BothEdges) - if err != nil { - t.Fatal(err) - } - p, err = Debounce(p, time.Second, 0, gpio.BothEdges) - if err != nil { - t.Fatal(err) - } - r, ok := p.(gpio.RealPin) - if !ok { - t.Fatal("expected gpio.RealPin") - } - a, ok := r.Real().(*gpiotest.Pin) - if !ok { - t.Fatal("expected gpiotest.Pin") - } - if a != &f { - t.Fatal("expected actual pin") - } -} - -// - -func init() { - resetNow() -} - -func resetNow() { - now = func() time.Time { - panic("unexpected call") - } -} - -func mocktime(t *testing.T, offsets []time.Duration) func() { - offset := 0 - d := time.Date(2000, 01, 01, 0, 0, 0, 0, time.UTC) - now = func() time.Time { - if offset == len(offsets) { - t.Fatal("need one more offset") - } - v := d.Add(offsets[offset]) - offset++ - return v - } - return func() { - resetNow() - if offset != len(offsets) { - t.Fatalf("expected to consume all time mocks; used %d, expectd %d", offset, len(offsets)) - } - } -} diff --git a/experimental/devices/gpioutil/doc.go b/experimental/devices/gpioutil/doc.go deleted file mode 100644 index be12872..0000000 --- a/experimental/devices/gpioutil/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2018 The Periph Authors. All rights reserved. -// Use of this source code is governed under the Apache License, Version 2.0 -// that can be found in the LICENSE file. - -// Package gpioutil includes utilities to filter or augment GPIOs. -package gpioutil diff --git a/experimental/devices/gpioutil/example_test.go b/experimental/devices/gpioutil/example_test.go deleted file mode 100644 index ecf6804..0000000 --- a/experimental/devices/gpioutil/example_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2018 The Periph Authors. All rights reserved. -// Use of this source code is governed under the Apache License, Version 2.0 -// that can be found in the LICENSE file. - -package gpioutil_test - -import ( - "fmt" - "log" - "time" - - "periph.io/x/periph/conn/gpio" - "periph.io/x/periph/conn/gpio/gpioreg" - "periph.io/x/periph/conn/physic" - "periph.io/x/periph/experimental/devices/gpioutil" -) - -func ExampleDebounce() { - p := gpioreg.ByName("GPIO16") - if p != nil { - log.Fatal("please open another GPIO") - } - - // Ignore glitches lasting less than 3ms, and ignore repeated edges within - // 30ms. - d, err := gpioutil.Debounce(p, 3*time.Millisecond, 30*time.Millisecond, gpio.BothEdges) - if err != nil { - log.Fatal(err) - } - - defer d.Halt() - for { - if d.WaitForEdge(-1) { - fmt.Println(d.Read()) - } - } -} - -func ExamplePollEdge() { - // Flow when it is known that the GPIO does not support edge detection. - p := gpioreg.ByName("XOI-P1") - if p != nil { - log.Fatal("please open another GPIO") - } - p = gpioutil.PollEdge(p, 20*physic.Hertz) - if err := p.In(gpio.PullDown, gpio.RisingEdge); err != nil { - log.Fatal(err) - } - - defer p.Halt() - for { - if p.WaitForEdge(-1) { - fmt.Println(p.Read()) - } - } -} - -func Example() { - // Complete solution: - // - Fallback to software polling if the GPIO doesn't support hardware edge - // detection. - // - Denoise and debounce the reading. - // - // Order is important, as Debounce() requires working edge detection. - p := gpioreg.ByName("XOI-P1") - if p != nil { - log.Fatal("please open another GPIO") - } - if err := p.In(gpio.PullDown, gpio.BothEdges); err == nil { - // Try to fallback into software polling, then reinitialize. - p = gpioutil.PollEdge(p, 50*physic.Hertz) - if err = p.In(gpio.PullDown, gpio.BothEdges); err != nil { - log.Fatal(err) - } - } - - // Ignore glitches lasting less than 10ms, and ignore repeated edges within - // 30ms. Make sure to not use denoiser period lower than the software poller - // frequency. - d, err := gpioutil.Debounce(p, 10*time.Millisecond, 30*time.Millisecond, gpio.BothEdges) - if err != nil { - log.Fatal(err) - } - - defer d.Halt() - for { - if d.WaitForEdge(-1) { - fmt.Println(d.Read()) - } - } -} diff --git a/experimental/devices/gpioutil/polledge.go b/experimental/devices/gpioutil/polledge.go deleted file mode 100644 index 7090ae0..0000000 --- a/experimental/devices/gpioutil/polledge.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2018 The Periph Authors. All rights reserved. -// Use of this source code is governed under the Apache License, Version 2.0 -// that can be found in the LICENSE file. - -package gpioutil - -import ( - "time" - - "periph.io/x/periph/conn/gpio" - "periph.io/x/periph/conn/physic" -) - -// pollEdge is a gpio.PinIO where edge detection is done manually. -type pollEdge struct { - // Immutable. - gpio.PinIO - // period is the delay between each poll. - period time.Duration - die chan struct{} - - // Mutable. - // edge is the current edge detection. - edge gpio.Edge -} - -// PollEdge returns a gpio.PinIO which implements edge detection via polling. -// -// Example of GPIOs without edge detection are GPIOs accessible over an I²C -// chip or over USB. -// -// freq must be above 0. A reasonable value is 20Hz reading. High rate -// essentially means a busy loop. -func PollEdge(p gpio.PinIO, freq physic.Frequency) gpio.PinIO { - return &pollEdge{PinIO: p, period: freq.Duration(), die: make(chan struct{}, 1)} -} - -// In implements gpio.PinIO. -func (p *pollEdge) In(pull gpio.Pull, edge gpio.Edge) error { - p.edge = gpio.NoEdge - err := p.PinIO.In(pull, gpio.NoEdge) - if err == nil { - p.edge = edge - } - return err -} - -// WaitForEdge implements gpio.PinIO. -func (p *pollEdge) WaitForEdge(timeout time.Duration) bool { - select { - case <-p.die: - default: - } - defer func() { - select { - case <-p.die: - default: - } - }() - curr := p.PinIO.Read() - // -1 means to wait indefinitely. - if timeout >= 0 { - defer time.AfterFunc(timeout, func() { - p.die <- struct{}{} - }).Stop() - } - // Sadly it's not possible to stop then restart a ticker, so we can't cache - // it in the object. - t := time.NewTicker(p.period) - defer t.Stop() - for { - select { - case <-t.C: - n := p.PinIO.Read() - if n != curr { - switch p.edge { - case gpio.RisingEdge: - if n == gpio.High { - return true - } - curr = n - case gpio.FallingEdge: - if n == gpio.Low { - return true - } - curr = n - case gpio.BothEdges: - return true - } - } - case <-p.die: - return false - } - } -} - -// Halt implements gpio.PinIO. -// -// It unblocks any WaitForEdge loop. -func (p *pollEdge) Halt() error { - select { - // If a WaitForEdge was pending, it will be unblocked. - case p.die <- struct{}{}: - default: - } - return nil -} - -// Real implements gpio.RealPin. -func (p *pollEdge) Real() gpio.PinIO { - if r, ok := p.PinIO.(gpio.RealPin); ok { - return r.Real() - } - return p.PinIO -} - -var _ gpio.PinIO = &pollEdge{} diff --git a/experimental/devices/gpioutil/polledge_test.go b/experimental/devices/gpioutil/polledge_test.go deleted file mode 100644 index 1c63220..0000000 --- a/experimental/devices/gpioutil/polledge_test.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2018 The Periph Authors. All rights reserved. -// Use of this source code is governed under the Apache License, Version 2.0 -// that can be found in the LICENSE file. - -package gpioutil - -import ( - "sync" - "testing" - "time" - - "periph.io/x/periph/conn/gpio" - "periph.io/x/periph/conn/gpio/gpiotest" - "periph.io/x/periph/conn/physic" -) - -func TestAssumption(t *testing.T) { - f := gpiotest.Pin{} - if f.In(gpio.PullNoChange, gpio.BothEdges) == nil { - t.Fatal("Using gpiotest.Pin in no edge support mode") - } - if PollEdge(&f, 20*physic.Hertz) == nil { - t.Fatal("expected error") - } -} - -func TestPollEdge_Short(t *testing.T) { - p := PollEdge(&gpiotest.Pin{}, physic.Hertz) - if err := p.In(gpio.PullNoChange, gpio.BothEdges); err != nil { - t.Fatal(err) - } - if err := p.Halt(); err != nil { - t.Fatal(err) - } - // timeout triggers. - if p.WaitForEdge(time.Nanosecond) { - t.Fatal("unexpected edge") - } -} - -func TestPollEdge_Halt(t *testing.T) { - f := pinWait{wait: make(chan struct{})} - p := PollEdge(&f, physic.Hertz) - go func() { - // Make sure the pin was read at least once, which means the code below is - // inside WaitForEdge(). - <-f.wait - if err := p.Halt(); err != nil { - t.Fatal(err) - } - }() - // p.die triggers. - if p.WaitForEdge(-1) { - t.Fatal("unexpected edge") - } -} - -func TestPollEdge_RisingEdge(t *testing.T) { - f := pinLevels{levels: []gpio.Level{gpio.High, gpio.Low, gpio.High}} - p := PollEdge(&f, physic.KiloHertz) - if err := p.In(gpio.PullNoChange, gpio.RisingEdge); err != nil { - t.Fatal(err) - } - if !p.WaitForEdge(-1) { - t.Fatal("expected edge") - } - if len(f.levels) != 0 { - t.Fatalf("unconsumed levels: %v", f.levels) - } -} - -func TestPollEdge_FallingEdge(t *testing.T) { - f := pinLevels{levels: []gpio.Level{gpio.Low, gpio.High, gpio.Low}} - p := PollEdge(&f, physic.KiloHertz) - if err := p.In(gpio.PullNoChange, gpio.FallingEdge); err != nil { - t.Fatal(err) - } - if !p.WaitForEdge(-1) { - t.Fatal("expected edge") - } - if len(f.levels) != 0 { - t.Fatalf("unconsumed levels: %v", f.levels) - } -} - -func TestPollEdge_BothEdges(t *testing.T) { - f := pinLevels{levels: []gpio.Level{gpio.High, gpio.Low}} - p := PollEdge(&f, physic.KiloHertz) - if err := p.In(gpio.PullNoChange, gpio.BothEdges); err != nil { - t.Fatal(err) - } - if !p.WaitForEdge(-1) { - t.Fatal("expected edge") - } - if len(f.levels) != 0 { - t.Fatal("unconsumed level") - } -} - -func TestPollEdge_RealPin(t *testing.T) { - f := gpiotest.Pin{} - p := PollEdge(&f, physic.Hertz) - r, ok := p.(gpio.RealPin) - if !ok { - t.Fatal("expected gpio.RealPin") - } - a, ok := r.Real().(*gpiotest.Pin) - if !ok { - t.Fatal("expected gpiotest.Pin") - } - if a != &f { - t.Fatal("expected actual pin") - } -} - -func TestPollEdge_RealPin_Deep(t *testing.T) { - f := gpiotest.Pin{} - p := PollEdge(PollEdge(&f, physic.Hertz), physic.Hertz) - r, ok := p.(gpio.RealPin) - if !ok { - t.Fatal("expected gpio.RealPin") - } - a, ok := r.Real().(*gpiotest.Pin) - if !ok { - t.Fatal("expected gpiotest.Pin") - } - if a != &f { - t.Fatal("expected actual pin") - } -} - -// - -type pinLevels struct { - gpiotest.Pin - mu sync.Mutex - levels []gpio.Level -} - -func (p *pinLevels) Read() gpio.Level { - p.mu.Lock() - defer p.mu.Unlock() - l := p.levels[0] - p.levels = p.levels[1:] - return l -} - -type pinWait struct { - gpiotest.Pin - wait chan struct{} - once sync.Once -} - -func (p *pinWait) Read() gpio.Level { - p.once.Do(func() { - p.wait <- struct{}{} - }) - return true -}