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