This commit is contained in:
saji 2023-05-20 14:53:10 -05:00
parent 86c439438d
commit a9a11c5456
3 changed files with 125 additions and 91 deletions

43
cmd/gotelem/cli/client.go Normal file
View file

@ -0,0 +1,43 @@
package cli
import (
"fmt"
"github.com/kschamplin/gotelem"
"github.com/kschamplin/gotelem/mprpc"
"github.com/urfave/cli/v2"
)
func init() {
subCmds = append(subCmds, clientCmd)
}
var clientCmd = &cli.Command{
Name: "client",
Aliases: []string{"c"},
Usage: "interact with a gotelem server",
ArgsUsage: "[server url]",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "gui",
Aliases: []string{"g"},
Usage: "start a local TUI",
},
},
Description: `
Connects to a gotelem server or relay. Can be used to
`,
}
// the client should connect to a TCP server and listen to packets.
func CANFrameHandler(f *gotelem.Frame) (*mprpc.RPCEmpty, error){
fmt.Printf("got frame, %v\n", f)
return nil, nil
}
var initialRPCHandlers = map[string]mprpc.ServiceFunc{
"can": mprpc.MakeService(CANFrameHandler),
}

View file

@ -5,11 +5,9 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"strings"
"time" "time"
"github.com/kschamplin/gotelem" "github.com/kschamplin/gotelem"
"github.com/kschamplin/gotelem/socketcan"
"github.com/kschamplin/gotelem/xbee" "github.com/kschamplin/gotelem/xbee"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"golang.org/x/exp/slog" "golang.org/x/exp/slog"
@ -19,7 +17,7 @@ var serveFlags = []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "device", Name: "device",
Aliases: []string{"d"}, Aliases: []string{"d"},
Usage: "The XBee to connect to", Usage: "The XBee to connect to. Leave blank to not use XBee",
EnvVars: []string{"XBEE_DEVICE"}, EnvVars: []string{"XBEE_DEVICE"},
}, },
&cli.StringFlag{ &cli.StringFlag{
@ -38,10 +36,11 @@ var serveCmd = &cli.Command{
Action: serve, Action: serve,
} }
// FIXME: naming // FIXME: naming
// this is a server handler for i.e tcp socket, http server, socketCAN, xbee, // this is a server handler for i.e tcp socket, http server, socketCAN, xbee,
// etc. we can register them in init() functions. // etc. we can register them in init() functions.
type testThing func(cCtx *cli.Context, broker *gotelem.Broker) (err error) type testThing func(cCtx *cli.Context, broker *gotelem.Broker, logger *slog.Logger) (err error)
type service interface { type service interface {
fmt.Stringer fmt.Stringer
@ -51,9 +50,16 @@ type service interface {
// this variable stores all the hanlders. It has some basic ones, but also // this variable stores all the hanlders. It has some basic ones, but also
// can be extended on certain platforms (see cli/socketcan.go) // can be extended on certain platforms (see cli/socketcan.go)
// or if certain features are present (see sqlite.go) // or if certain features are present (see cli/sqlite.go)
var serveThings = []service{ var serveThings = []service{
&XBeeService{}, &XBeeService{},
&CanLoggerService{},
}
func deriveLogger (oldLogger *slog.Logger, svc service) (newLogger *slog.Logger) {
newLogger = oldLogger.With("svc", svc.String())
return
} }
func serve(cCtx *cli.Context) error { func serve(cCtx *cli.Context) error {
@ -65,20 +71,21 @@ func serve(cCtx *cli.Context) error {
done := make(chan struct{}) done := make(chan struct{})
// start the can listener // start the can listener
// can logger.
go CanDump(broker, logger.WithGroup("candump"), done)
if cCtx.String("can") != "" {
logger.Info("using can device")
go canHandler(broker, logger.With("device", cCtx.String("can")), done, cCtx.String("can"))
if strings.HasPrefix(cCtx.String("can"), "v") {
go vcanTest(cCtx.String("can"))
}
}
go broker.Start() go broker.Start()
for _, svc := range serveThings {
svcLogger := deriveLogger(logger, svc)
logger.Info("starting service", "svc", svc.String())
go func(mySvc service) {
err := mySvc.Start(cCtx, broker, svcLogger)
if err != nil {
logger.Error("service stopped!", "err", err, "svc", mySvc.String())
}
}(svc)
}
// tcp listener server. // tcp listener server.
ln, err := net.Listen("tcp", ":8082") ln, err := net.Listen("tcp", ":8082")
if err != nil { if err != nil {
@ -95,18 +102,24 @@ func serve(cCtx *cli.Context) error {
} }
} }
func tcpSvc(ctx *cli.Context, broker *gotelem.Broker) error {
type rpcService struct {
}
func tcpSvc(ctx *cli.Context, broker *gotelem.Broker, logger *slog.Logger) error {
// TODO: extract port/ip from cli context. // TODO: extract port/ip from cli context.
ln, err := net.Listen("tcp", ":8082") ln, err := net.Listen("tcp", ":8082")
if err != nil { if err != nil {
fmt.Printf("Error listening: %v\n", err) fmt.Printf("Error listening: %v\n", err)
logger.Warn("error listening", "err", err)
return err
} }
for { for {
conn, err := ln.Accept() conn, err := ln.Accept()
if err != nil { if err != nil {
fmt.Printf("error accepting: %v\n", err) logger.Warn("error accepting connection", "err", err)
} }
go handleCon(conn, broker, slog.Default().WithGroup("tcp"), ctx.Done()) go handleCon(conn, broker, logger.With("addr", conn.RemoteAddr()), ctx.Done())
} }
} }
@ -143,70 +156,31 @@ func handleCon(conn net.Conn, broker *gotelem.Broker, l *slog.Logger, done <-cha
} }
// this spins up a new can socket on vcan0 and broadcasts a packet every second. for testing. // this spins up a new can socket on vcan0 and broadcasts a packet every second. for testing.
func vcanTest(devname string) {
sock, err := socketcan.NewCanSocket(devname)
if err != nil {
slog.Error("error opening socket", "err", err)
return
}
testFrame := &gotelem.Frame{
Id: 0x234,
Kind: gotelem.CanSFFFrame,
Data: []byte{0, 1, 2, 3, 4, 5, 6, 7},
}
for {
slog.Info("sending test packet") type CanLoggerService struct {
sock.Send(testFrame) cw gotelem.CanWriter
time.Sleep(1 * time.Second)
}
} }
// connects the broker to a socket can func (c *CanLoggerService) String() string {
func canHandler(broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}, devname string) { return "CanLoggerService"
rxCh := broker.Subscribe("socketcan")
sock, err := socketcan.NewCanSocket(devname)
if err != nil {
l.Error("error opening socket", "err", err)
return
} }
// start a simple dispatcher that just relays can frames. func (c *CanLoggerService) Status() {
rxCan := make(chan gotelem.Frame)
go func() {
for {
pkt, err := sock.Recv()
if err != nil {
l.Warn("error reading SocketCAN", "err", err)
return
}
rxCan <- *pkt
}
}()
for {
select {
case msg := <-rxCh:
l.Info("Sending a CAN bus message", "id", msg.Id, "data", msg.Data)
sock.Send(&msg)
case msg := <-rxCan:
l.Info("Got a CAN bus message", "id", msg.Id, "data", msg.Data)
broker.Publish("socketcan", msg)
case <-done:
sock.Close()
return
}
}
} }
func CanDump(broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}) {
func (c *CanLoggerService) Start(cCtx *cli.Context, broker *gotelem.Broker, l *slog.Logger) (err error) {
rxCh := broker.Subscribe("candump") rxCh := broker.Subscribe("candump")
t := time.Now() t := time.Now()
fname := fmt.Sprintf("candump_%d-%02d-%02dT%02d.%02d.%02d.txt", fname := fmt.Sprintf("candump_%d-%02d-%02dT%02d.%02d.%02d.txt",
t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())
l.Info("logging to file", "filename", fname)
cw, err := gotelem.OpenCanWriter(fname) cw, err := gotelem.OpenCanWriter(fname)
if err != nil { if err != nil {
slog.Error("error opening file", "err", err) l.Error("error opening file", "filename", fname, "err", err)
return
} }
for { for {
@ -214,7 +188,7 @@ func CanDump(broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}) {
case msg := <-rxCh: case msg := <-rxCh:
cw.Send(&msg) cw.Send(&msg)
case <-done: case <-cCtx.Done():
cw.Close() cw.Close()
return return
} }
@ -232,9 +206,7 @@ func (x *XBeeService) Status() {
} }
func (x *XBeeService) Start(cCtx *cli.Context, func (x *XBeeService) Start(cCtx *cli.Context, broker *gotelem.Broker, logger *slog.Logger) (err error) {
broker *gotelem.Broker, logger *slog.Logger,
) (err error) {
if cCtx.String("xbee") == "" { if cCtx.String("xbee") == "" {
logger.Info("not using xbee") logger.Info("not using xbee")
return return
@ -260,7 +232,6 @@ func (x *XBeeService) Start(cCtx *cli.Context,
x.session.Close() x.session.Close()
return return
case msg := <-rxCh: case msg := <-rxCh:
// TODO: take can message and send it over CAN.
logger.Info("got msg", "msg", msg) logger.Info("got msg", "msg", msg)
buf := make([]byte, 0) buf := make([]byte, 0)
@ -275,6 +246,5 @@ func (x *XBeeService) Start(cCtx *cli.Context,
} }
return
} }

View file

@ -3,6 +3,9 @@
package cli package cli
import ( import (
"strings"
"time"
"github.com/kschamplin/gotelem" "github.com/kschamplin/gotelem"
"github.com/kschamplin/gotelem/socketcan" "github.com/kschamplin/gotelem/socketcan"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -12,7 +15,6 @@ import (
// this file adds socketCAN commands and functionality when building on linux. // this file adds socketCAN commands and functionality when building on linux.
// It is an example of the modular architecture of the command line and server stack. // It is an example of the modular architecture of the command line and server stack.
var canDevFlag = &cli.StringFlag{ var canDevFlag = &cli.StringFlag{
Name: "can", Name: "can",
Aliases: []string{"c"}, Aliases: []string{"c"},
@ -34,11 +36,10 @@ func init() {
subCmds = append(subCmds, socketCANCmd) subCmds = append(subCmds, socketCANCmd)
} }
// FIXME: add logging back in since it's missing rn // FIXME: add logging back in since it's missing rn
type socketCANService struct { type socketCANService struct {
sock socketcan.CanSocket
} }
func (s *socketCANService) Status() { func (s *socketCANService) Status() {
@ -50,9 +51,16 @@ func (s *socketCANService) String() string {
} }
func (s *socketCANService) Start(cCtx *cli.Context, broker *gotelem.Broker, logger *slog.Logger) (err error) { func (s *socketCANService) Start(cCtx *cli.Context, broker *gotelem.Broker, logger *slog.Logger) (err error) {
// vcan0 demo
if strings.HasPrefix(cCtx.String("can"), "v") {
go vcanTest(cCtx.String("can"))
}
rxCh := broker.Subscribe("socketCAN") rxCh := broker.Subscribe("socketCAN")
sock, err := socketcan.NewCanSocket(cCtx.String("can")) sock, err := socketcan.NewCanSocket(cCtx.String("can"))
if err != nil { if err != nil {
logger.Error("error opening socket", "err", err)
return return
} }
@ -66,7 +74,6 @@ func (s *socketCANService) Start(cCtx *cli.Context, broker *gotelem.Broker, logg
} }
rxCan <- *pkt rxCan <- *pkt
} }
}() }()
for { for {
@ -79,10 +86,8 @@ func (s *socketCANService) Start(cCtx *cli.Context, broker *gotelem.Broker, logg
return return
} }
} }
} }
var socketCANCmd = &cli.Command{ var socketCANCmd = &cli.Command{
Name: "can", Name: "can",
Usage: "SocketCAN utilities", Usage: "SocketCAN utilities",
@ -104,9 +109,25 @@ Various helper utilties for CAN bus on sockets.
Value: false, Value: false,
}, },
}, },
}, },
}, },
} }
func vcanTest(devname string) {
sock, err := socketcan.NewCanSocket(devname)
if err != nil {
slog.Error("error opening socket", "err", err)
return
}
testFrame := &gotelem.Frame{
Id: 0x234,
Kind: gotelem.CanSFFFrame,
Data: []byte{0, 1, 2, 3, 4, 5, 6, 7},
}
for {
slog.Info("sending test packet")
sock.Send(testFrame)
time.Sleep(1 * time.Second)
}
}