major wins
This commit is contained in:
parent
eefc511f4f
commit
47a818a451
|
@ -7,14 +7,17 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var subCmds = []*cli.Command{
|
||||||
|
serveCmd,
|
||||||
|
xbeeCmd,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func Execute() {
|
func Execute() {
|
||||||
app := &cli.App{
|
app := &cli.App{
|
||||||
Name: "gotelem",
|
Name: "gotelem",
|
||||||
Usage: "see everything",
|
Usage: "see everything",
|
||||||
Commands: []*cli.Command{
|
Commands: subCmds,
|
||||||
serveCmd,
|
|
||||||
xbeeCmd,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
|
|
@ -15,34 +15,45 @@ import (
|
||||||
"golang.org/x/exp/slog"
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var serveFlags = []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "device",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "The XBee to connect to",
|
||||||
|
EnvVars: []string{"XBEE_DEVICE"},
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "logfile",
|
||||||
|
Aliases: []string{"l"},
|
||||||
|
Value: "log.txt",
|
||||||
|
Usage: "file to store log to",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var serveCmd = &cli.Command{
|
var serveCmd = &cli.Command{
|
||||||
Name: "serve",
|
Name: "serve",
|
||||||
Aliases: []string{"server", "s"},
|
Aliases: []string{"server", "s"},
|
||||||
Usage: "Start a telemetry server",
|
Usage: "Start a telemetry server",
|
||||||
Flags: []cli.Flag{
|
Flags: serveFlags,
|
||||||
&cli.BoolFlag{Name: "test", Usage: "use vcan0 test"},
|
Action: serve,
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "device",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "The XBee to connect to",
|
|
||||||
EnvVars: []string{"XBEE_DEVICE"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "can",
|
|
||||||
Aliases: []string{"c"},
|
|
||||||
Usage: "CAN device string",
|
|
||||||
EnvVars: []string{"CAN_DEVICE"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "logfile",
|
|
||||||
Aliases: []string{"l"},
|
|
||||||
Value: "log.txt",
|
|
||||||
Usage: "file to store log to",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: serve,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: naming
|
||||||
|
// this is a server handler for i.e tcp socket, http server, socketCAN, xbee,
|
||||||
|
// etc. we can register them in init() functions.
|
||||||
|
type testThing func(cCtx *cli.Context, broker *gotelem.Broker) (err error)
|
||||||
|
|
||||||
|
type service interface {
|
||||||
|
fmt.Stringer
|
||||||
|
Start(cCtx *cli.Context, broker *gotelem.Broker) (err error)
|
||||||
|
Status()
|
||||||
|
}
|
||||||
|
|
||||||
|
// this variable stores all the hanlders. It has some basic ones, but also
|
||||||
|
// can be extended on certain platforms (see cli/socketcan.go)
|
||||||
|
// or if certain features are present (see sqlite.go)
|
||||||
|
var serveThings = []testThing{}
|
||||||
|
|
||||||
func serve(cCtx *cli.Context) error {
|
func serve(cCtx *cli.Context) error {
|
||||||
// TODO: output both to stderr and a file.
|
// TODO: output both to stderr and a file.
|
||||||
logger := slog.New(slog.NewTextHandler(os.Stderr))
|
logger := slog.New(slog.NewTextHandler(os.Stderr))
|
||||||
|
@ -55,7 +66,6 @@ func serve(cCtx *cli.Context) error {
|
||||||
// can logger.
|
// can logger.
|
||||||
go CanDump(broker, logger.WithGroup("candump"), done)
|
go CanDump(broker, logger.WithGroup("candump"), done)
|
||||||
|
|
||||||
|
|
||||||
if cCtx.String("device") != "" {
|
if cCtx.String("device") != "" {
|
||||||
logger.Info("using xbee device")
|
logger.Info("using xbee device")
|
||||||
transport, err := xbee.ParseDeviceString(cCtx.String("device"))
|
transport, err := xbee.ParseDeviceString(cCtx.String("device"))
|
||||||
|
@ -93,10 +103,25 @@ func serve(cCtx *cli.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func tcpSvc(ctx *cli.Context, broker *gotelem.Broker) error {
|
||||||
|
// TODO: extract port/ip from cli context.
|
||||||
|
ln, err := net.Listen("tcp", ":8082")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error listening: %v\n", err)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error accepting: %v\n", err)
|
||||||
|
}
|
||||||
|
go handleCon(conn, broker, slog.Default().WithGroup("tcp"), ctx.Done())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func handleCon(conn net.Conn, broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}) {
|
func handleCon(conn net.Conn, broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}) {
|
||||||
// reader := msgp.NewReader(conn)
|
// reader := msgp.NewReader(conn)
|
||||||
|
|
||||||
|
|
||||||
subname := fmt.Sprint("tcp", conn.RemoteAddr().String())
|
subname := fmt.Sprint("tcp", conn.RemoteAddr().String())
|
||||||
|
|
||||||
l.Info("started handling", "name", subname)
|
l.Info("started handling", "name", subname)
|
||||||
|
@ -124,10 +149,8 @@ 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) {
|
func vcanTest(devname string) {
|
||||||
sock, err := socketcan.NewCanSocket(devname)
|
sock, err := socketcan.NewCanSocket(devname)
|
||||||
|
@ -152,7 +175,6 @@ func vcanTest(devname string) {
|
||||||
func canHandler(broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}, devname string) {
|
func canHandler(broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}, devname string) {
|
||||||
rxCh := broker.Subscribe("socketcan")
|
rxCh := broker.Subscribe("socketcan")
|
||||||
sock, err := socketcan.NewCanSocket(devname)
|
sock, err := socketcan.NewCanSocket(devname)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Error("error opening socket", "err", err)
|
l.Error("error opening socket", "err", err)
|
||||||
return
|
return
|
||||||
|
@ -208,13 +230,11 @@ func CanDump(broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func XBeeSend(broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}, trspt *xbee.Transport) {
|
func XBeeSend(broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}, trspt *xbee.Transport) {
|
||||||
rxCh := broker.Subscribe("xbee")
|
rxCh := broker.Subscribe("xbee")
|
||||||
l.Info("starting xbee send routine")
|
l.Info("starting xbee send routine")
|
||||||
|
|
||||||
xb, err := xbee.NewSession(trspt, l.With("device", trspt.Type()))
|
xb, err := xbee.NewSession(trspt, l.With("device", trspt.Type()))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Error("failed to start xbee session", "err", err)
|
l.Error("failed to start xbee session", "err", err)
|
||||||
return
|
return
|
||||||
|
@ -242,6 +262,4 @@ func XBeeSend(broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}, trsp
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
96
cmd/gotelem/cli/socketcan.go
Normal file
96
cmd/gotelem/cli/socketcan.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kschamplin/gotelem"
|
||||||
|
"github.com/kschamplin/gotelem/socketcan"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
|
||||||
|
var canDevFlag = &cli.StringFlag{
|
||||||
|
Name: "can",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Usage: "CAN device string",
|
||||||
|
EnvVars: []string{"CAN_DEVICE"},
|
||||||
|
DefaultText: "vcan0",
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function sets up the `serve` flags and services that use socketCAN
|
||||||
|
func init() {
|
||||||
|
serveFlags = append(serveFlags, &cli.BoolFlag{Name: "test", Usage: "use vcan0 test"})
|
||||||
|
serveFlags = append(serveFlags, canDevFlag)
|
||||||
|
// add services for server
|
||||||
|
|
||||||
|
serveThings = append(serveThings, socketCANService)
|
||||||
|
|
||||||
|
// add can subcommand/actions
|
||||||
|
// TODO: make socketcan utility commands.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME: add logging back in since it's missing rn
|
||||||
|
|
||||||
|
func socketCANService(cCtx *cli.Context, broker *gotelem.Broker) (err error) {
|
||||||
|
rxCh := broker.Subscribe("socketCAN")
|
||||||
|
sock, err := socketcan.NewCanSocket(cCtx.String("can"))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rxCan := make(chan gotelem.Frame)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
pkt, _ := sock.Recv()
|
||||||
|
rxCan <- *pkt
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg := <-rxCh:
|
||||||
|
sock.Send(&msg)
|
||||||
|
case msg := <-rxCan:
|
||||||
|
broker.Publish("socketCAN", msg)
|
||||||
|
case <-cCtx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var socketCANCmd = &cli.Command{
|
||||||
|
Name: "can",
|
||||||
|
Usage: "SocketCAN utilities",
|
||||||
|
Description: `
|
||||||
|
Various helper utilties for CAN bus on sockets.
|
||||||
|
|
||||||
|
`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
canDevFlag,
|
||||||
|
},
|
||||||
|
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "dump",
|
||||||
|
Usage: "dump CAN packets to stdout",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "decode",
|
||||||
|
Usage: "unpack known CAN packets to JSON using skylab files",
|
||||||
|
Value: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,12 @@ func (d *DataField) MakeMarshal(offset int) string {
|
||||||
|
|
||||||
} else if t ,ok := typeMap[d.Type]; ok {
|
} else if t ,ok := typeMap[d.Type]; ok {
|
||||||
// it's uint or int of some kind, use endian to write it.
|
// it's uint or int of some kind, use endian to write it.
|
||||||
|
if strings.HasPrefix(t, "i") {
|
||||||
|
// this means it's a signed integer.
|
||||||
|
// encoding/binary does not support putting signed ints, instead
|
||||||
|
// we should cast it to unsigned and then use the unsigned int functions.
|
||||||
|
return fmt.Sprintf("binary.LittleEndian.PutU%s(b[%d:], u%s(p.%s))", t, offset, t, fieldName)
|
||||||
|
}
|
||||||
return fmt.Sprintf("binary.LittleEndian.Put%s(b[%d:], p.%s)", toCamelInitCase(t, true), offset, fieldName)
|
return fmt.Sprintf("binary.LittleEndian.Put%s(b[%d:], p.%s)", toCamelInitCase(t, true), offset, fieldName)
|
||||||
}
|
}
|
||||||
return "panic(\"failed to do it\")\n"
|
return "panic(\"failed to do it\")\n"
|
||||||
|
@ -169,9 +175,15 @@ func (d *DataField) MakeUnmarshal(offset int) string {
|
||||||
|
|
||||||
return fmt.Sprintf("p.%s = float32FromBytes(b[%d:], false)", fieldName, offset)
|
return fmt.Sprintf("p.%s = float32FromBytes(b[%d:], false)", fieldName, offset)
|
||||||
|
|
||||||
} else if t ,ok := typeMap[d.Type]; ok {
|
} else if t, ok := typeMap[d.Type]; ok {
|
||||||
// it's uint or int of some kind, use endian to read it.
|
// it's uint or int of some kind, use endian to read it.
|
||||||
// FIXME: support big endian
|
// FIXME: support big endian
|
||||||
|
if strings.HasPrefix(t, "i") {
|
||||||
|
// this means it's a signed integer.
|
||||||
|
// encoding/binary does not support putting signed ints, instead
|
||||||
|
// we should cast it to unsigned and then use the unsigned int functions.
|
||||||
|
return fmt.Sprintf("p.%s = %s(binary.LittleEndian.U%s(b[%d:]))", fieldName, t, t, offset)
|
||||||
|
}
|
||||||
return fmt.Sprintf("p.%s = binary.LittleEndian.%s(b[%d:])", fieldName, toCamelInitCase(t, true), offset)
|
return fmt.Sprintf("p.%s = binary.LittleEndian.%s(b[%d:])", fieldName, toCamelInitCase(t, true), offset)
|
||||||
}
|
}
|
||||||
panic("unhandled type")
|
panic("unhandled type")
|
||||||
|
@ -323,17 +335,22 @@ func main() {
|
||||||
|
|
||||||
fmt.Printf("running %s on %s\n", os.Args[0], os.Getenv("GOFILE"))
|
fmt.Printf("running %s on %s\n", os.Args[0], os.Getenv("GOFILE"))
|
||||||
|
|
||||||
fmt.Printf("skylab packet definition path is %s\n", os.Args[1])
|
basePath, err := filepath.Abs(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("skylab packet definition path is %s\n", basePath)
|
||||||
|
|
||||||
fGlob := filepath.Join(os.Args[1], "*.y?ml")
|
fGlob := filepath.Join(basePath, "*.y?ml")
|
||||||
files, err := filepath.Glob(fGlob)
|
files, err := filepath.Glob(fGlob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
fmt.Printf("found %d files\n", len(files))
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
fd, err := os.Open(f)
|
fd, err := os.Open(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to open file %s:%v\n", f, err)
|
fmt.Printf("failed to open file %s:%v\n", filepath.Base(f), err)
|
||||||
}
|
}
|
||||||
dec := yaml.NewDecoder(fd)
|
dec := yaml.NewDecoder(fd)
|
||||||
newFile := &SkylabFile{}
|
newFile := &SkylabFile{}
|
||||||
|
@ -341,7 +358,7 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
fmt.Printf("adding %d packets and %d boards\n", len(newFile.Packets), len(newFile.Boards))
|
fmt.Printf("%s: adding %d packets and %d boards\n", filepath.Base(f), len(newFile.Packets), len(newFile.Boards))
|
||||||
v.Packets = append(v.Packets, newFile.Packets...)
|
v.Packets = append(v.Packets, newFile.Packets...)
|
||||||
v.Boards = append(v.Boards, newFile.Boards...)
|
v.Boards = append(v.Boards, newFile.Boards...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ func float32FromBytes(b []byte, bigEndian bool) (f float32) {
|
||||||
type Packet interface {
|
type Packet interface {
|
||||||
MarshalPacket() ([]byte, error)
|
MarshalPacket() ([]byte, error)
|
||||||
UnmarshalPacket(p []byte) error
|
UnmarshalPacket(p []byte) error
|
||||||
Id() (uint32, error)
|
CANId() (uint32, error)
|
||||||
Size() uint
|
Size() uint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ type Unmarshaler interface {
|
||||||
|
|
||||||
// Ider is a packet that can get its ID, based on the index of the packet, if any.
|
// Ider is a packet that can get its ID, based on the index of the packet, if any.
|
||||||
type Ider interface {
|
type Ider interface {
|
||||||
Id() (uint32, error)
|
CANId() (uint32, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sizer allows for fast allocation.
|
// Sizer allows for fast allocation.
|
||||||
|
@ -63,7 +63,7 @@ type Sizer interface {
|
||||||
// CanSend takes a packet and makes CAN framing data.
|
// CanSend takes a packet and makes CAN framing data.
|
||||||
func CanSend(p Packet) (id uint32, data []byte, err error) {
|
func CanSend(p Packet) (id uint32, data []byte, err error) {
|
||||||
|
|
||||||
id, err = p.Id()
|
id, err = p.CANId()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ func ToJson(p Packet) (*JSONPacket, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := p.Id()
|
id, err := p.CANId()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,9 @@ func (p *{{$bfname}}) Unmarshal(b byte) {
|
||||||
// {{$structName}} is {{.Description}}
|
// {{$structName}} is {{.Description}}
|
||||||
type {{$structName}} struct {
|
type {{$structName}} struct {
|
||||||
{{- range .Data}}
|
{{- range .Data}}
|
||||||
{{- if .Units -}} // {{.Conversion}} {{.Units}} {{- end }}
|
{{- if .Units }}
|
||||||
|
// {{.Conversion}} {{.Units}}
|
||||||
|
{{- end }}
|
||||||
{{ .ToStructMember $structName }} `json:"{{.Name}}"`
|
{{ .ToStructMember $structName }} `json:"{{.Name}}"`
|
||||||
{{- end}}
|
{{- end}}
|
||||||
{{- if .Repeat }}
|
{{- if .Repeat }}
|
||||||
|
@ -43,7 +45,7 @@ type {{$structName}} struct {
|
||||||
{{- end }}
|
{{- end }}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *{{$structName}}) Id() (uint32, error) {
|
func (p *{{$structName}}) CANId() (uint32, error) {
|
||||||
{{- if .Repeat }}
|
{{- if .Repeat }}
|
||||||
if p.Idx >= {{.Repeat}} {
|
if p.Idx >= {{.Repeat}} {
|
||||||
return 0, errors.New("invalid packet index")
|
return 0, errors.New("invalid packet index")
|
||||||
|
|
Loading…
Reference in a new issue