direttoforo UI client for GPIO
This commit is contained in:
parent
f9812d3ef8
commit
2d7b6f5bd5
1 changed files with 203 additions and 0 deletions
203
cmd/direttoforo-led/led.go
Normal file
203
cmd/direttoforo-led/led.go
Normal file
|
@ -0,0 +1,203 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/davecheney/gpio"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"git.lattuga.net/boyska/direttoforo"
|
||||
)
|
||||
|
||||
// appicc consumes a channel representing a stream of direttoforo.State updates
|
||||
// for each message, GPIOs are set accordingly
|
||||
// if the pin cannot be opened, this function will fail early. However, caller has no way to receive this
|
||||
// information nor informations about the error
|
||||
// this function will live as long as the chan "s" will
|
||||
func appicc(gpios gpioMapFlag, s <-chan direttoforo.State) error {
|
||||
unassigned := make([]string, 0)
|
||||
pins := make(map[string]gpio.Pin)
|
||||
for name, pinnumber := range gpios {
|
||||
pin, err := gpio.OpenPin(pinnumber, gpio.ModeOutput)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error opening pin")
|
||||
}
|
||||
pins[name] = pin
|
||||
}
|
||||
for state := range s {
|
||||
fmt.Println("led", state)
|
||||
for sname, sstate := range state.Streams {
|
||||
pin, ok := pins[sname]
|
||||
if ok {
|
||||
if sstate.Up {
|
||||
pin.Set()
|
||||
} else {
|
||||
pin.Clear()
|
||||
}
|
||||
} else {
|
||||
// warn about it?
|
||||
alreadywarned := false
|
||||
for _, v := range unassigned {
|
||||
if v == sname {
|
||||
alreadywarned = true
|
||||
}
|
||||
}
|
||||
if !alreadywarned {
|
||||
unassigned = append(unassigned, sname)
|
||||
fmt.Fprintf(os.Stderr, "INFO: stream '%s' unassigned\n", sname)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, pin := range pins {
|
||||
pin.Clear()
|
||||
pin.Close()
|
||||
}
|
||||
fmt.Println("led poweroff")
|
||||
return nil
|
||||
}
|
||||
|
||||
func connect(bindpath string, s chan<- direttoforo.State, quit <-chan interface{}) {
|
||||
defer close(s)
|
||||
OuterLoop:
|
||||
for {
|
||||
select {
|
||||
case <-quit:
|
||||
return
|
||||
default:
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
conn, err := net.Dial("unix", bindpath)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error connecting", err)
|
||||
continue OuterLoop
|
||||
}
|
||||
defer conn.Close()
|
||||
hello := []byte("DIRETTOFORO V1\n") // hello size is specified by protocol to be constant
|
||||
b := bufio.NewReader(conn)
|
||||
nread, err := b.Read(hello)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error reading protocol version", err)
|
||||
continue OuterLoop
|
||||
}
|
||||
if nread != 15 || string(hello) != "DIRETTOFORO V1\n" {
|
||||
fmt.Fprintln(os.Stderr, "error: protocol version mismatch")
|
||||
continue OuterLoop
|
||||
}
|
||||
|
||||
// read data from connection and put in a chan
|
||||
msgs := make(chan direttoforo.State)
|
||||
go func() {
|
||||
decoder := json.NewDecoder(conn)
|
||||
var newState direttoforo.State
|
||||
for {
|
||||
err = decoder.Decode(&newState)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error decoding", err)
|
||||
time.Sleep(time.Second)
|
||||
close(msgs)
|
||||
break
|
||||
}
|
||||
msgs <- newState
|
||||
}
|
||||
}()
|
||||
MsgLoop: // copy msgs to s, but handles quit signal, too
|
||||
for {
|
||||
select {
|
||||
case <-quit:
|
||||
return
|
||||
case msg := <-msgs:
|
||||
if msg.Streams == nil { // msgs closed
|
||||
break MsgLoop
|
||||
}
|
||||
s <- msg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: maybe create a map[string]int instead?
|
||||
type gpioMapFlag map[string]int
|
||||
|
||||
func (i *gpioMapFlag) String() string {
|
||||
return fmt.Sprint(*i)
|
||||
}
|
||||
|
||||
func (i *gpioMapFlag) Set(value string) error {
|
||||
for _, keyvalue := range strings.Split(value, ",") {
|
||||
kv := strings.SplitN(keyvalue, ":", 2)
|
||||
if len(kv) != 2 {
|
||||
return fmt.Errorf("Invalid keyvalue: %s", keyvalue)
|
||||
}
|
||||
value, err := strconv.Atoi(kv[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range *i {
|
||||
if v == value {
|
||||
return fmt.Errorf("Can't assign both '%s' and '%s' to %d", k, kv[0], value)
|
||||
}
|
||||
}
|
||||
(*i)[kv[0]] = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
gpios := make(gpioMapFlag)
|
||||
bindpath := flag.String("bindpath", "/var/lib/direttoforo/ui.sock", "UNIX domain socket path for UIs")
|
||||
flag.Var(&gpios, "gpios", "Comma-separated list of stream-GPIOnumber to be used. Example: rec:11,stream:7")
|
||||
flag.Parse()
|
||||
|
||||
fmt.Println(gpios)
|
||||
states := make(chan direttoforo.State)
|
||||
connectcloser := make(chan interface{})
|
||||
allDone := make(chan interface{})
|
||||
ledDone := make(chan error)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(allDone)
|
||||
}()
|
||||
go func() {
|
||||
err := appicc(gpios, states)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
ledDone <- err // appicc has no termination notification. This provides a way to have one
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
connect(*bindpath, states, connectcloser)
|
||||
wg.Done()
|
||||
}()
|
||||
killed := make(chan os.Signal, 1)
|
||||
signal.Notify(killed, os.Interrupt) // ctrl-c
|
||||
for {
|
||||
select {
|
||||
case sig := <-killed:
|
||||
fmt.Println("killed by", sig)
|
||||
close(connectcloser)
|
||||
case err := <-ledDone:
|
||||
// led controller failed early (error on OpenPin)
|
||||
if err != nil {
|
||||
close(connectcloser)
|
||||
}
|
||||
// else: it was closed because we are shutting
|
||||
// down
|
||||
case <-allDone:
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue