aboutsummaryrefslogtreecommitdiff
path: root/transport/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'transport/client.go')
-rw-r--r--transport/client.go135
1 files changed, 135 insertions, 0 deletions
diff --git a/transport/client.go b/transport/client.go
new file mode 100644
index 0000000..042d2ce
--- /dev/null
+++ b/transport/client.go
@@ -0,0 +1,135 @@
+package transport
+
+import (
+ "buf.build/gen/go/meshtastic/protobufs/protocolbuffers/go/meshtastic"
+ "fmt"
+ "github.com/charmbracelet/log"
+ "google.golang.org/protobuf/proto"
+ "math/rand"
+)
+
+type HandlerFunc func(message proto.Message)
+
+type Client struct {
+ sc *StreamConn
+ handlers *HandlerRegistry
+ log *log.Logger
+
+ config struct {
+ complete bool
+ configID uint32
+ *meshtastic.MyNodeInfo
+ *meshtastic.DeviceMetadata
+ nodes []*meshtastic.NodeInfo
+ channels []*meshtastic.Channel
+ config []*meshtastic.Config
+ modules []*meshtastic.ModuleConfig
+ }
+}
+
+func NewClient(sc *StreamConn, errorOnNoHandler bool) *Client {
+ return &Client{
+ log: log.WithPrefix("client"),
+ sc: sc,
+ handlers: NewHandlerRegistry(errorOnNoHandler),
+ }
+}
+
+// You have to send this first to get the radio into protobuf mode and have it accept and send packets via serial
+func (c *Client) sendGetConfig() error {
+ r := rand.Uint32()
+ c.config.configID = r
+ msg := &meshtastic.ToRadio{
+ PayloadVariant: &meshtastic.ToRadio_WantConfigId{
+ WantConfigId: r,
+ },
+ }
+ c.log.Debug("sending want config", "id", r)
+ if err := c.sc.Write(msg); err != nil {
+ return fmt.Errorf("writing want config command: %w", err)
+ }
+ c.log.Debug("sent want config")
+ return nil
+}
+
+func (c *Client) Handle(kind proto.Message, handler MessageHandler) {
+ c.handlers.RegisterHandler(kind, handler)
+}
+
+func (c *Client) SendToRadio(msg *meshtastic.ToRadio) error {
+ return c.sc.Write(msg)
+}
+
+func (c *Client) Connect() error {
+ if err := c.sendGetConfig(); err != nil {
+ return fmt.Errorf("requesting config: %w", err)
+ }
+ go func() {
+ for {
+ msg := &meshtastic.FromRadio{}
+ err := c.sc.Read(msg)
+ if err != nil {
+ c.log.Error("error reading from radio", "err", err)
+ continue
+ }
+ c.log.Debug("received message from radio", "msg", msg)
+ var variant proto.Message
+ switch msg.GetPayloadVariant().(type) {
+ // These pbufs all get sent upon initial connection to the node
+ case *meshtastic.FromRadio_MyInfo:
+ c.config.MyNodeInfo = msg.GetMyInfo()
+ variant = c.config.MyNodeInfo
+ case *meshtastic.FromRadio_Metadata:
+ c.config.DeviceMetadata = msg.GetMetadata()
+ variant = c.config.DeviceMetadata
+ case *meshtastic.FromRadio_NodeInfo:
+ node := msg.GetNodeInfo()
+ c.config.nodes = append(c.config.nodes, node)
+ variant = node
+ case *meshtastic.FromRadio_Channel:
+ channel := msg.GetChannel()
+ c.config.channels = append(c.config.channels, channel)
+ variant = channel
+ case *meshtastic.FromRadio_Config:
+ cfg := msg.GetConfig()
+ c.config.config = append(c.config.config, cfg)
+ variant = cfg
+ case *meshtastic.FromRadio_ModuleConfig:
+ cfg := msg.GetModuleConfig()
+ c.config.modules = append(c.config.modules, cfg)
+ variant = cfg
+ case *meshtastic.FromRadio_ConfigCompleteId:
+ c.log.Info("config complete")
+ c.config.complete = true
+ continue
+ // below are packets not part of initial connection
+
+ case *meshtastic.FromRadio_LogRecord:
+ variant = msg.GetLogRecord()
+ case *meshtastic.FromRadio_MqttClientProxyMessage:
+ variant = msg.GetMqttClientProxyMessage()
+ case *meshtastic.FromRadio_QueueStatus:
+ variant = msg.GetQueueStatus()
+ case *meshtastic.FromRadio_Rebooted:
+ // true if radio just rebooted
+ c.log.Debug("rebooted", "rebooted", msg.GetRebooted())
+ continue
+ case *meshtastic.FromRadio_XmodemPacket:
+ variant = msg.GetXmodemPacket()
+
+ case *meshtastic.FromRadio_Packet:
+ variant = msg.GetPacket()
+ default:
+ c.log.Warn("unhandled protobuf from radio")
+ }
+ if !c.config.complete {
+ continue
+ }
+ err = c.handlers.HandleMessage(variant)
+ if err != nil {
+ c.log.Error("error handling message", "err", err)
+ }
+ }
+ }()
+ return nil
+}