diff options
| -rw-r--r-- | example/main.go | 49 | ||||
| -rw-r--r-- | examples/mqtt/README.md | 5 | ||||
| -rw-r--r-- | examples/mqtt/main.go | 92 | ||||
| -rw-r--r-- | examples/radio/README.md | 6 | ||||
| -rw-r--r-- | examples/radio/main.go | 87 | ||||
| -rw-r--r-- | go.mod | 8 | ||||
| -rw-r--r-- | go.sum | 16 |
7 files changed, 202 insertions, 61 deletions
diff --git a/example/main.go b/example/main.go deleted file mode 100644 index f88d52d..0000000 --- a/example/main.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "context" - "github.com/charmbracelet/log" - pb "github.com/meshnet-gophers/meshtastic-go/meshtastic" - "github.com/meshnet-gophers/meshtastic-go/transport" - "github.com/meshnet-gophers/meshtastic-go/transport/serial" - "google.golang.org/protobuf/proto" - "os" - "os/signal" - "time" -) - -func main() { - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) - defer cancel() - - log.SetLevel(log.DebugLevel) - - ports := serial.GetPorts() - serialConn, err := serial.Connect(ports[0]) - if err != nil { - panic(err) - } - streamConn, err := transport.NewClientStreamConn(serialConn) - if err != nil { - panic(err) - } - defer func() { - if err := streamConn.Close(); err != nil { - panic(err) - } - }() - - client := transport.NewClient(streamConn, false) - client.Handle(new(pb.MeshPacket), func(msg proto.Message) { - pkt := msg.(*pb.MeshPacket) - log.Info("Received message from radio", "msg", pkt) - }) - ctxTimeout, cancelTimeout := context.WithTimeout(ctx, 10*time.Second) - defer cancelTimeout() - if client.Connect(ctxTimeout) != nil { - panic("Failed to connect to the radio") - } - - log.Info("Waiting for interrupt signal") - <-ctx.Done() -} diff --git a/examples/mqtt/README.md b/examples/mqtt/README.md new file mode 100644 index 0000000..1ef2b02 --- /dev/null +++ b/examples/mqtt/README.md @@ -0,0 +1,5 @@ +This example connects to the public MQTT server, receives and decodes messages on the EU_868 LongFast topic, and logs them out + +```shell +go run main.go +``` diff --git a/examples/mqtt/main.go b/examples/mqtt/main.go new file mode 100644 index 0000000..0ad9c5f --- /dev/null +++ b/examples/mqtt/main.go @@ -0,0 +1,92 @@ +package main + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "github.com/charmbracelet/log" + pb "github.com/meshnet-gophers/meshtastic-go/meshtastic" + "github.com/meshnet-gophers/meshtastic-go/mqtt" + "github.com/meshnet-gophers/meshtastic-go/radio" + "google.golang.org/protobuf/proto" + "strings" +) + +func main() { + client := mqtt.NewClient("tcp://mqtt.meshtastic.org:1883", "meshdev", "large4cats", "msh") + err := client.Connect() + if err != nil { + log.Fatal(err) + } + client.Handle("LongFast", channelHandler("LongFast")) + log.Info("Started") + select {} +} + +func channelHandler(channel string) mqtt.HandlerFunc { + return func(m mqtt.Message) { + var env pb.ServiceEnvelope + err := proto.Unmarshal(m.Payload, &env) + if err != nil { + log.Fatal("failed unmarshalling to service envelope", "err", err, "payload", hex.EncodeToString(m.Payload)) + return + } + + key, err := generateKey("1PG7OiApB1nwvP+rz05pAQ==") + if err != nil { + log.Fatal(err) + } + + decodedMessage, err := radio.XOR(env.Packet.GetEncrypted(), key, env.Packet.Id, env.Packet.From) + if err != nil { + log.Error(err) + } + var message pb.Data + err = proto.Unmarshal(decodedMessage, &message) + + log.Info(processMessage(message), "topic", m.Topic, "channel", channel, "portnum", message.Portnum.String()) + } +} + +func processMessage(message pb.Data) string { + if message.Portnum == pb.PortNum_NODEINFO_APP { + var user = pb.User{} + proto.Unmarshal(message.Payload, &user) + return user.String() + } + if message.Portnum == pb.PortNum_POSITION_APP { + var pos = pb.Position{} + proto.Unmarshal(message.Payload, &pos) + return pos.String() + } + if message.Portnum == pb.PortNum_TELEMETRY_APP { + var t = pb.Telemetry{} + proto.Unmarshal(message.Payload, &t) + return t.String() + } + if message.Portnum == pb.PortNum_NEIGHBORINFO_APP { + var n = pb.NeighborInfo{} + proto.Unmarshal(message.Payload, &n) + return n.String() + } + if message.Portnum == pb.PortNum_STORE_FORWARD_APP { + var s = pb.StoreAndForward{} + proto.Unmarshal(message.Payload, &s) + return s.String() + } + + return fmt.Sprintf("unknown message type") +} + +func generateKey(key string) ([]byte, error) { + // Pad the key with '=' characters to ensure it's a valid base64 string + padding := (4 - len(key)%4) % 4 + paddedKey := key + strings.Repeat("=", padding) + + // Replace '-' with '+' and '_' with '/' + replacedKey := strings.ReplaceAll(paddedKey, "-", "+") + replacedKey = strings.ReplaceAll(replacedKey, "_", "/") + + // Decode the base64-encoded key + return base64.StdEncoding.DecodeString(replacedKey) +} diff --git a/examples/radio/README.md b/examples/radio/README.md new file mode 100644 index 0000000..4a6bec5 --- /dev/null +++ b/examples/radio/README.md @@ -0,0 +1,6 @@ +This example connects to a Meshtastic device connected to a serial port, decodes received messages, and logs them out + +```shell +go run main.go # search for first device connected to a serial port +go run main.go /dev/ttyUSB0 # explicitly provide a port to connect to +``` diff --git a/examples/radio/main.go b/examples/radio/main.go new file mode 100644 index 0000000..feda4bd --- /dev/null +++ b/examples/radio/main.go @@ -0,0 +1,87 @@ +package main + +import ( + "context" + "fmt" + "github.com/charmbracelet/log" + pb "github.com/meshnet-gophers/meshtastic-go/meshtastic" + "github.com/meshnet-gophers/meshtastic-go/transport" + "github.com/meshnet-gophers/meshtastic-go/transport/serial" + "google.golang.org/protobuf/proto" + "os" + "os/signal" + "time" +) + +var port string + +func main() { + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + + log.SetLevel(log.DebugLevel) + + if len(os.Args) > 1 { + port = os.Args[1] + } else { + port = serial.GetPorts()[0] + } + serialConn, err := serial.Connect(port) + if err != nil { + panic(err) + } + streamConn, err := transport.NewClientStreamConn(serialConn) + if err != nil { + panic(err) + } + defer func() { + if err := streamConn.Close(); err != nil { + panic(err) + } + }() + + client := transport.NewClient(streamConn, false) + client.Handle(new(pb.MeshPacket), func(msg proto.Message) { + pkt := msg.(*pb.MeshPacket) + data := pkt.GetDecoded() + log.Info("Received message from radio", "msg", processMessage(*data), "from", pkt.From, "portnum", data.Portnum.String()) + }) + ctxTimeout, cancelTimeout := context.WithTimeout(ctx, 10*time.Second) + defer cancelTimeout() + if client.Connect(ctxTimeout) != nil { + panic("Failed to connect to the radio") + } + + log.Info("Waiting for interrupt signal") + <-ctx.Done() +} + +func processMessage(message pb.Data) string { + if message.Portnum == pb.PortNum_NODEINFO_APP { + var user = pb.User{} + proto.Unmarshal(message.Payload, &user) + return user.String() + } + if message.Portnum == pb.PortNum_POSITION_APP { + var pos = pb.Position{} + proto.Unmarshal(message.Payload, &pos) + return pos.String() + } + if message.Portnum == pb.PortNum_TELEMETRY_APP { + var t = pb.Telemetry{} + proto.Unmarshal(message.Payload, &t) + return t.String() + } + if message.Portnum == pb.PortNum_NEIGHBORINFO_APP { + var n = pb.NeighborInfo{} + proto.Unmarshal(message.Payload, &n) + return n.String() + } + if message.Portnum == pb.PortNum_STORE_FORWARD_APP { + var s = pb.StoreAndForward{} + proto.Unmarshal(message.Payload, &s) + return s.String() + } + + return fmt.Sprintf("unknown message type") +} @@ -3,18 +3,18 @@ module github.com/meshnet-gophers/meshtastic-go go 1.21 require ( - github.com/charmbracelet/log v0.3.1 + github.com/charmbracelet/log v0.4.0 github.com/eclipse/paho.mqtt.golang v1.4.3 github.com/planetscale/vtprotobuf v0.6.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 go.bug.st/serial v1.6.2 golang.org/x/sync v0.6.0 - google.golang.org/protobuf v1.32.0 + google.golang.org/protobuf v1.33.0 ) require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/lipgloss v0.9.1 // indirect + github.com/charmbracelet/lipgloss v0.10.0 // indirect github.com/creack/goselect v0.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect @@ -1,9 +1,9 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= -github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= -github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw= -github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g= +github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= +github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= +github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= +github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -35,8 +35,8 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8= go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo= @@ -48,8 +48,8 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
