mirror of
https://github.com/RoganDawes/P4wnP1_aloa.git
synced 2025-04-01 00:18:25 +02:00
272 lines
8.4 KiB
Go
272 lines
8.4 KiB
Go
// +build linux
|
|
|
|
package pgpio
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"periph.io/x/periph/conn/gpio"
|
|
"periph.io/x/periph/conn/physic"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
EEdgeDetectNotRunning = errors.New("edge detection not running")
|
|
EEdgeDetectAborted = errors.New("edge detection aborted")
|
|
)
|
|
|
|
type steadyTimeLevel struct {
|
|
SteadyTime time.Duration
|
|
Level gpio.Level
|
|
}
|
|
|
|
type P4wnp1PinIO struct {
|
|
piPin gpio.PinIO
|
|
|
|
/*
|
|
edgeDetectLoopContext *context.Context //nil if no internal edge detect loop is running
|
|
edgeDetectLoopCancel context.CancelFunc //nil if no internal edge detect loop is running
|
|
edgeDetectionMutex *sync.Mutex
|
|
*/
|
|
|
|
edge gpio.Edge
|
|
pull gpio.Pull
|
|
edgeDetectionAbort bool
|
|
edgeDetectionChannel chan steadyTimeLevel //Every time a valid edge is detected, this channel returns the duration since the last valid edge (==steadyTime. could be used for debounce) and the gpio.Level during edge event
|
|
waitForEdgeStopped bool
|
|
lastEdgeTime time.Time
|
|
}
|
|
|
|
func (p *P4wnp1PinIO) startEdgeDetection(pull gpio.Pull, edge gpio.Edge, preserveUnconsumed bool) (err error) {
|
|
fmt.Println("starting edge detection for", p.Name(), "...")
|
|
|
|
p.lastEdgeTime = time.Now()
|
|
if preserveUnconsumed {
|
|
p.edgeDetectionChannel = make(chan steadyTimeLevel, 0) // don't allow buffering --> block read loop if detected edge isn't consumed
|
|
} else {
|
|
p.edgeDetectionChannel = make(chan steadyTimeLevel, 1) // allow buffering a single value
|
|
}
|
|
|
|
p.edgeDetectionAbort = false
|
|
p.waitForEdgeStopped = false
|
|
|
|
go func() {
|
|
Loop:
|
|
for !p.edgeDetectionAbort {
|
|
// Note: it seems to be impossible to force WaitForEdge to produce a 'false' result if no timeout is given.
|
|
// Providing a timeout isn't suitable for this use case (continuous edge detection without interruption till
|
|
// stopEdgeDetection() is called). A possible solution would be to call WaitForEdge in a loop with a timeout
|
|
// and checking for the abort condition in every iteration, but this involves a tradeoff between responsiveness
|
|
// to the stop method (long timeouts) and higher system load (short timeout).
|
|
//
|
|
// To cope with that, the state of `p.edgeDetectionAbort` is checked after every return of `WaitForEdge(-1)`.
|
|
// In case no edges are detected after stopEdgeDetection has been called, this would block forever. In order to
|
|
// deal with that, the stopEdgeDetection method changes the pull state of the GPIO resistor between PullUp and
|
|
// PullDown, till this loop ends and indicates termination by setting p.waitForEdgeStopped to true.
|
|
//
|
|
// This approach is only tested on Pi0w and likely behaves different on other drivers or doesn't work at all
|
|
// if no internal pull up/pull down resistors are present. The PinIO.Halt() method unfortunately doesn't interrupt
|
|
// WaitForEdge.
|
|
waitSuccess := p.piPin.WaitForEdge(-1)
|
|
fmt.Println("Edge event received")
|
|
if !waitSuccess {
|
|
// don't consume the edge, stopEdgeDetection has been called meanwhile --> abort the loop
|
|
fmt.Println("WaitForEdge failed, aborting EdgeDetection")
|
|
break Loop
|
|
}
|
|
if p.edgeDetectionAbort {
|
|
// don't consume the edge, stopEdgeDetection has been called meanwhile --> abort the loop
|
|
fmt.Println("Ignored edge and because stopEdgeDetection was called, aborting edgeDetection")
|
|
break Loop
|
|
}
|
|
|
|
//if here, we got a un-debounced and unvalidated edge and thus measure the duration since the last detected edge
|
|
now := time.Now()
|
|
timeSinceLastEdge := now.Sub(p.lastEdgeTime)
|
|
p.lastEdgeTime = now
|
|
|
|
var level gpio.Level
|
|
switch p.edge {
|
|
case gpio.RisingEdge:
|
|
level = gpio.High // don't read from pin, assume correct detection
|
|
case gpio.FallingEdge:
|
|
level = gpio.Low // don't read from pin, assume correct detection
|
|
case gpio.BothEdges:
|
|
level = p.piPin.Read()
|
|
default:
|
|
// this includes gpio.NoEdge and shouldn't be set if this loop is running, so we abort the whole loop
|
|
break Loop
|
|
}
|
|
|
|
// Send the steadyTimeLeve along the channel
|
|
stl := steadyTimeLevel{
|
|
Level: level,
|
|
SteadyTime: timeSinceLastEdge,
|
|
}
|
|
|
|
if preserveUnconsumed {
|
|
p.edgeDetectionChannel <- stl //write steadyTimeLevel to channel and block loop till read by ExtWaitForEdge
|
|
// ToDo: fix debounce agnostic decision
|
|
// Note: this has influence on timeSince last edge. The debounce duration has to be known here, to avoid blocking
|
|
// write to the channel (and possibly increasing timeSinceLastEdge), in case the event occurred during debounce duration.
|
|
} else {
|
|
//pop old stl from channel if needed
|
|
for len(p.edgeDetectionChannel) > 0 {
|
|
<-p.edgeDetectionChannel
|
|
}
|
|
p.edgeDetectionChannel <- stl //put latest stl to channel
|
|
}
|
|
}
|
|
|
|
p.waitForEdgeStopped = true
|
|
|
|
//indicate that edge detection has successfully terminated by closing the edgeDetectionChannel channel (and set to nil to use as indicator for isEdgeDetectionRunning, without reading from this channel)
|
|
close(p.edgeDetectionChannel)
|
|
p.edgeDetectionChannel = nil
|
|
|
|
//assure edgeDetection is stopped
|
|
fmt.Println("... edge detection for", p.Name(), "stopped")
|
|
}()
|
|
|
|
fmt.Println("... edge detection for", p.Name(), "started")
|
|
return
|
|
}
|
|
|
|
func (p *P4wnp1PinIO) isEdgeDetectionRunning() bool {
|
|
if p.edgeDetectionChannel == nil {
|
|
//fmt.Println("edge detection not running")
|
|
return false
|
|
}
|
|
//fmt.Println("edge detection running")
|
|
return true
|
|
}
|
|
|
|
func (p *P4wnp1PinIO) stopEdgeDetection() error {
|
|
if !p.isEdgeDetectionRunning() {
|
|
return EEdgeDetectNotRunning
|
|
}
|
|
|
|
fmt.Println("stopping edge detection for", p.Name(), "...")
|
|
p.edgeDetectionAbort = true
|
|
|
|
// hackish approach to end WaitForEdge, by toggling Pull resistors (see comments in startEdgeDetection for details)
|
|
for !p.waitForEdgeStopped {
|
|
p.piPin.In(gpio.PullDown, p.edge)
|
|
if !p.waitForEdgeStopped {
|
|
p.piPin.In(gpio.PullUp, p.edge)
|
|
}
|
|
}
|
|
|
|
// disable edge detection interrupt and restore pull resistor state
|
|
p.piPin.In(p.pull, gpio.NoEdge)
|
|
|
|
// wait till stop success is indicated
|
|
if p.edgeDetectionChannel != nil { // if channel still exists
|
|
<-p.edgeDetectionChannel //wait for close
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p P4wnp1PinIO) Edge() gpio.Edge {
|
|
return p.edge
|
|
}
|
|
|
|
func (p *P4wnp1PinIO) ExtWaitForEdge(ctx context.Context, debounceDuration time.Duration) (level gpio.Level, err error) {
|
|
useDebounce := debounceDuration > 0
|
|
|
|
for { // the select statement is wrapped into a for loop, to account for edges not fulfilling the debounceDuration criteria
|
|
select {
|
|
case steadyTimeLevel, channelOpen := <-p.edgeDetectionChannel:
|
|
//channel closed, so we return error
|
|
if !channelOpen {
|
|
return level, EEdgeDetectNotRunning
|
|
}
|
|
if !useDebounce {
|
|
// no debounce needed, ultimately return the edge change
|
|
return steadyTimeLevel.Level, nil
|
|
} else {
|
|
// we have to assure that no other edge change event occurs in debounce duration (steady level) before returning
|
|
if steadyTimeLevel.SteadyTime >= debounceDuration {
|
|
//detected edge fulfills debounce criteria
|
|
return steadyTimeLevel.Level, nil
|
|
}
|
|
// else ignore edge
|
|
fmt.Printf("Ignored detected edge as invalidated by debounce: %v\n", steadyTimeLevel)
|
|
}
|
|
case <-ctx.Done():
|
|
return level, EEdgeDetectAborted
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
func (p P4wnp1PinIO) String() string {
|
|
return p.piPin.String()
|
|
}
|
|
*/
|
|
func (p P4wnp1PinIO) Halt() error {
|
|
return p.piPin.Halt()
|
|
}
|
|
|
|
func (p P4wnp1PinIO) Name() string {
|
|
return p.piPin.Name()
|
|
}
|
|
|
|
func (p P4wnp1PinIO) Number() int {
|
|
return p.piPin.Number()
|
|
}
|
|
|
|
func (p P4wnp1PinIO) Function() string {
|
|
return p.piPin.Function()
|
|
}
|
|
|
|
func (p *P4wnp1PinIO) In(pull gpio.Pull, edge gpio.Edge) (err error) {
|
|
p.stopEdgeDetection()
|
|
// bring up edgeDetection go routine (again) if needed
|
|
err = p.piPin.In(pull, edge)
|
|
if err != nil {
|
|
return
|
|
}
|
|
p.pull = pull
|
|
p.edge = edge
|
|
if edge != gpio.NoEdge {
|
|
p.startEdgeDetection(pull, edge, false) //don't preserve unconsumed events
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (p P4wnp1PinIO) Read() gpio.Level {
|
|
return p.piPin.Read()
|
|
}
|
|
|
|
func (p P4wnp1PinIO) WaitForEdge(timeout time.Duration) bool {
|
|
return p.piPin.WaitForEdge(timeout)
|
|
}
|
|
|
|
func (p P4wnp1PinIO) Pull() gpio.Pull {
|
|
return p.piPin.Pull()
|
|
}
|
|
|
|
func (p P4wnp1PinIO) DefaultPull() gpio.Pull {
|
|
return p.piPin.DefaultPull()
|
|
}
|
|
|
|
func (p *P4wnp1PinIO) Out(l gpio.Level) error {
|
|
//stop edge detection, if needed
|
|
p.stopEdgeDetection()
|
|
return p.piPin.Out(l)
|
|
}
|
|
|
|
func (p *P4wnp1PinIO) PWM(duty gpio.Duty, f physic.Frequency) error {
|
|
//stop edge detection, if needed
|
|
p.stopEdgeDetection()
|
|
return p.piPin.PWM(duty, f)
|
|
}
|
|
|
|
func NewP4wnp1PinIO(p gpio.PinIO) *P4wnp1PinIO {
|
|
return &P4wnp1PinIO{piPin: p}
|
|
}
|