misc things
This commit is contained in:
parent
de57b38958
commit
5b64a2bef2
|
@ -1,13 +1,18 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kschamplin/gotelem"
|
"github.com/kschamplin/gotelem"
|
||||||
"github.com/kschamplin/gotelem/socketcan"
|
"github.com/kschamplin/gotelem/socketcan"
|
||||||
|
"github.com/kschamplin/gotelem/xbee"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var serveCmd = &cli.Command{
|
var serveCmd = &cli.Command{
|
||||||
|
@ -15,49 +20,116 @@ var serveCmd = &cli.Command{
|
||||||
Aliases: []string{"server", "s"},
|
Aliases: []string{"server", "s"},
|
||||||
Usage: "Start a telemetry server",
|
Usage: "Start a telemetry server",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.BoolFlag{Name: "xbee", Aliases: []string{"x"}, Usage: "Find and connect to an XBee"},
|
&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"},
|
||||||
},
|
},
|
||||||
Action: func(ctx *cli.Context) error {
|
&cli.StringFlag{
|
||||||
serve(ctx.Bool("xbee"))
|
Name: "can",
|
||||||
return nil
|
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
func serve(useXbee bool) {
|
func serve(cCtx *cli.Context) error {
|
||||||
|
// TODO: output both to stderr and a file.
|
||||||
|
logger := slog.New(slog.NewTextHandler(os.Stderr))
|
||||||
|
|
||||||
|
slog.SetDefault(logger)
|
||||||
broker := gotelem.NewBroker(3)
|
broker := gotelem.NewBroker(3)
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
// start the can listener
|
// start the can listener
|
||||||
go vcanTest()
|
// can logger.
|
||||||
go canHandler(broker)
|
go CanDump(broker, logger.WithGroup("candump"), done)
|
||||||
|
|
||||||
|
|
||||||
|
if cCtx.String("device") != "" {
|
||||||
|
logger.Info("using xbee device")
|
||||||
|
transport, err := xbee.ParseDeviceString(cCtx.String("device"))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to open device string", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
go XBeeSend(broker, logger.WithGroup("xbee"), done, transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
// tcp listener server.
|
||||||
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)
|
||||||
}
|
}
|
||||||
fmt.Printf("Listening on :8082\n")
|
logger.Info("TCP listener started", "addr", ln.Addr().String())
|
||||||
|
|
||||||
for {
|
for {
|
||||||
conn, err := ln.Accept()
|
conn, err := ln.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("error accepting: %v\n", err)
|
fmt.Printf("error accepting: %v\n", err)
|
||||||
}
|
}
|
||||||
go handleCon(conn, broker)
|
go handleCon(conn, broker, logger.WithGroup("tcp"), done)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCon(conn net.Conn, broker *gotelem.Broker) {
|
func handleCon(conn net.Conn, broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}) {
|
||||||
// reader := msgp.NewReader(conn)
|
// reader := msgp.NewReader(conn)
|
||||||
|
|
||||||
conn.Close()
|
subname := fmt.Sprint("hi", conn.RemoteAddr().String())
|
||||||
}
|
|
||||||
|
|
||||||
func xbeeSvc(b *gotelem.Broker) {
|
rxCh := broker.Subscribe(subname)
|
||||||
|
defer broker.Unsubscribe(subname)
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg := <-rxCh:
|
||||||
|
// FIXME: poorly optimized
|
||||||
|
buf := make([]byte, 0, 8)
|
||||||
|
binary.LittleEndian.AppendUint32(buf, msg.Id)
|
||||||
|
buf = append(buf, msg.Data...)
|
||||||
|
|
||||||
|
_, err := conn.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
l.Warn("error writing tcp packet", "err", err)
|
||||||
|
}
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 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() {
|
func vcanTest(devname string) {
|
||||||
sock, _ := socketcan.NewCanSocket("vcan0")
|
sock, err := socketcan.NewCanSocket(devname)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error opening socket", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
testFrame := &gotelem.Frame{
|
testFrame := &gotelem.Frame{
|
||||||
Id: 0x234,
|
Id: 0x234,
|
||||||
Kind: gotelem.CanSFFFrame,
|
Kind: gotelem.CanSFFFrame,
|
||||||
|
@ -65,31 +137,106 @@ func vcanTest() {
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
|
|
||||||
fmt.Printf("sending test packet\n")
|
slog.Info("sending test packet")
|
||||||
sock.Send(testFrame)
|
sock.Send(testFrame)
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func canHandler(broker *gotelem.Broker) {
|
// connects the broker to a socket can
|
||||||
|
func canHandler(broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}, devname string) {
|
||||||
rxCh := broker.Subscribe("socketcan")
|
rxCh := broker.Subscribe("socketcan")
|
||||||
sock, _ := socketcan.NewCanSocket("vcan0")
|
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.
|
// start a simple dispatcher that just relays can frames.
|
||||||
rxCan := make(chan gotelem.Frame)
|
rxCan := make(chan gotelem.Frame)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
pkt, _ := sock.Recv()
|
pkt, err := sock.Recv()
|
||||||
|
if err != nil {
|
||||||
|
l.Warn("error reading SocketCAN", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
rxCan <- *pkt
|
rxCan <- *pkt
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case msg := <-rxCh:
|
case msg := <-rxCh:
|
||||||
|
l.Info("Sending a CAN bus message", "id", msg.Id, "data", msg.Data)
|
||||||
sock.Send(&msg)
|
sock.Send(&msg)
|
||||||
case msg := <-rxCan:
|
case msg := <-rxCan:
|
||||||
fmt.Printf("got a packet from the can %v\n", msg)
|
l.Info("Got a CAN bus message", "id", msg.Id, "data", msg.Data)
|
||||||
broker.Publish("socketcan", msg)
|
broker.Publish("socketcan", msg)
|
||||||
|
case <-done:
|
||||||
|
sock.Close()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CanDump(broker *gotelem.Broker, l *slog.Logger, done <-chan struct{}) {
|
||||||
|
rxCh := broker.Subscribe("candump")
|
||||||
|
t := time.Now()
|
||||||
|
fname := fmt.Sprintf("candump_%d-%02d-%02dT%02d.%02d.%02d.txt",
|
||||||
|
t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())
|
||||||
|
|
||||||
|
cw, err := gotelem.OpenCanWriter(fname)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error opening file", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg := <-rxCh:
|
||||||
|
|
||||||
|
cw.Send(&msg)
|
||||||
|
case <-done:
|
||||||
|
cw.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Info("connected to local xbee", "addr", xb.LocalAddr())
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
xb.Close()
|
||||||
|
return
|
||||||
|
case msg := <-rxCh:
|
||||||
|
// TODO: take can message and send it over CAN.
|
||||||
|
l.Info("got msg", "msg", msg)
|
||||||
|
buf := make([]byte, 0)
|
||||||
|
|
||||||
|
binary.LittleEndian.AppendUint32(buf, msg.Id)
|
||||||
|
buf = append(buf, msg.Data...)
|
||||||
|
|
||||||
|
_, err := xb.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
l.Warn("error writing to xbee", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,15 @@ const (
|
||||||
keyIODevice ctxKey = iota
|
keyIODevice ctxKey = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var xbeeDeviceFlag = &cli.StringFlag{
|
||||||
|
Name: "device",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "The XBee to connect to",
|
||||||
|
Required: true,
|
||||||
|
EnvVars: []string{"XBEE_DEVICE"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var xbeeCmd = &cli.Command{
|
var xbeeCmd = &cli.Command{
|
||||||
Name: "xbee",
|
Name: "xbee",
|
||||||
Aliases: []string{"x"},
|
Aliases: []string{"x"},
|
||||||
|
@ -40,13 +49,7 @@ TCP/UDP connections require a port and will fail if one is not provided.
|
||||||
|
|
||||||
`,
|
`,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
xbeeDeviceFlag,
|
||||||
Name: "device",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "The XBee to connect to",
|
|
||||||
Required: true,
|
|
||||||
EnvVars: []string{"XBEE_DEVICE"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// this parses the device string and creates the io device.
|
// this parses the device string and creates the io device.
|
||||||
// TODO: should we create the session here instead?
|
// TODO: should we create the session here instead?
|
||||||
|
@ -97,7 +100,7 @@ func xbeeInfo(ctx *cli.Context) error {
|
||||||
return cli.Exit(err, 1)
|
return cli.Exit(err, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := xb.ATCommand([2]rune{'I', 'D'}, nil, false)
|
b, err := xb.ATCommand([2]byte{'I', 'D'}, nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.Exit(err, 1)
|
return cli.Exit(err, 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
//go:build ignore
|
|
||||||
|
|
||||||
// this file is a generator for skylab code.
|
|
||||||
package main
|
|
||||||
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Field interface {
|
|
||||||
Name() string
|
|
||||||
|
|
||||||
Size() int // the size of the data.
|
|
||||||
|
|
||||||
// returns something like
|
|
||||||
// AuxVoltage uint16
|
|
||||||
// used inside the packet struct
|
|
||||||
Embed() string
|
|
||||||
|
|
||||||
// returns
|
|
||||||
Marshal() string
|
|
||||||
Decode() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is a standard field, not a bitfield.
|
|
||||||
type DataField struct {
|
|
||||||
Name string
|
|
||||||
Type string
|
|
||||||
Units string // mostly for documentation
|
|
||||||
Conversion float32
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// a PacketDef is a full can packet.
|
|
||||||
type PacketDef struct {
|
|
||||||
Name string
|
|
||||||
Description string
|
|
||||||
Id uint32
|
|
||||||
BigEndian bool
|
|
||||||
data: []Field
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need to generate bitfield types.
|
|
||||||
// packet structs per each packet
|
|
||||||
// constancts for packet IDs or a map.
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
|
|
||||||
example for a simple packet type
|
|
||||||
it also needs a json marshalling.
|
|
||||||
|
|
||||||
type BMSMeasurement struct {
|
|
||||||
BatteryVoltage uint16
|
|
||||||
AuxVoltage uint16
|
|
||||||
Current float32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BMSMeasurement)MarshalPacket() ([]byte, error) {
|
|
||||||
pkt := make([]byte, b.Size())
|
|
||||||
binary.LittleEndian.PutUint16(pkt[0:], b.BatteryVoltage * 0.01)
|
|
||||||
binary.LittleEndian.PutUint16(pkt[2:],b.AuxVoltage * 0.001)
|
|
||||||
binary.LittleEndian.PutFloat32(b.Current) // TODO: make float function
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BMSMeasurement)UnmarshalPacket(p []byte) error {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BMSMeasurement) Id() uint32 {
|
|
||||||
return 0x010
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BMSMeasurement) Size() int {
|
|
||||||
return 8
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BMSMeasurement) String() string {
|
|
||||||
return "blah blah"
|
|
||||||
}
|
|
||||||
|
|
||||||
we also need some kind of mechanism to lookup data type.
|
|
||||||
|
|
||||||
func getPkt (id uint32, data []byte) (Packet, error) {
|
|
||||||
|
|
||||||
// insert really massive switch case statement here.
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
1
go.mod
1
go.mod
|
@ -19,4 +19,5 @@ require (
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/stretchr/testify v1.8.0 // indirect
|
github.com/stretchr/testify v1.8.0 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
263
skylab/gen_skylab.go
Normal file
263
skylab/gen_skylab.go
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
//go:build ignore
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
// this file is a generator for skylab code.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// data field.
|
||||||
|
type DataField struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
Units string // mostly for documentation
|
||||||
|
Conversion float32
|
||||||
|
Bits []struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a PacketDef is a full can packet.
|
||||||
|
type PacketDef struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
Id uint32
|
||||||
|
BigEndian bool
|
||||||
|
Repeat int
|
||||||
|
Offset int
|
||||||
|
Data []DataField
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to generate bitfield types.
|
||||||
|
// packet structs per each packet
|
||||||
|
// constancts for packet IDs or a map.
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
|
||||||
|
example for a simple packet type
|
||||||
|
it also needs a json marshalling.
|
||||||
|
|
||||||
|
type BMSMeasurement struct {
|
||||||
|
BatteryVoltage uint16
|
||||||
|
AuxVoltage uint16
|
||||||
|
Current float32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BMSMeasurement)MarshalPacket() ([]byte, error) {
|
||||||
|
pkt := make([]byte, b.Size())
|
||||||
|
binary.LittleEndian.PutUint16(pkt[0:], b.BatteryVoltage * 0.01)
|
||||||
|
binary.LittleEndian.PutUint16(pkt[2:],b.AuxVoltage * 0.001)
|
||||||
|
binary.LittleEndian.PutFloat32(b.Current) // TODO: make float function
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BMSMeasurement)UnmarshalPacket(p []byte) error {
|
||||||
|
// the opposite of above.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BMSMeasurement) Id() uint32 {
|
||||||
|
return 0x010
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BMSMeasurement) Size() int {
|
||||||
|
return 8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BMSMeasurement) String() string {
|
||||||
|
return "blah blah"
|
||||||
|
}
|
||||||
|
|
||||||
|
we also need some kind of mechanism to lookup data type.
|
||||||
|
|
||||||
|
func getPkt (id uint32, data []byte) (Packet, error) {
|
||||||
|
|
||||||
|
// insert really massive switch case statement here.
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
var test = `
|
||||||
|
packets:
|
||||||
|
- name: dashboard_pedal_percentages
|
||||||
|
description: ADC values from the brake and accelerator pedals.
|
||||||
|
id: 0x290
|
||||||
|
endian: little
|
||||||
|
frequency: 10
|
||||||
|
data:
|
||||||
|
- name: accel_pedal_value
|
||||||
|
type: uint8_t
|
||||||
|
- name: brake_pedal_value
|
||||||
|
type: uint8_t
|
||||||
|
`
|
||||||
|
|
||||||
|
type SkylabFile struct {
|
||||||
|
Packets []PacketDef
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeMap = map[string]string{
|
||||||
|
"uint16_t": "uint16",
|
||||||
|
"uint32_t": "uint32",
|
||||||
|
"uint64_t": "uint64",
|
||||||
|
"uint8_t": "uint8",
|
||||||
|
"float": "float32",
|
||||||
|
|
||||||
|
"int16_t": "int16",
|
||||||
|
"int32_t": "int32",
|
||||||
|
"int64_t": "int64",
|
||||||
|
"int8_t": "int8",
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeSizeMap = map[string]uint{
|
||||||
|
"uint16_t": 2,
|
||||||
|
"uint32_t": 4,
|
||||||
|
"uint64_t": 8,
|
||||||
|
"uint8_t": 1,
|
||||||
|
"float": 4,
|
||||||
|
|
||||||
|
"int16_t": 2,
|
||||||
|
"int32_t": 4,
|
||||||
|
"int64_t": 8,
|
||||||
|
"int8_t": 1,
|
||||||
|
"bitfield": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DataField) ToStructMember() string {
|
||||||
|
if d.Type != "bitfield" {
|
||||||
|
return toCamelInitCase(d.Name, true) + " " + typeMap[d.Type]
|
||||||
|
}
|
||||||
|
// it's a bitfield, things are more complicated.
|
||||||
|
slog.Warn("bitfields are skipped for now")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func (p PacketDef) Size() int {
|
||||||
|
// makes a function that returns the size of the code.
|
||||||
|
|
||||||
|
var size int = 0
|
||||||
|
for _, val := range p.Data {
|
||||||
|
size += int(typeSizeMap[val.Type])
|
||||||
|
}
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (p PacketDef) MakeMarshal() string {
|
||||||
|
var buf strings.Builder
|
||||||
|
|
||||||
|
var offset int = 0
|
||||||
|
// we have a b []byte as the correct-size byte array to store in.
|
||||||
|
// and the packet itself is represented as `p`
|
||||||
|
for _, val := range p.Data {
|
||||||
|
if val.Type == "uint8_t" || val.Type == "int8_t" {
|
||||||
|
buf.WriteString(fmt.Sprintf("b[%d] = p.%s\n", offset, toCamelInitCase(val.Name, true)))
|
||||||
|
} else if val.Type == "bitfield" {
|
||||||
|
|
||||||
|
} else if val.Type == "float" {
|
||||||
|
|
||||||
|
} else if name,ok := typeMap[val.Type]; ok {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
offset += int(typeSizeMap[val.Type])
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var templ = `
|
||||||
|
// go code generated! don't touch!
|
||||||
|
{{ $structName := camelCase .Name true}}
|
||||||
|
// {{$structName}} is {{.Description}}
|
||||||
|
type {{$structName}} struct {
|
||||||
|
{{- range .Data}}
|
||||||
|
{{.ToStructMember}}
|
||||||
|
{{- end}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *{{$structName}}) Id() uint32 {
|
||||||
|
return {{.Id}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *{{$structName}}) Size() int {
|
||||||
|
return {{.Size}}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
|
||||||
|
// stolen camelCaser code. initCase = true means CamelCase, false means camelCase
|
||||||
|
func toCamelInitCase(s string, initCase bool) string {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if s == "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
n := strings.Builder{}
|
||||||
|
n.Grow(len(s))
|
||||||
|
capNext := initCase
|
||||||
|
for i, v := range []byte(s) {
|
||||||
|
vIsCap := v >= 'A' && v <= 'Z'
|
||||||
|
vIsLow := v >= 'a' && v <= 'z'
|
||||||
|
if capNext {
|
||||||
|
if vIsLow {
|
||||||
|
v += 'A'
|
||||||
|
v -= 'a'
|
||||||
|
}
|
||||||
|
} else if i == 0 {
|
||||||
|
if vIsCap {
|
||||||
|
v += 'a'
|
||||||
|
v -= 'A'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vIsCap || vIsLow {
|
||||||
|
n.WriteByte(v)
|
||||||
|
capNext = false
|
||||||
|
} else if vIsNum := v >= '0' && v <= '9'; vIsNum {
|
||||||
|
n.WriteByte(v)
|
||||||
|
capNext = true
|
||||||
|
} else {
|
||||||
|
capNext = v == '_' || v == ' ' || v == '-' || v == '.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
v := &SkylabFile{}
|
||||||
|
|
||||||
|
err := yaml.Unmarshal([]byte(test), v)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("err %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%#v\n", v.Packets)
|
||||||
|
|
||||||
|
fnMap := template.FuncMap{
|
||||||
|
"camelCase": toCamelInitCase,
|
||||||
|
}
|
||||||
|
tmpl, err := template.New("packet").Funcs(fnMap).Parse(templ)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tmpl.Execute(os.Stdout, v.Packets[0])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ func (b RawATCmd) Bytes() []byte {
|
||||||
// EncodeATCommand takes an AT command and encodes it in the payload format.
|
// EncodeATCommand takes an AT command and encodes it in the payload format.
|
||||||
// it takes the frame index (which can be zero) as well as if it should be queued or
|
// it takes the frame index (which can be zero) as well as if it should be queued or
|
||||||
// not. It encodes the AT command to be framed and sent over the wire and returns the packet
|
// not. It encodes the AT command to be framed and sent over the wire and returns the packet
|
||||||
func encodeATCommand(cmd [2]rune, p []byte, idx uint8, queued bool) RawATCmd {
|
func encodeATCommand(cmd [2]byte, p []byte, idx uint8, queued bool) RawATCmd {
|
||||||
// we encode a new byte slice that contains the cmd + payload concatenated correclty.
|
// we encode a new byte slice that contains the cmd + payload concatenated correclty.
|
||||||
// this is then used to make the command frame, which contains ID/Type/Queued or not.
|
// this is then used to make the command frame, which contains ID/Type/Queued or not.
|
||||||
// the ATCmdFrame can be converted to bytes to be sent over the wire once framed.
|
// the ATCmdFrame can be converted to bytes to be sent over the wire once framed.
|
||||||
|
|
|
@ -90,7 +90,7 @@ func Test_encodeRemoteATCommand(t *testing.T) {
|
||||||
|
|
||||||
func Test_encodeATCommand(t *testing.T) {
|
func Test_encodeATCommand(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
cmd [2]rune
|
cmd [2]byte
|
||||||
p []byte
|
p []byte
|
||||||
idx uint8
|
idx uint8
|
||||||
queued bool
|
queued bool
|
||||||
|
@ -104,7 +104,7 @@ func Test_encodeATCommand(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "Setting AT Command",
|
name: "Setting AT Command",
|
||||||
args: args{
|
args: args{
|
||||||
cmd: [2]rune{'N', 'I'},
|
cmd: [2]byte{'N', 'I'},
|
||||||
idx: 0xA1,
|
idx: 0xA1,
|
||||||
p: []byte{0x45, 0x6E, 0x64, 0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65},
|
p: []byte{0x45, 0x6E, 0x64, 0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65},
|
||||||
queued: false,
|
queued: false,
|
||||||
|
@ -114,7 +114,7 @@ func Test_encodeATCommand(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "Query AT Command",
|
name: "Query AT Command",
|
||||||
args: args{
|
args: args{
|
||||||
cmd: [2]rune{'T', 'P'},
|
cmd: [2]byte{'T', 'P'},
|
||||||
idx: 0x17,
|
idx: 0x17,
|
||||||
p: nil,
|
p: nil,
|
||||||
queued: false,
|
queued: false,
|
||||||
|
@ -124,7 +124,7 @@ func Test_encodeATCommand(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "Queue Local AT Command",
|
name: "Queue Local AT Command",
|
||||||
args: args{
|
args: args{
|
||||||
cmd: [2]rune{'B', 'D'},
|
cmd: [2]byte{'B', 'D'},
|
||||||
idx: 0x53,
|
idx: 0x53,
|
||||||
p: []byte{0x07},
|
p: []byte{0x07},
|
||||||
queued: true,
|
queued: true,
|
||||||
|
@ -134,7 +134,7 @@ func Test_encodeATCommand(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "Queue Query AT Command",
|
name: "Queue Query AT Command",
|
||||||
args: args{
|
args: args{
|
||||||
cmd: [2]rune{'T', 'P'},
|
cmd: [2]byte{'T', 'P'},
|
||||||
idx: 0x17,
|
idx: 0x17,
|
||||||
p: nil,
|
p: nil,
|
||||||
queued: true,
|
queued: true,
|
||||||
|
|
|
@ -55,6 +55,9 @@ type Session struct {
|
||||||
// can only be one direct connection to a device. This is pretty reasonable IMO.
|
// can only be one direct connection to a device. This is pretty reasonable IMO.
|
||||||
// but needs to be documented very clearly.
|
// but needs to be documented very clearly.
|
||||||
conns map[uint64]*Conn
|
conns map[uint64]*Conn
|
||||||
|
|
||||||
|
// local address
|
||||||
|
lAddr XBeeAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSession takes an IO device and a logger and returns a new XBee session.
|
// NewSession takes an IO device and a logger and returns a new XBee session.
|
||||||
|
@ -76,6 +79,21 @@ func NewSession(dev io.ReadWriteCloser, baseLog *slog.Logger) (*Session, error)
|
||||||
|
|
||||||
go sess.rxHandler()
|
go sess.rxHandler()
|
||||||
|
|
||||||
|
|
||||||
|
// now we should get the local address cached so LocalAddr is fast.
|
||||||
|
sh, err := sess.ATCommand([2]byte{'S', 'H'}, nil, false)
|
||||||
|
if err != nil {
|
||||||
|
return sess, errors.New("error getting SH")
|
||||||
|
}
|
||||||
|
sl, err := sess.ATCommand([2]byte{'S', 'L'}, nil, false)
|
||||||
|
if err != nil {
|
||||||
|
return sess, errors.New("error getting SL")
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := append(sh, sl...)
|
||||||
|
|
||||||
|
sess.lAddr = XBeeAddr(binary.BigEndian.Uint64(addr))
|
||||||
|
|
||||||
return sess, nil
|
return sess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,10 +108,13 @@ func (sess *Session) rxHandler() {
|
||||||
scan := bufio.NewScanner(sess.ioDev)
|
scan := bufio.NewScanner(sess.ioDev)
|
||||||
scan.Split(xbeeFrameSplit)
|
scan.Split(xbeeFrameSplit)
|
||||||
|
|
||||||
|
sess.log.Debug("starting rx handler", "device", sess.ioDev)
|
||||||
|
|
||||||
// scan.Scan() will return false when there's EOF, i.e the io device is closed.
|
// scan.Scan() will return false when there's EOF, i.e the io device is closed.
|
||||||
// this is activated by sess.Close()
|
// this is activated by sess.Close()
|
||||||
for scan.Scan() {
|
for scan.Scan() {
|
||||||
data, err := parseFrame(scan.Bytes())
|
data, err := parseFrame(scan.Bytes())
|
||||||
|
sess.log.Debug("got an api frame", "data", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sess.log.Warn("error parsing frame", "error", err, "data", data)
|
sess.log.Warn("error parsing frame", "error", err, "data", data)
|
||||||
continue
|
continue
|
||||||
|
@ -207,7 +228,7 @@ func (sess *Session) writeAddr(p []byte, dest uint64) (n int, err error) {
|
||||||
// instead, an AC command must be set to apply the queued changes. `queued` does not
|
// instead, an AC command must be set to apply the queued changes. `queued` does not
|
||||||
// affect query-type commands, which always return right away.
|
// affect query-type commands, which always return right away.
|
||||||
// the AT command is an interface.
|
// the AT command is an interface.
|
||||||
func (sess *Session) ATCommand(cmd [2]rune, data []byte, queued bool) (payload []byte, err error) {
|
func (sess *Session) ATCommand(cmd [2]byte, data []byte, queued bool) (payload []byte, err error) {
|
||||||
// we must encode the command, and then create the actual packet.
|
// we must encode the command, and then create the actual packet.
|
||||||
// then we send the packet, and wait for the response
|
// then we send the packet, and wait for the response
|
||||||
// TODO: how to handle multiple-response-packet AT commands?
|
// TODO: how to handle multiple-response-packet AT commands?
|
||||||
|
@ -262,12 +283,7 @@ func (sess *Session) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sess *Session) LocalAddr() XBeeAddr {
|
func (sess *Session) LocalAddr() XBeeAddr {
|
||||||
// TODO: should we get this once at the start? and then just store it?
|
return sess.lAddr
|
||||||
sh, _ := sess.ATCommand([2]rune{'S', 'H'}, nil, false)
|
|
||||||
sl, _ := sess.ATCommand([2]rune{'S', 'L'}, nil, false)
|
|
||||||
|
|
||||||
addr := uint64(binary.BigEndian.Uint32(sh)) << 32 & uint64(binary.BigEndian.Uint32(sl))
|
|
||||||
return XBeeAddr(addr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sess *Session) RemoteAddr() XBeeAddr {
|
func (sess *Session) RemoteAddr() XBeeAddr {
|
||||||
|
|
|
@ -47,9 +47,9 @@ func TestXBeeHardware(t *testing.T) {
|
||||||
// now we should test sending a packet. and getting a response.
|
// now we should test sending a packet. and getting a response.
|
||||||
|
|
||||||
t.Run("Get Network ID", func(t *testing.T) {
|
t.Run("Get Network ID", func(t *testing.T) {
|
||||||
b, err := sess.ATCommand([2]rune{'I', 'D'}, nil, false)
|
b, err := sess.ATCommand([2]byte{'I', 'D'}, nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("ATCommand() error = %v", err)
|
t.Fatalf("ATCommand() error = %v", err)
|
||||||
}
|
}
|
||||||
if len(b) != 2 {
|
if len(b) != 2 {
|
||||||
t.Errorf("reponse length mismatch: expected 2 got %d", len(b))
|
t.Errorf("reponse length mismatch: expected 2 got %d", len(b))
|
||||||
|
@ -57,10 +57,10 @@ func TestXBeeHardware(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Check NP", func(t *testing.T) {
|
t.Run("Check NP", func(t *testing.T) {
|
||||||
b, err := sess.ATCommand([2]rune{'N', 'P'}, nil, false)
|
b, err := sess.ATCommand([2]byte{'N', 'P'}, nil, false)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("ATCommand() error = %v", err)
|
t.Fatalf("ATCommand() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
val := binary.BigEndian.Uint16(b)
|
val := binary.BigEndian.Uint16(b)
|
||||||
|
@ -69,6 +69,27 @@ func TestXBeeHardware(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
t.Run("check source address", func(t *testing.T) {
|
||||||
|
a := sess.LocalAddr()
|
||||||
|
|
||||||
|
t.Logf("local device address is %v", a)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
t.Run("Check device name", func(t *testing.T) {
|
||||||
|
a, err := sess.ATCommand([2]byte{'N', 'I'}, nil, false)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not run NI: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := string(a)
|
||||||
|
t.Logf("Device Name: %s", name)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseDeviceString(t *testing.T) {
|
func TestParseDeviceString(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue