GPIO,service,webclient: debounce GPIO,update SINGLE TriggerAction, WebClient allows loading bluetooth and master settings via TriggerAction

This commit is contained in:
MaMe82
2018-11-13 10:37:46 +01:00
parent 391a9400af
commit 637b51e4e1
8 changed files with 558 additions and 385 deletions

View File

@@ -1,8 +1,11 @@
// +build linux
package service
import (
"errors"
"context"
"fmt"
"github.com/mame82/P4wnP1_go/service/pgpio"
"periph.io/x/periph"
"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/gpio/gpioreg"
@@ -10,28 +13,25 @@ import (
"periph.io/x/periph/host"
"periph.io/x/periph/host/rpi"
"sync"
"time"
"errors"
pb "github.com/mame82/P4wnP1_go/proto"
)
/*
ToDo: Entprellen + StopAllEdgeDetecting (when new TriggerActionSet is deployed)
ToDo: If a single item of the trigger action set changes, all GPIOTriggers have to be re-enumearted, to assure that there's no edge detection running for a trigger which doesn't exist anymore
*/
var (
EGpioNotAvailable = errors.New("sub system GPIO not available")
EGpioPinInvalid = errors.New("invalid GPIO pin")
)
type GpioManager struct {
availableGpioPins []gpio.PinIO
availableGpioPins []*pgpio.P4wnp1PinIO
availableGpioPinsMap map[string]*pgpio.P4wnp1PinIO
availableGpioNames []string
rootSvc *Service
edgeDetectingMutex *sync.Mutex
edgeDetecting map[gpio.PinIO]bool
edgeDetecting map[gpio.PinIO]bool
IsUsable bool
@@ -45,12 +45,11 @@ func (gm *GpioManager) Start() {
func (gm *GpioManager) Stop() {
}
func NewGpioManager(rootSvc *Service) (res *GpioManager) {
gm := &GpioManager{
rootSvc: rootSvc,
}
state,err := host.Init()
state, err := host.Init()
if err != nil {
gm.IsUsable = false
return
@@ -59,39 +58,107 @@ func NewGpioManager(rootSvc *Service) (res *GpioManager) {
gm.State = state
gm.IsUsable = rpi.Present()
gm.availableGpioPinsMap = make(map[string]*pgpio.P4wnp1PinIO)
gpios := gpioreg.All()
for _,g := range gpios {
for _, g := range gpios {
if pinreg.IsConnected(g) {
gm.availableGpioPins = append(gm.availableGpioPins,g)
ppin := pgpio.NewP4wnp1PinIO(g)
gm.availableGpioPins = append(gm.availableGpioPins, ppin)
gm.availableGpioPinsMap[g.Name()] = ppin
gm.availableGpioNames = append(gm.availableGpioNames, g.Name())
}
}
gm.edgeDetecting = make(map[gpio.PinIO]bool)
gm.edgeDetectingMutex = &sync.Mutex{}
return gm
}
func (gm GpioManager) GetAvailableGpios() (res []gpio.PinIO, err error) {
func (gm *GpioManager) GetAvailableGpios() (res []*pgpio.P4wnp1PinIO, err error) {
if gm.IsUsable {
return gm.availableGpioPins,nil
return gm.availableGpioPins, nil
}
return res,EGpioNotAvailable
return res, EGpioNotAvailable
}
func (gm GpioManager) GetAvailableGpioNames() (res []string, err error) {
func (gm *GpioManager) GetAvailableGpioNames() (res []string, err error) {
if gm.IsUsable {
return gm.availableGpioNames,nil
return gm.availableGpioNames, nil
}
return res,EGpioNotAvailable
return res, EGpioNotAvailable
}
func (gm *GpioManager) DeployGpioTrigger(in *pb.TriggerGPIOIn) (err error) {
if !gm.IsUsable {
return EGpioNotAvailable
}
p,present := gm.availableGpioPinsMap[in.GpioName]
if !present {
return EGpioPinInvalid
}
fmt.Printf("Deploying trigger for GPIO: %+v\n", p)
pull := gpio.Float
switch in.PullUpDown {
case pb.GPIOInPullUpDown_DOWN:
pull = gpio.PullDown
case pb.GPIOInPullUpDown_UP:
pull = gpio.PullUp
}
edge := gpio.BothEdges
switch in.GpioInEdge {
case pb.GPIOInEdge_FALLING:
edge = gpio.FallingEdge
case pb.GPIOInEdge_RISING:
edge = gpio.RisingEdge
}
p.In(pull, edge)
debounceDelay := time.Duration(in.DebounceMillis) * time.Millisecond
//ToDo: remove next line, as this is a fixed test delay of 100 ms
debounceDelay = 100 * time.Millisecond
go func() {
fmt.Println("Starting edge detection for pin " + p.Name())
detectErr := error(nil)
for detectErr == nil {
var detectedLevel gpio.Level
detectedLevel,detectErr = p.ExtWaitForEdge(context.Background(), debounceDelay)
fmt.Printf("... done wait for edge %s level: %v\n", p.Name(), detectedLevel)
//Edge detected, check if still edge detecting before consuming
switch detectedLevel {
case gpio.High:
fmt.Println("Gpio " + p.Name() + " changed to high")
gm.rootSvc.SubSysEvent.Emit(ConstructEventTriggerGpioIn(p.Name(), bool(gpio.High)))
case gpio.Low:
fmt.Println("Gpio " + p.Name() + " changed to low")
gm.rootSvc.SubSysEvent.Emit(ConstructEventTriggerGpioIn(p.Name(), bool(gpio.Low)))
}
}
fmt.Println("!!!! STOPPED edge loop for pin " + p.Name())
}()
return nil
}
/*
func (gm *GpioManager) DeployGpioTriggerOld(in *pb.TriggerGPIOIn) (err error) {
if !gm.IsUsable {
return EGpioNotAvailable
}
p := gpioreg.ByName(in.GpioName)
if p == nil {
return EGpioPinInvalid
@@ -124,19 +191,46 @@ func (gm *GpioManager) DeployGpioTrigger(in *pb.TriggerGPIOIn) (err error) {
gm.edgeDetecting[p] = true
gm.edgeDetectingMutex.Unlock()
debounceDelay := time.Duration(in.DebounceMillis) * time.Millisecond
//ToDo: remove next line, as this is a fixed test delay of 100 ms
debounceDelay = 100 *time.Millisecond
go func() {
fmt.Println("Starting edge detection for pin " + p.Name())
lastHit := time.Now()
for gm.isEdgeDetecting(p) {
p.WaitForEdge(-1)
fmt.Println("Wait for edge " + p.Name() + " ...")
waitSuccess := p.WaitForEdge(-1)
fmt.Printf("... done wait for edge %s waitSucces: %v\n", p.Name(), waitSuccess)
if !waitSuccess {
break
}
now := time.Now()
bounce := true
sinceLastHit := now.Sub(lastHit)
if sinceLastHit > debounceDelay {
bounce = false
// could do `lastHit = now` if timer only should be reset for "no bounce"
}
lastHit = now
//Edge detected, check if still edge detecting before consuming
if gm.isEdgeDetecting(p) {
switch p.Read() {
case gpio.High:
fmt.Println("Gpio " + p.Name() + " changed to high")
gm.rootSvc.SubSysEvent.Emit(ConstructEventTriggerGpioIn(p.Name(), bool(gpio.High)))
if !bounce {
gm.rootSvc.SubSysEvent.Emit(ConstructEventTriggerGpioIn(p.Name(), bool(gpio.High)))
} else {
fmt.Println("... ignored, as in debounceDelay")
}
case gpio.Low:
fmt.Println("Gpio " + p.Name() + " changed to low")
gm.rootSvc.SubSysEvent.Emit(ConstructEventTriggerGpioIn(p.Name(), bool(gpio.Low)))
if !bounce {
gm.rootSvc.SubSysEvent.Emit(ConstructEventTriggerGpioIn(p.Name(), bool(gpio.Low)))
} else {
fmt.Println("... ignored, as in debounceDelay")
}
}
} else {
@@ -144,6 +238,7 @@ func (gm *GpioManager) DeployGpioTrigger(in *pb.TriggerGPIOIn) (err error) {
break
}
}
fmt.Println("STOPPED edge detection for pin " + p.Name())
gm.edgeDetectingMutex.Lock()
delete(gm.edgeDetecting, p)
gm.edgeDetectingMutex.Unlock()
@@ -152,23 +247,19 @@ func (gm *GpioManager) DeployGpioTrigger(in *pb.TriggerGPIOIn) (err error) {
return nil
}
*/
func (gm *GpioManager) FireGpioAction(out *pb.ActionGPIOOut) (err error) {
fmt.Println("FireGPIOAction for", out.GpioName)
if !gm.IsUsable {
return EGpioNotAvailable
}
p := gpioreg.ByName(out.GpioName)
if p == nil {
p,present := gm.availableGpioPinsMap[out.GpioName]
if !present {
return EGpioPinInvalid
}
// If edge detection is already running, stop it
if gm.isEdgeDetecting(p) {
gm.stopEdgeDetectionForPin(p)
}
level := gpio.Low
switch out.Value {
case pb.GPIOOutValue_HIGH:
@@ -179,15 +270,28 @@ func (gm *GpioManager) FireGpioAction(out *pb.ActionGPIOOut) (err error) {
}
}
fmt.Printf("Setting %s to out level %v...\n", p.Name(), level)
p.Out(level)
fmt.Println("..setting level done")
return nil
}
func (gm *GpioManager) ResetPins() {
fmt.Println("Resetting all pins")
for _, pin := range gm.availableGpioPins {
if pin.Edge() != gpio.NoEdge {
pin.In(gpio.Float, gpio.NoEdge)
}
fmt.Println("... halting pin " + pin.Name())
pin.Halt()
}
}
/*
func (gm *GpioManager) isEdgeDetecting(p gpio.PinIO) bool {
fmt.Println("Check edge detection for " + p.Name())
gm.edgeDetectingMutex.Lock()
defer gm.edgeDetectingMutex.Unlock()
if _,exists := gm.edgeDetecting[p]; exists && gm.edgeDetecting[p] {
@@ -197,20 +301,39 @@ func (gm *GpioManager) isEdgeDetecting(p gpio.PinIO) bool {
fmt.Println("Edge detection for " + p.Name() + " not running")
return false
}
*/
/*
func (gm *GpioManager) stopEdgeDetectionForPin(p gpio.PinIO) {
fmt.Println("Stopping edge detection for pin " + p.Name())
gm.edgeDetectingMutex.Lock()
if _,exists := gm.edgeDetecting[p]; exists {
gm.edgeDetecting[p] = false
}
gm.edgeDetectingMutex.Unlock()
//p.Halt()
p.In(gpio.Float, gpio.BothEdges)
isNotDeleted := func() bool {
gm.edgeDetectingMutex.Lock()
defer gm.edgeDetectingMutex.Unlock()
_,exists := gm.edgeDetecting[p]
return exists
}
//write high/low till gpio is deleted from map, to assure pending edge detection ended
for gm.isEdgeDetecting(p) {
p.Out(gpio.High)
if gm.isEdgeDetecting(p) {
p.Out(gpio.Low)
for isNotDeleted() {
fmt.Println("... stopping " + p.Name() + " setting PullDown to trigger final edge")
p.In(gpio.PullDown, gpio.BothEdges)
if isNotDeleted() {
fmt.Println("... stopping " + p.Name() + " setting PullUp to trigger final edge")
p.In(gpio.PullUp, gpio.BothEdges)
}
}
}
p.In(gpio.Float, gpio.NoEdge)
}
*/

View File

@@ -628,6 +628,40 @@ func taTriggerTypeMatchesEvtTriggerType(ttype triggerType, evt *pb.Event) bool {
return false
}
// removes the given TriggerAction
// ToDo: for now only the ID is compared, to assure we don't remove a TriggerAction which has been changed meanwhile, we should deep-compare the whole object
func (tam *TriggerActionManager) RemoveTriggerAction(removeTa *pb.TriggerAction) (taRemoved *pb.TriggerAction, err error) {
tam.registeredTriggerActionMutex.Lock()
defer tam.registeredTriggerActionMutex.Unlock()
for idx,ta := range tam.registeredTriggerActions.TriggerActions {
if ta.Id == removeTa.Id {
// remove element (not a problem for running `for`-loop, as it is interrupted here)
tam.registeredTriggerActions.TriggerActions = append(tam.registeredTriggerActions.TriggerActions[:idx], tam.registeredTriggerActions.TriggerActions[idx+1:]...)
//if target ta trigger had a sequenceChecker assigned, remove it
if _,match := ta.Trigger.(*pb.TriggerAction_GroupReceiveMulti); match {
tam.groupReceiveSequenceCheckersMutex.Lock()
delete(tam.groupReceiveSequenceCheckers, ta)
tam.groupReceiveSequenceCheckersMutex.Unlock()
}
return ta, nil
}
}
return nil, ErrTaNotFound
}
func (tam *TriggerActionManager) GetTriggerActionByID(Id uint32) (ta *pb.TriggerAction ,err error) {
for _,ta = range tam.registeredTriggerActions.TriggerActions {
if ta.Id == Id {
return ta, nil
}
}
return nil, ErrTaNotFound
}
// returns the TriggerAction with assigned ID
func (tam *TriggerActionManager) AddTriggerAction(ta *pb.TriggerAction) (taAdded *pb.TriggerAction, err error) {
@@ -664,40 +698,6 @@ func (tam *TriggerActionManager) AddTriggerAction(ta *pb.TriggerAction) (taAdded
return taAdded,nil
}
// removes the given TriggerAction
// ToDo: for now only the ID is compared, to assure we don't remove a TriggerAction which has been changed meanwhile, we should deep-compare the whole object
func (tam *TriggerActionManager) RemoveTriggerAction(removeTa *pb.TriggerAction) (taRemoved *pb.TriggerAction, err error) {
tam.registeredTriggerActionMutex.Lock()
defer tam.registeredTriggerActionMutex.Unlock()
for idx,ta := range tam.registeredTriggerActions.TriggerActions {
if ta.Id == removeTa.Id {
// remove element (not a problem for running `for`-loop, as it is interrupted here)
tam.registeredTriggerActions.TriggerActions = append(tam.registeredTriggerActions.TriggerActions[:idx], tam.registeredTriggerActions.TriggerActions[idx+1:]...)
//if target ta trigger had a sequenceChecker assigned, remove it
if _,match := ta.Trigger.(*pb.TriggerAction_GroupReceiveMulti); match {
tam.groupReceiveSequenceCheckersMutex.Lock()
delete(tam.groupReceiveSequenceCheckers, ta)
tam.groupReceiveSequenceCheckersMutex.Unlock()
}
return ta, nil
}
}
return nil, ErrTaNotFound
}
func (tam *TriggerActionManager) GetTriggerActionByID(Id uint32) (ta *pb.TriggerAction ,err error) {
for _,ta = range tam.registeredTriggerActions.TriggerActions {
if ta.Id == Id {
return ta, nil
}
}
return nil, ErrTaNotFound
}
func (tam *TriggerActionManager) UpdateTriggerAction(srcTa *pb.TriggerAction, addIfMissing bool) (err error) {
tam.registeredTriggerActionMutex.Lock()
@@ -742,6 +742,12 @@ func (tam *TriggerActionManager) UpdateTriggerAction(srcTa *pb.TriggerAction, ad
}
tam.groupReceiveSequenceCheckersMutex.Unlock()
}
//if trigger is GpioIn, configure GPIO
if triggerGpioIn,match := targetTA.Trigger.(*pb.TriggerAction_GpioIn); match {
tam.rootSvc.SubSysGpio.DeployGpioTrigger(triggerGpioIn.GpioIn)
}
return nil
}
@@ -785,6 +791,19 @@ func (tam *TriggerActionManager) GetCurrentTriggerActionSet() (ta *pb.TriggerAct
return &tam.registeredTriggerActions
}
func (tam *TriggerActionManager) redeployGpioForAllTas() {
tam.registeredTriggerActionMutex.Lock()
defer tam.registeredTriggerActionMutex.Unlock()
tam.rootSvc.SubSysGpio.ResetPins()
for _,ta := range tam.registeredTriggerActions.TriggerActions {
ttype, _ := retrieveTriggerActionTypes(ta)
if ttype == triggerTypeGpioIn {
gpioIn := ta.Trigger.(*pb.TriggerAction_GpioIn).GpioIn
tam.rootSvc.SubSysGpio.DeployGpioTrigger(gpioIn)
}
}
}
func (tam *TriggerActionManager) Start() {
tam.evtRcv = tam.rootSvc.SubSysEvent.RegisterReceiver(common_web.EVT_TRIGGER)
go tam.processing_loop()

View File

@@ -55,11 +55,15 @@ func (s *server) DeployTriggerActionSetUpdate(ctx context.Context, updateTas *pb
defer s.rootSvc.SubSysEvent.Emit(ConstructEventNotifyStateChange(common_web.STATE_CHANGE_EVT_TYPE_TRIGGER_ACTIONS))
for _, updateTa := range updateTas.TriggerActions {
// try to find the trigger action to update by ID
fmt.Printf("Updating TriggerAction %d ...\n", updateTa.Id)
if s.rootSvc.SubSysTriggerActions.UpdateTriggerAction(updateTa, false) != nil {
fmt.Printf("Updating TriggerAction %d failed: %v\n", updateTa.Id, err)
// coudln't find the given action, return with error
return &s.rootSvc.SubSysTriggerActions.registeredTriggerActions,errors.New(fmt.Sprintf("Couldn't find trigger action with id %d", updateTa.Id))
}
fmt.Printf("Updating TriggerAction %d succeeded\n", updateTa.Id)
}
resultingTas = &s.rootSvc.SubSysTriggerActions.registeredTriggerActions
return
}