diff --git a/cmd/server.go b/cmd/server.go index 2bf0058..23e740d 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -1,11 +1,69 @@ package cmd import ( + "fmt" + "net" + "time" + + "github.com/kschamplin/gotelem/internal/gotelem" + "github.com/tinylib/msgp/msgp" "github.com/urfave/cli/v2" ) +const xbeeCategory = "XBee settings" + var serveCmd = &cli.Command{ Name: "serve", Aliases: []string{"server", "s"}, Usage: "Start a telemetry server", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "xbee", Aliases: []string{"x"}, Usage: "Find and connect to an XBee"}, + }, + Action: func(ctx *cli.Context) error { + serve() + return nil + }, +} + + +type session struct { + conn net.Conn + send chan gotelem.Body + recv chan gotelem.Body + quit chan bool +} + +func serve() { + ln, err := net.Listen("tcp", ":8082") + if err != nil { + fmt.Printf("Error listening: %v\n", err) + } + fmt.Printf("Listening on :8082\n") + + for { + conn, err := ln.Accept() + if err != nil { + fmt.Printf("error accepting: %v\n", err) + } + go handleCon(conn) + } +} + +func handleCon(conn net.Conn) { + // reader := msgp.NewReader(conn) + writer := msgp.NewWriter(conn) + for { + // data := telemnet.StatusBody{ + // BatteryPct: 1.2, + // ErrCode: 0, + // } + // data.EncodeMsg(writer) + data := gotelem.StatusBody{ + BatteryPct: 1.2, + ErrCode: 0, + } + data.EncodeMsg(writer) + writer.Flush() + time.Sleep(1 * time.Second) + } } diff --git a/go.mod b/go.mod index 286a5ee..f05e608 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,10 @@ require ( require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/philhofer/fwd v1.1.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/tinylib/msgp v1.1.8 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/tools v0.8.0 // indirect ) diff --git a/go.sum b/go.sum index eec3cde..b2c58ae 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,48 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= +github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw= github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/can/frame.go b/internal/can/frame.go similarity index 90% rename from can/frame.go rename to internal/can/frame.go index 1cf3698..1e1d0d9 100644 --- a/can/frame.go +++ b/internal/can/frame.go @@ -29,3 +29,8 @@ type CanSink interface { type CanSource interface { Recv() (*Frame, error) } + +type CanTransciever interface { + CanSink + CanSource +} diff --git a/internal/can/frame_kind.go b/internal/can/frame_kind.go new file mode 100644 index 0000000..948ab75 --- /dev/null +++ b/internal/can/frame_kind.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -output=frame_kind.go -type Kind"; DO NOT EDIT. + +package can + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[SFF-0] + _ = x[EFF-1] + _ = x[RTR-2] + _ = x[ERR-3] +} + +const _Kind_name = "SFFEFFRTRERR" + +var _Kind_index = [...]uint8{0, 3, 6, 9, 12} + +func (i Kind) String() string { + if i >= Kind(len(_Kind_index)-1) { + return "Kind(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Kind_name[_Kind_index[i]:_Kind_index[i+1]] +} diff --git a/internal/db/can_adapter.go b/internal/db/can_adapter.go new file mode 100644 index 0000000..07ae9ca --- /dev/null +++ b/internal/db/can_adapter.go @@ -0,0 +1,21 @@ +package db + +import ( + "database/sql" + + "github.com/kschamplin/gotelem/internal/can" +) + +// this file implements a CAN adapter for the sqlite db. + +type CanDB struct { + Db *sql.DB +} + +func (cdb *CanDB) Send(_ *can.Frame) error { + panic("not implemented") // TODO: Implement +} + +func (cdb *CanDB) Recv() (*can.Frame, error) { + panic("not implemented") // TODO: Implement +} diff --git a/internal/gotelem/messages.go b/internal/gotelem/messages.go new file mode 100644 index 0000000..0f0cd74 --- /dev/null +++ b/internal/gotelem/messages.go @@ -0,0 +1,60 @@ +package gotelem + +import ( + "github.com/tinylib/msgp/msgp" +) + +// a body is a thing that can get a type, which we put in the header. +// we use the header to store metadata too +type Body interface { + GetType() string + msgp.Marshaler +} + +//go:generate msgp +type Data struct { + Header map[string]string `msg:"header"` + Body msgp.Raw `msg:"body"` +} + +type CanBody struct { + Id uint32 `msg:"id"` + Payload []byte `msg:"data"` + Source string `msg:"src"` +} + +func (*CanBody) GetType() string { + return "canp" +} + +// A status contains information about the running application. +// mainly internal battery percentage. +type StatusBody struct { + BatteryPct float32 `msg:"batt"` + ErrCode int16 `msg:"err"` // 0 is good. +} + +func (*StatusBody) GetType() string { + return "status" +} + +// takes anything that has a GetType() string method and packs it up. +func NewData(body Body) (*Data, error) { + data := &Data{} + + data.Header["type"] = body.GetType() + + // add other metadata here. + data.Header["ver"] = "0.0.1" + + data.Header["test"] = "mesg" + + rawBody, err := body.MarshalMsg(nil) + if err != nil { + return nil, err + } + + data.Body = rawBody + + return data, nil +} diff --git a/internal/gotelem/messages_gen.go b/internal/gotelem/messages_gen.go new file mode 100644 index 0000000..e6445fd --- /dev/null +++ b/internal/gotelem/messages_gen.go @@ -0,0 +1,491 @@ +package gotelem + +// Code generated by github.com/tinylib/msgp DO NOT EDIT. + +import ( + "github.com/tinylib/msgp/msgp" +) + +// DecodeMsg implements msgp.Decodable +func (z *CanBody) DecodeMsg(dc *msgp.Reader) (err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, err = dc.ReadMapKeyPtr() + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "id": + z.Id, err = dc.ReadUint32() + if err != nil { + err = msgp.WrapError(err, "Id") + return + } + case "data": + z.Payload, err = dc.ReadBytes(z.Payload) + if err != nil { + err = msgp.WrapError(err, "Payload") + return + } + case "src": + z.Source, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Source") + return + } + default: + err = dc.Skip() + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z *CanBody) EncodeMsg(en *msgp.Writer) (err error) { + // map header, size 3 + // write "id" + err = en.Append(0x83, 0xa2, 0x69, 0x64) + if err != nil { + return + } + err = en.WriteUint32(z.Id) + if err != nil { + err = msgp.WrapError(err, "Id") + return + } + // write "data" + err = en.Append(0xa4, 0x64, 0x61, 0x74, 0x61) + if err != nil { + return + } + err = en.WriteBytes(z.Payload) + if err != nil { + err = msgp.WrapError(err, "Payload") + return + } + // write "src" + err = en.Append(0xa3, 0x73, 0x72, 0x63) + if err != nil { + return + } + err = en.WriteString(z.Source) + if err != nil { + err = msgp.WrapError(err, "Source") + return + } + return +} + +// MarshalMsg implements msgp.Marshaler +func (z *CanBody) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // map header, size 3 + // string "id" + o = append(o, 0x83, 0xa2, 0x69, 0x64) + o = msgp.AppendUint32(o, z.Id) + // string "data" + o = append(o, 0xa4, 0x64, 0x61, 0x74, 0x61) + o = msgp.AppendBytes(o, z.Payload) + // string "src" + o = append(o, 0xa3, 0x73, 0x72, 0x63) + o = msgp.AppendString(o, z.Source) + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *CanBody) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "id": + z.Id, bts, err = msgp.ReadUint32Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Id") + return + } + case "data": + z.Payload, bts, err = msgp.ReadBytesBytes(bts, z.Payload) + if err != nil { + err = msgp.WrapError(err, "Payload") + return + } + case "src": + z.Source, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Source") + return + } + default: + bts, err = msgp.Skip(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + o = bts + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *CanBody) Msgsize() (s int) { + s = 1 + 3 + msgp.Uint32Size + 5 + msgp.BytesPrefixSize + len(z.Payload) + 4 + msgp.StringPrefixSize + len(z.Source) + return +} + +// DecodeMsg implements msgp.Decodable +func (z *Data) DecodeMsg(dc *msgp.Reader) (err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, err = dc.ReadMapKeyPtr() + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "header": + var zb0002 uint32 + zb0002, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err, "Header") + return + } + if z.Header == nil { + z.Header = make(map[string]string, zb0002) + } else if len(z.Header) > 0 { + for key := range z.Header { + delete(z.Header, key) + } + } + for zb0002 > 0 { + zb0002-- + var za0001 string + var za0002 string + za0001, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Header") + return + } + za0002, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Header", za0001) + return + } + z.Header[za0001] = za0002 + } + case "body": + err = z.Body.DecodeMsg(dc) + if err != nil { + err = msgp.WrapError(err, "Body") + return + } + default: + err = dc.Skip() + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z *Data) EncodeMsg(en *msgp.Writer) (err error) { + // map header, size 2 + // write "header" + err = en.Append(0x82, 0xa6, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72) + if err != nil { + return + } + err = en.WriteMapHeader(uint32(len(z.Header))) + if err != nil { + err = msgp.WrapError(err, "Header") + return + } + for za0001, za0002 := range z.Header { + err = en.WriteString(za0001) + if err != nil { + err = msgp.WrapError(err, "Header") + return + } + err = en.WriteString(za0002) + if err != nil { + err = msgp.WrapError(err, "Header", za0001) + return + } + } + // write "body" + err = en.Append(0xa4, 0x62, 0x6f, 0x64, 0x79) + if err != nil { + return + } + err = z.Body.EncodeMsg(en) + if err != nil { + err = msgp.WrapError(err, "Body") + return + } + return +} + +// MarshalMsg implements msgp.Marshaler +func (z *Data) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // map header, size 2 + // string "header" + o = append(o, 0x82, 0xa6, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72) + o = msgp.AppendMapHeader(o, uint32(len(z.Header))) + for za0001, za0002 := range z.Header { + o = msgp.AppendString(o, za0001) + o = msgp.AppendString(o, za0002) + } + // string "body" + o = append(o, 0xa4, 0x62, 0x6f, 0x64, 0x79) + o, err = z.Body.MarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, "Body") + return + } + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *Data) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "header": + var zb0002 uint32 + zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Header") + return + } + if z.Header == nil { + z.Header = make(map[string]string, zb0002) + } else if len(z.Header) > 0 { + for key := range z.Header { + delete(z.Header, key) + } + } + for zb0002 > 0 { + var za0001 string + var za0002 string + zb0002-- + za0001, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Header") + return + } + za0002, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Header", za0001) + return + } + z.Header[za0001] = za0002 + } + case "body": + bts, err = z.Body.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Body") + return + } + default: + bts, err = msgp.Skip(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + o = bts + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *Data) Msgsize() (s int) { + s = 1 + 7 + msgp.MapHeaderSize + if z.Header != nil { + for za0001, za0002 := range z.Header { + _ = za0002 + s += msgp.StringPrefixSize + len(za0001) + msgp.StringPrefixSize + len(za0002) + } + } + s += 5 + z.Body.Msgsize() + return +} + +// DecodeMsg implements msgp.Decodable +func (z *StatusBody) DecodeMsg(dc *msgp.Reader) (err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, err = dc.ReadMapKeyPtr() + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "batt": + z.BatteryPct, err = dc.ReadFloat32() + if err != nil { + err = msgp.WrapError(err, "BatteryPct") + return + } + case "err": + z.ErrCode, err = dc.ReadInt16() + if err != nil { + err = msgp.WrapError(err, "ErrCode") + return + } + default: + err = dc.Skip() + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z StatusBody) EncodeMsg(en *msgp.Writer) (err error) { + // map header, size 2 + // write "batt" + err = en.Append(0x82, 0xa4, 0x62, 0x61, 0x74, 0x74) + if err != nil { + return + } + err = en.WriteFloat32(z.BatteryPct) + if err != nil { + err = msgp.WrapError(err, "BatteryPct") + return + } + // write "err" + err = en.Append(0xa3, 0x65, 0x72, 0x72) + if err != nil { + return + } + err = en.WriteInt16(z.ErrCode) + if err != nil { + err = msgp.WrapError(err, "ErrCode") + return + } + return +} + +// MarshalMsg implements msgp.Marshaler +func (z StatusBody) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // map header, size 2 + // string "batt" + o = append(o, 0x82, 0xa4, 0x62, 0x61, 0x74, 0x74) + o = msgp.AppendFloat32(o, z.BatteryPct) + // string "err" + o = append(o, 0xa3, 0x65, 0x72, 0x72) + o = msgp.AppendInt16(o, z.ErrCode) + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *StatusBody) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "batt": + z.BatteryPct, bts, err = msgp.ReadFloat32Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "BatteryPct") + return + } + case "err": + z.ErrCode, bts, err = msgp.ReadInt16Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "ErrCode") + return + } + default: + bts, err = msgp.Skip(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + o = bts + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z StatusBody) Msgsize() (s int) { + s = 1 + 5 + msgp.Float32Size + 4 + msgp.Int16Size + return +} diff --git a/internal/gotelem/messages_gen_test.go b/internal/gotelem/messages_gen_test.go new file mode 100644 index 0000000..51fa3e1 --- /dev/null +++ b/internal/gotelem/messages_gen_test.go @@ -0,0 +1,349 @@ +package gotelem + +// Code generated by github.com/tinylib/msgp DO NOT EDIT. + +import ( + "bytes" + "testing" + + "github.com/tinylib/msgp/msgp" +) + +func TestMarshalUnmarshalCanBody(t *testing.T) { + v := CanBody{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func BenchmarkMarshalMsgCanBody(b *testing.B) { + v := CanBody{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgCanBody(b *testing.B) { + v := CanBody{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalCanBody(b *testing.B) { + v := CanBody{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestEncodeDecodeCanBody(t *testing.T) { + v := CanBody{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + + m := v.Msgsize() + if buf.Len() > m { + t.Log("WARNING: TestEncodeDecodeCanBody Msgsize() is inaccurate") + } + + vn := CanBody{} + err := msgp.Decode(&buf, &vn) + if err != nil { + t.Error(err) + } + + buf.Reset() + msgp.Encode(&buf, &v) + err = msgp.NewReader(&buf).Skip() + if err != nil { + t.Error(err) + } +} + +func BenchmarkEncodeCanBody(b *testing.B) { + v := CanBody{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + b.SetBytes(int64(buf.Len())) + en := msgp.NewWriter(msgp.Nowhere) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.EncodeMsg(en) + } + en.Flush() +} + +func BenchmarkDecodeCanBody(b *testing.B) { + v := CanBody{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + b.SetBytes(int64(buf.Len())) + rd := msgp.NewEndlessReader(buf.Bytes(), b) + dc := msgp.NewReader(rd) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + err := v.DecodeMsg(dc) + if err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalUnmarshalData(t *testing.T) { + v := Data{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func BenchmarkMarshalMsgData(b *testing.B) { + v := Data{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgData(b *testing.B) { + v := Data{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalData(b *testing.B) { + v := Data{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestEncodeDecodeData(t *testing.T) { + v := Data{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + + m := v.Msgsize() + if buf.Len() > m { + t.Log("WARNING: TestEncodeDecodeData Msgsize() is inaccurate") + } + + vn := Data{} + err := msgp.Decode(&buf, &vn) + if err != nil { + t.Error(err) + } + + buf.Reset() + msgp.Encode(&buf, &v) + err = msgp.NewReader(&buf).Skip() + if err != nil { + t.Error(err) + } +} + +func BenchmarkEncodeData(b *testing.B) { + v := Data{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + b.SetBytes(int64(buf.Len())) + en := msgp.NewWriter(msgp.Nowhere) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.EncodeMsg(en) + } + en.Flush() +} + +func BenchmarkDecodeData(b *testing.B) { + v := Data{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + b.SetBytes(int64(buf.Len())) + rd := msgp.NewEndlessReader(buf.Bytes(), b) + dc := msgp.NewReader(rd) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + err := v.DecodeMsg(dc) + if err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalUnmarshalStatusBody(t *testing.T) { + v := StatusBody{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func BenchmarkMarshalMsgStatusBody(b *testing.B) { + v := StatusBody{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgStatusBody(b *testing.B) { + v := StatusBody{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalStatusBody(b *testing.B) { + v := StatusBody{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestEncodeDecodeStatusBody(t *testing.T) { + v := StatusBody{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + + m := v.Msgsize() + if buf.Len() > m { + t.Log("WARNING: TestEncodeDecodeStatusBody Msgsize() is inaccurate") + } + + vn := StatusBody{} + err := msgp.Decode(&buf, &vn) + if err != nil { + t.Error(err) + } + + buf.Reset() + msgp.Encode(&buf, &v) + err = msgp.NewReader(&buf).Skip() + if err != nil { + t.Error(err) + } +} + +func BenchmarkEncodeStatusBody(b *testing.B) { + v := StatusBody{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + b.SetBytes(int64(buf.Len())) + en := msgp.NewWriter(msgp.Nowhere) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.EncodeMsg(en) + } + en.Flush() +} + +func BenchmarkDecodeStatusBody(b *testing.B) { + v := StatusBody{} + var buf bytes.Buffer + msgp.Encode(&buf, &v) + b.SetBytes(int64(buf.Len())) + rd := msgp.NewEndlessReader(buf.Bytes(), b) + dc := msgp.NewReader(rd) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + err := v.DecodeMsg(dc) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/socketcan/socketcan.go b/internal/socketcan/socketcan.go similarity index 98% rename from socketcan/socketcan.go rename to internal/socketcan/socketcan.go index 19a739a..13a10c3 100644 --- a/socketcan/socketcan.go +++ b/internal/socketcan/socketcan.go @@ -6,7 +6,7 @@ import ( "fmt" "net" - "github.com/kschamplin/gotelem/can" + "github.com/kschamplin/gotelem/internal/can" "golang.org/x/sys/unix" ) diff --git a/socketcan/socketcan_test.go b/internal/socketcan/socketcan_test.go similarity index 98% rename from socketcan/socketcan_test.go rename to internal/socketcan/socketcan_test.go index c381ac6..f039074 100644 --- a/socketcan/socketcan_test.go +++ b/internal/socketcan/socketcan_test.go @@ -5,7 +5,7 @@ import ( "net" "testing" - "github.com/kschamplin/gotelem/can" + "github.com/kschamplin/gotelem/internal/can" "golang.org/x/sys/unix" ) diff --git a/internal/xbee/api_frame.go b/internal/xbee/api_frame.go new file mode 100644 index 0000000..eca34c4 --- /dev/null +++ b/internal/xbee/api_frame.go @@ -0,0 +1,235 @@ +package xbee + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" +) + +// the frames have an outer shell - we will make a function that takes +// an inner frame element and wraps it in the appropriate headers. + +// first, we should make it take the frame directly, so we make an interface +// that represents "framable" things. note that bytes.Buffer also fulfils this. + +type Frameable interface { + // returns the API identifier for this frame. + GetId() byte + // encodes this frame correctly. + Bytes() ([]byte, error) +} + +// now we can describe our function that takes a framable and contains it + calculates checksums. +func calculateChecksum(data []byte) byte { + var sum byte + for _, v := range data { + sum += v + } + return 0xFF - sum +} + +func WriteFrame(w io.Writer, cmd Frameable) (n int, err error) { + frame_data, err := cmd.Bytes() + + if err != nil { + return + } + frame := make([]byte, len(frame_data)+4) + frame[0] = 0x7E + + binary.BigEndian.PutUint16(frame[1:], uint16(len(frame_data))) + + copy(frame[3:], frame_data) + + chk := calculateChecksum(frame_data) + + frame[len(frame)-1] = chk + return w.Write(frame) +} + +func makeXbeeApiFrame(cmd Frameable) ([]byte, error) { + dataBuf, _ := cmd.Bytes() + frameBuf := make([]byte, len(dataBuf)+4) + + // move data and construct the frame + + frameBuf[0] = 0x7E // start delimiter + + // length + // todo: check endiannes (0x7e, msb lsb) + binary.BigEndian.PutUint16(frameBuf[1:3], uint16(len(dataBuf))) + + copy(frameBuf[3:], dataBuf) + + chksum := calculateChecksum(dataBuf) + + frameBuf[len(frameBuf)-1] = chksum + + return frameBuf, nil +} + +// now we can describe frames in other files that implement Frameable. +// the remaining challenge is reception and actual API frames. +// xbee uses the first byte of the "frame data" as the API identifier or command. + +//go:generate stringer -output=api_frame_cmd.go -type xbeeCmd +type XBeeCmd byte + +const ( + // commands sent to the xbee s3b + + ATCmd XBeeCmd = 0x08 // AT Command + ATCmdQueue XBeeCmd = 0x09 // AT Command - Queue Parameter Value + TxReq XBeeCmd = 0x10 // TX Request + TxReqExpl XBeeCmd = 0x11 // Explicit TX Request + RemoteCmdReq XBeeCmd = 0x17 // Remote Command Request + // commands recieved from the xbee + + ATCmdResponse XBeeCmd = 0x88 // AT Command Response + ModemStatus XBeeCmd = 0x8A // Modem Status + TxStatus XBeeCmd = 0x8B // Transmit Status + RouteInfoPkt XBeeCmd = 0x8D // Route information packet + AddrUpdate XBeeCmd = 0x8E // Aggregate Addressing Update + RxPkt XBeeCmd = 0x90 // RX Indicator (AO=0) + RxPktExpl XBeeCmd = 0x91 // Explicit RX Indicator (AO=1) + IOSample XBeeCmd = 0x92 // Data Sample RX Indicator + NodeId XBeeCmd = 0x95 // Note Identification Indicator + RemoteCmdResp XBeeCmd = 0x97 // Remote Command Response +) + +// AT commands are hard, so let's write out all the major ones here + +type ATCmdFrame struct { + Id byte + Cmd string + Param []byte + Queued bool +} + +// implement the frame stuff for us. +func (atFrame *ATCmdFrame) Bytes() ([]byte, error) { + buf := new(bytes.Buffer) + + if atFrame.Queued { + // queued (batched) at comamnds have different Frame type + buf.WriteByte(byte(ATCmdQueue)) + + } else { + // normal frame type + buf.WriteByte(byte(ATCmd)) + + } + + buf.WriteByte(atFrame.Id) + + // write cmd, if it's the right length. + if cmdLen := len(atFrame.Cmd); cmdLen != 2 { + return nil, fmt.Errorf("AT command incorrect length: %d", cmdLen) + } + buf.Write([]byte(atFrame.Cmd)) + + // write param. + buf.Write(atFrame.Param) + return buf.Bytes(), nil +} + +// transmissions to this address are instead broadcast +const BroadcastAddr = 0xFFFF + +type TxFrame struct { + Id byte + Destination uint64 + BCastRadius uint8 + Options uint8 + Payload []byte +} + +func (txFrame *TxFrame) Bytes() ([]byte, error) { + buf := new(bytes.Buffer) + + buf.WriteByte(byte(TxReq)) + + buf.WriteByte(txFrame.Id) + + a := make([]byte, 8) + binary.LittleEndian.PutUint64(a, txFrame.Destination) + buf.Write(a) + + // write the reserved part. + buf.Write([]byte{0xFF, 0xFE}) + + // write the radius + buf.WriteByte(txFrame.BCastRadius) + + buf.WriteByte(txFrame.Options) + + buf.Write(txFrame.Payload) + + return buf.Bytes(), nil +} + +type RemoteATCmdReq struct { + ATCmdFrame + Destination uint64 + Options uint8 +} + +func (remoteAT *RemoteATCmdReq) Bytes() ([]byte, error) { + buf := new(bytes.Buffer) + buf.WriteByte(byte(RemoteCmdReq)) + + buf.WriteByte(remoteAT.Id) + + a := make([]byte, 8) + binary.LittleEndian.PutUint64(a, remoteAT.Destination) + buf.Write(a) + + // write the reserved part. + buf.Write([]byte{0xFF, 0xFE}) + // write options + buf.WriteByte(remoteAT.Options) + + // now, write the AT command and the data. + buf.Write([]byte(remoteAT.Cmd)) + + buf.Write(remoteAT.Param) + + return buf.Bytes(), nil + +} + +// Now we will implement receiving packets from a character stream. +// we first need to make a thing that produces frames from a stream using a scanner. + +// this is a split function for bufio.scanner. It makes it easier to handle the FSM +// for extracting data from a stream. For the Xbee, this means that we must +// find the magic start character, (check that it's escaped), read the length, +// and then ensure we have enough length to finish the token, requesting more data +// if we do not. +// +// see https://pkg.go.dev/bufio#SplitFunc for more info +// https://medium.com/golangspec/in-depth-introduction-to-bufio-scanner-in-golang-55483bb689b4 +func xbeeFrameSplit(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + // there's no data, request more. + return 0, nil, nil + } + + if startIdx := bytes.IndexByte(data, 0x7E); startIdx >= 0 { + // we have a start character. get the length. + // we add 4 since start delimiter (1) + length (2) + checksum (1). + // the length inside the packet represents the frame data only. + var frameLen = binary.BigEndian.Uint16(data[startIdx+1:startIdx+3]) + 4 + if len(data[startIdx:]) < int(frameLen) { + // we got the length, but there's not enough data for the frame. we can trim the + // data that came before the start, but not return a token. + return startIdx, nil, nil + } + // there is enough data to pull a frame. + // todo: check checksum here? we can return an error. + return startIdx + int(frameLen), data[startIdx : startIdx+int(frameLen)], nil + } + // we didn't find a start character in our data, so request more. trash everythign given to us + return len(data), nil, nil +} diff --git a/internal/xbee/api_frame_test.go b/internal/xbee/api_frame_test.go new file mode 100644 index 0000000..e4399f1 --- /dev/null +++ b/internal/xbee/api_frame_test.go @@ -0,0 +1,119 @@ +package xbee + +import ( + "bytes" + "reflect" + "testing" +) + +func Test_xbeeFrameSplit(t *testing.T) { + type args struct { + data []byte + atEOF bool + } + tests := []struct { + name string + args args + wantAdvance int + wantToken []byte + wantErr bool + }{ + // TODO: Add test cases. + { + name: "empty data", + args: args{ + data: []byte{}, + atEOF: false, + }, + wantAdvance: 0, + wantToken: nil, + wantErr: false, + }, + { + name: "no start delimiter", + args: args{ + data: []byte{0x11, 0x22, 0x23, 0x44, 0x44, 0x77, 0x33}, + atEOF: false, + }, + wantAdvance: 7, + wantToken: nil, + wantErr: false, + }, + { + name: "incomplete packet", + args: args{ + data: []byte{0x7E, 0x00, 0x02, 0x23, 0x11}, + atEOF: false, + }, + wantAdvance: 0, + wantToken: nil, + wantErr: false, + }, + { + name: "valid packet", + args: args{ + data: []byte{0x7E, 0x00, 0x02, 0x23, 0x11, 0xCB}, + atEOF: false, + }, + wantAdvance: 6, + wantToken: []byte{0x7E, 0x00, 0x02, 0x23, 0x11, 0xCB}, + wantErr: false, + }, + { + name: "valid packet w/ padding", + args: args{ + data: []byte{0x00, 0x7E, 0x00, 0x02, 0x23, 0x11, 0xCB, 0x00}, + atEOF: false, + }, + wantAdvance: 7, + wantToken: []byte{0x7E, 0x00, 0x02, 0x23, 0x11, 0xCB}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotAdvance, gotToken, err := xbeeFrameSplit(tt.args.data, tt.args.atEOF) + if (err != nil) != tt.wantErr { + t.Errorf("xbeeFrameSplit() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotAdvance != tt.wantAdvance { + t.Errorf("xbeeFrameSplit() gotAdvance = %v, want %v", gotAdvance, tt.wantAdvance) + } + if !reflect.DeepEqual(gotToken, tt.wantToken) { + t.Errorf("xbeeFrameSplit() gotToken = %v, want %v", gotToken, tt.wantToken) + } + }) + } +} + +func TestWriteFrame(t *testing.T) { + type args struct { + cmd Frameable + } + tests := []struct { + name string + args args + wantN int + wantW string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &bytes.Buffer{} + gotN, err := WriteFrame(w, tt.args.cmd) + if (err != nil) != tt.wantErr { + t.Errorf("WriteFrame() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotN != tt.wantN { + t.Errorf("WriteFrame() = %v, want %v", gotN, tt.wantN) + } + if gotW := w.String(); gotW != tt.wantW { + t.Errorf("WriteFrame() = %v, want %v", gotW, tt.wantW) + } + }) + } +} diff --git a/xbee/at.go b/internal/xbee/at.go similarity index 100% rename from xbee/at.go rename to internal/xbee/at.go diff --git a/xbee/api_frame.go b/xbee/api_frame.go deleted file mode 100644 index 8d6d27a..0000000 --- a/xbee/api_frame.go +++ /dev/null @@ -1,71 +0,0 @@ -package xbee - -import "encoding/binary" - -// the frames have an outer shell - we will make a function that takes -// an inner frame element and wraps it in the appropriate headers. - -// first, we should make it take the frame directly, so we make an interface -// that represents "framable" things. note that bytes.Buffer also fulfils this. - -type Frameable interface { - Bytes() []byte -} - -// now we can describe our function that takes a framable and contains it + calculates checksums. -func calculateChecksum(data []byte) byte { - var sum byte - for _, v := range data { - sum += v - } - return 0xFF - sum -} -func makeXbeeApiFrame(cmd Frameable) ([]byte, error) { - dataBuf := cmd.Bytes() - frameBuf := make([]byte, len(dataBuf)+4) - - // move data and construct the frame - - frameBuf[0] = 0x7E // start delimiter - - // length - // todo: check endiannes (0x7e, msb lsb) - binary.LittleEndian.PutUint16(frameBuf[1:3], uint16(len(dataBuf))) - - copy(frameBuf[3:], dataBuf) - - chksum := calculateChecksum(dataBuf) - - frameBuf[len(frameBuf)-1] = chksum - - return frameBuf, nil -} - -// now we can describe frames in other files that implement Frameable. this makes trasmission complete. -// the remaining challenge is reception and actual API frames. -// xbee uses the first byte of the "frame data" as the API identifier or command. - -//go:generate stringer -output=api_frame_cmd.go -type xbeeCmd -type xbeeCmd byte - -const ( - // commands sent to the xbee s3b - - ATCmd xbeeCmd = 0x08 // AT Command - ATCmdQueuePVal xbeeCmd = 0x09 // AT Command - Queue Parameter Value - TxReq xbeeCmd = 0x10 // TX Request - TxReqExpl xbeeCmd = 0x11 // Explicit TX Request - RemoteCmdReq xbeeCmd = 0x17 // Remote Command Request - // commands recieved from the xbee - - ATCmdResponse xbeeCmd = 0x88 // AT Command Response - ModemStatus xbeeCmd = 0x8A // Modem Status - TxStatus xbeeCmd = 0x8B // Transmit Status - RouteInfoPkt xbeeCmd = 0x8D // Route information packet - AddrUpdate xbeeCmd = 0x8E // Aggregate Addressing Update - RxPkt xbeeCmd = 0x90 // RX Indicator (AO=0) - RxPktExpl xbeeCmd = 0x91 // Explicit RX Indicator (AO=1) - IOSample xbeeCmd = 0x92 // Data Sample RX Indicator - NodeId xbeeCmd = 0x95 // Note Identification Indicator - RemoteCmdResp xbeeCmd = 0x97 // Remote Command Response -) diff --git a/xbee/api_frame_test.go b/xbee/api_frame_test.go deleted file mode 100644 index 6e203d4..0000000 --- a/xbee/api_frame_test.go +++ /dev/null @@ -1 +0,0 @@ -package xbee_test