major wins

This commit is contained in:
saji 2023-05-18 17:47:14 -05:00
parent eefc511f4f
commit 47a818a451
6 changed files with 182 additions and 46 deletions

View file

@ -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 {

View file

@ -15,34 +15,45 @@ import (
"golang.org/x/exp/slog" "golang.org/x/exp/slog"
) )
var serveCmd = &cli.Command{ var serveFlags = []cli.Flag{
Name: "serve",
Aliases: []string{"server", "s"},
Usage: "Start a telemetry server",
Flags: []cli.Flag{
&cli.BoolFlag{Name: "test", Usage: "use vcan0 test"},
&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",
EnvVars: []string{"XBEE_DEVICE"}, EnvVars: []string{"XBEE_DEVICE"},
}, },
&cli.StringFlag{
Name: "can",
Aliases: []string{"c"},
Usage: "CAN device string",
EnvVars: []string{"CAN_DEVICE"},
},
&cli.StringFlag{ &cli.StringFlag{
Name: "logfile", Name: "logfile",
Aliases: []string{"l"}, Aliases: []string{"l"},
Value: "log.txt", Value: "log.txt",
Usage: "file to store log to", Usage: "file to store log to",
}, },
}, }
var serveCmd = &cli.Command{
Name: "serve",
Aliases: []string{"server", "s"},
Usage: "Start a telemetry server",
Flags: serveFlags,
Action: serve, 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
} }
} }
} }

View 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,
},
},
},
},
}

View file

@ -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...)
} }

View file

@ -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
} }

View file

@ -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")