diff --git a/cmd/skylabify/skylabify.go b/cmd/skylabify/skylabify.go new file mode 100644 index 0000000..ff8cd95 --- /dev/null +++ b/cmd/skylabify/skylabify.go @@ -0,0 +1,131 @@ +package main + +import ( + "bufio" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "strconv" + "strings" + "syscall" + + "github.com/kschamplin/gotelem/skylab" + "github.com/urfave/cli/v2" +) + +// this command can be used to decode candump logs and dump json output. + +func main() { + + app := cli.NewApp() + app.Name = "skylabify" + app.Usage = "decode skylab packets" + app.ArgsUsage = "" + app.Commands = nil + app.Description = `skylabify can read in candump logs and output newline-delimited JSON. +It is designed to make reading candumps fast and easy. + +skylabify can be combined with jq and candump to allow for advanced queries. + +Examples: + skylabify candump.txt + + candump -L can0 | skylabify - + + skylabify previous_candump.txt | jq + +I highly suggest reading the manpages for candump and jq. The -L option is +required for piping candump into skylabify. Likewise, data should be stored with +-l. + +` + + app.Flags = []cli.Flag{ + &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"v"}, + }, + } + + app.Action = run + app.HideHelp = true + if err := app.Run(os.Args); err != nil { + panic(err) + } +} + +func run(ctx *cli.Context) (err error) { + path := ctx.Args().Get(0) + if path == "" { + fmt.Printf("missing input file\n") + cli.ShowAppHelpAndExit(ctx, int(syscall.EINVAL)) + } + + var istream *os.File + if path == "-" { + istream = os.Stdin + } else { + istream, err = os.Open(path) + if err != nil { + return + } + } + + canDumpReader := bufio.NewReader(istream) + + for { + // dumpline looks like this: + // (1684538768.521889) can0 200#8D643546 + dumpLine, err := canDumpReader.ReadString('\n') + if err != nil { + if errors.Is(err, io.EOF) { + return nil + } + return err + } + // remove trailing newline + dumpLine = strings.TrimSpace(dumpLine) + + segments := strings.Split(dumpLine, " ") + + var cd candumpJSON + // this is cursed but easiest way to get a float from a string. + fmt.Sscanf(segments[0], "(%g)", &cd.Timestamp) + + // this is for the latter part, we need to split id/data + hexes := strings.Split(segments[2], "#") + + // get the id + cd.Id, err = strconv.ParseUint(hexes[0], 16, 64) + if err != nil { + return err + } + + // get the data to a []byte + rawData, err := hex.DecodeString(hexes[1]) + if err != nil { + return err + } + + // parse the data []byte to a skylab packet + cd.Data, err = skylab.FromCanFrame(uint32(cd.Id), rawData) + if err != nil { + return err + } + + // format and print out. + out, _ := json.Marshal(cd) + fmt.Printf("%s\n", out) + + } + return nil +} + +type candumpJSON struct { + Timestamp float64 `json:"ts"` + Id uint64 `json:"id"` + Data skylab.Packet `json:"data"` +}