aboutsummaryrefslogtreecommitdiff
path: root/transport/client.go
blob: 042d2ced84fbe18ab8518942c7270dc134031e26 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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
}