gpioutil: move to experimental/conn/gpio/gpioutil

This mirrors the end location.
pull/1/head
Marc-Antoine Ruel 8 years ago
parent 44ff30f2a3
commit 0f9300c793

@ -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{}

@ -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))
}
}
}

@ -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

@ -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())
}
}
}

@ -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{}

@ -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
}
Loading…
Cancel
Save