From 47a818a4519d1148866014677a7e18d1d2e2cb00 Mon Sep 17 00:00:00 2001 From: saji Date: Thu, 18 May 2023 17:47:14 -0500 Subject: [PATCH] major wins --- cmd/gotelem/cli/root.go | 11 ++-- cmd/gotelem/cli/server.go | 80 ++++++++++++++++----------- cmd/gotelem/cli/socketcan.go | 96 +++++++++++++++++++++++++++++++++ skylab/gen_skylab.go | 27 ++++++++-- skylab/skylab.go | 8 +-- skylab/templates/golang.go.tmpl | 6 ++- 6 files changed, 182 insertions(+), 46 deletions(-) create mode 100644 cmd/gotelem/cli/socketcan.go diff --git a/cmd/gotelem/cli/root.go b/cmd/gotelem/cli/root.go index d39c26d..973e74a 100644 --- a/cmd/gotelem/cli/root.go +++ b/cmd/gotelem/cli/root.go @@ -7,14 +7,17 @@ import ( "github.com/urfave/cli/v2" ) +var subCmds = []*cli.Command{ + serveCmd, + xbeeCmd, +} + + func Execute() { app := &cli.App{ Name: "gotelem", Usage: "see everything", - Commands: []*cli.Command{ - serveCmd, - xbeeCmd, - }, + Commands: subCmds, } if err := app.Run(os.Args); err != nil { diff --git a/cmd/gotelem/cli/server.go b/cmd/gotelem/cli/server.go index eebc241..97c0355 100644 --- a/cmd/gotelem/cli/server.go +++ b/cmd/gotelem/cli/server.go @@ -15,34 +15,45 @@ import ( "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{ Name: "serve", Aliases: []string{"server", "s"}, Usage: "Start a telemetry server", - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "test", Usage: "use vcan0 test"}, - &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, + Flags: serveFlags, + 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 { // TODO: output both to stderr and a file. logger := slog.New(slog.NewTextHandler(os.Stderr)) @@ -55,7 +66,6 @@ func serve(cCtx *cli.Context) error { // can logger. go CanDump(broker, logger.WithGroup("candump"), done) - if cCtx.String("device") != "" { logger.Info("using xbee 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{}) { // reader := msgp.NewReader(conn) - subname := fmt.Sprint("tcp", conn.RemoteAddr().String()) 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. func vcanTest(devname string) { 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) { rxCh := broker.Subscribe("socketcan") sock, err := socketcan.NewCanSocket(devname) - if err != nil { l.Error("error opening socket", "err", err) 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) { rxCh := broker.Subscribe("xbee") l.Info("starting xbee send routine") xb, err := xbee.NewSession(trspt, l.With("device", trspt.Type())) - if err != nil { l.Error("failed to start xbee session", "err", err) return @@ -242,6 +262,4 @@ func XBeeSend(broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}, trsp } } - - } diff --git a/cmd/gotelem/cli/socketcan.go b/cmd/gotelem/cli/socketcan.go new file mode 100644 index 0000000..a09f5d1 --- /dev/null +++ b/cmd/gotelem/cli/socketcan.go @@ -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, + }, + }, + + }, + }, +} + + diff --git a/skylab/gen_skylab.go b/skylab/gen_skylab.go index b37baf4..9f954cd 100644 --- a/skylab/gen_skylab.go +++ b/skylab/gen_skylab.go @@ -152,6 +152,12 @@ func (d *DataField) MakeMarshal(offset int) string { } else if t ,ok := typeMap[d.Type]; ok { // 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 "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) - } 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. // 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) } panic("unhandled type") @@ -323,17 +335,22 @@ func main() { 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) if err != nil { panic(err) } + fmt.Printf("found %d files\n", len(files)) for _, f := range files { fd, err := os.Open(f) 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) newFile := &SkylabFile{} @@ -341,7 +358,7 @@ func main() { if err != nil { 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.Boards = append(v.Boards, newFile.Boards...) } diff --git a/skylab/skylab.go b/skylab/skylab.go index 6e37e75..9aeb948 100644 --- a/skylab/skylab.go +++ b/skylab/skylab.go @@ -36,7 +36,7 @@ func float32FromBytes(b []byte, bigEndian bool) (f float32) { type Packet interface { MarshalPacket() ([]byte, error) UnmarshalPacket(p []byte) error - Id() (uint32, error) + CANId() (uint32, error) 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. type Ider interface { - Id() (uint32, error) + CANId() (uint32, error) } // Sizer allows for fast allocation. @@ -63,7 +63,7 @@ type Sizer interface { // CanSend takes a packet and makes CAN framing data. func CanSend(p Packet) (id uint32, data []byte, err error) { - id, err = p.Id() + id, err = p.CANId() if err != nil { return } @@ -86,7 +86,7 @@ func ToJson(p Packet) (*JSONPacket, error) { return nil, err } - id, err := p.Id() + id, err := p.CANId() if err != nil { return nil, err } diff --git a/skylab/templates/golang.go.tmpl b/skylab/templates/golang.go.tmpl index 7a4d4c8..b31919e 100644 --- a/skylab/templates/golang.go.tmpl +++ b/skylab/templates/golang.go.tmpl @@ -34,7 +34,9 @@ func (p *{{$bfname}}) Unmarshal(b byte) { // {{$structName}} is {{.Description}} type {{$structName}} struct { {{- range .Data}} - {{- if .Units -}} // {{.Conversion}} {{.Units}} {{- end }} + {{- if .Units }} + // {{.Conversion}} {{.Units}} + {{- end }} {{ .ToStructMember $structName }} `json:"{{.Name}}"` {{- end}} {{- if .Repeat }} @@ -43,7 +45,7 @@ type {{$structName}} struct { {{- end }} } -func (p *{{$structName}}) Id() (uint32, error) { +func (p *{{$structName}}) CANId() (uint32, error) { {{- if .Repeat }} if p.Idx >= {{.Repeat}} { return 0, errors.New("invalid packet index")