misc things

This commit is contained in:
saji 2023-05-13 10:38:35 -05:00
parent de57b38958
commit 5b64a2bef2
9 changed files with 497 additions and 137 deletions

View file

@ -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{
Action: func(ctx *cli.Context) error { Name: "device",
serve(ctx.Bool("xbee")) Aliases: []string{"d"},
return nil 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,
} }
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)
}
}
}
}

View file

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

View file

@ -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
View file

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

View file

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

View file

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

View file

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

View file

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