mirror of https://github.com/periph/devices
parent
f54b53f207
commit
6853591d2a
@ -0,0 +1,90 @@
|
|||||||
|
// 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{}
|
||||||
@ -0,0 +1,180 @@
|
|||||||
|
// 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(0) {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
// 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
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
// 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/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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue