aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSmoke <[email protected]>2024-01-22 15:56:16 -1000
committerSmoke <[email protected]>2024-01-22 15:56:16 -1000
commit5fa965d66db226df3b94131c9b0933822d55c985 (patch)
tree432f82aec644c24c6d325d4407801d37e9b2a75e
parent70bb2c77356d349165ba46ea98f8346284c2e44e (diff)
updates
-rw-r--r--.idea/.gitignore8
-rw-r--r--.idea/meshtastic-go.iml9
-rw-r--r--.idea/modules.xml8
-rw-r--r--.idea/vcs.xml6
-rw-r--r--lora/helpers.go54
-rw-r--r--lora/lora_test.go43
-rw-r--r--mqtt/client.go2
-rw-r--r--transport/handlers.go62
-rw-r--r--transport/serial/serial.go126
9 files changed, 289 insertions, 29 deletions
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/meshtastic-go.iml b/.idea/meshtastic-go.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/meshtastic-go.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+ <component name="Go" enabled="true" />
+ <component name="NewModuleRootManager">
+ <content url="file://$MODULE_DIR$" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module> \ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..90d9c91
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/meshtastic-go.iml" filepath="$PROJECT_DIR$/.idea/meshtastic-go.iml" />
+ </modules>
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/lora/helpers.go b/lora/helpers.go
index c843cc3..e3e7750 100644
--- a/lora/helpers.go
+++ b/lora/helpers.go
@@ -1,35 +1,41 @@
+// Package lora provides utilities to assess the signal quality of LoRa (Long Range) communication
+// based on RSSI (Received Signal Strength Indicator) and SNR (Signal-to-Noise Ratio) values.
package lora
import "fmt"
-// translated from https://sensing-labs.com/f-a-q/a-good-radio-level/
-
-// Define signal quality and diagnostic notes.
-type signalQuality string
-type diagnosticNote string
+// SignalQuality defines a type for representing the quality of a signal.
+type SignalQuality string
const (
- Good signalQuality = "GOOD"
- Fair signalQuality = "FAIR"
- Bad signalQuality = "BAD"
+ // SignalQualityGood indicates a good signal quality.
+ SignalQualityGood SignalQuality = "GOOD"
+ // SignalQualityFair indicates a fair signal quality.
+ SignalQualityFair SignalQuality = "FAIR"
+ // SignalQualityBad indicates a bad signal quality.
+ SignalQualityBad SignalQuality = "BAD"
)
-// getSignalQuality determines the signal quality based on RSSI and SNR.
-func getSignalQuality(rssi, snr float64) signalQuality {
+// GetSignalQuality determines the signal quality based on RSSI and SNR.
+// A GOOD signal is determined by SNR >= -7 and RSSI >= -115.
+// A FAIR signal is determined by SNR >= -15 and RSSI >= -126.
+// Any signal that does not meet the GOOD or FAIR criteria is considered BAD.
+func GetSignalQuality(rssi, snr float64) SignalQuality {
// Define the boundaries for GOOD signal quality
if snr >= -7 && rssi >= -115 {
- return Good
+ return SignalQualityGood
}
// Define the boundaries for FAIR signal quality
if snr >= -15 && rssi >= -126 {
- return Fair
+ return SignalQualityFair
}
// If none of the above conditions are met, signal is BAD
- return Bad
+ return SignalQualityBad
}
-// getDiagnosticNotes provides recommendations based on RSSI and SNR values.
-func getDiagnosticNotes(rssi, snr float64) diagnosticNote {
+// GetDiagnosticNotes provides recommendations based on RSSI and SNR values.
+// It returns different notes depending on the quality of the RF level.
+func GetDiagnosticNotes(rssi, snr float64) string {
if rssi >= -115 && snr >= -7 {
return "RF level is optimal to get a good reception reliability."
} else if rssi >= -126 && snr >= -15 {
@@ -39,14 +45,22 @@ func getDiagnosticNotes(rssi, snr float64) diagnosticNote {
}
}
-func Demo() {
- // Example usage
+// ExampleGetSignalQuality demonstrates the usage of the GetSignalQuality function.
+func ExampleGetSignalQuality() {
rssi := -120.0 // RSSI value
snr := -10.0 // SNR value
- quality := getSignalQuality(rssi, snr)
- notes := getDiagnosticNotes(rssi, snr)
-
+ quality := GetSignalQuality(rssi, snr)
fmt.Printf("The signal quality is %s.\n", quality)
+ // Output: The signal quality is FAIR.
+}
+
+// ExampleGetDiagnosticNotes demonstrates the usage of the GetDiagnosticNotes function.
+func ExampleGetDiagnosticNotes() {
+ rssi := -130.0 // RSSI value
+ snr := -20.0 // SNR value
+
+ notes := GetDiagnosticNotes(rssi, snr)
fmt.Printf("Diagnostic Notes: %s\n", notes)
+ // Output: Diagnostic Notes: NOISY environment. Try to put device out of electromagnetic sources.
}
diff --git a/lora/lora_test.go b/lora/lora_test.go
new file mode 100644
index 0000000..43d0b7b
--- /dev/null
+++ b/lora/lora_test.go
@@ -0,0 +1,43 @@
+package lora
+
+import "testing"
+
+// TestGetSignalQuality tests the GetSignalQuality function with different RSSI and SNR values.
+func TestGetSignalQuality(t *testing.T) {
+ tests := []struct {
+ rssi float64
+ snr float64
+ expected SignalQuality
+ }{
+ {-115, -7, SignalQualityGood},
+ {-120, -10, SignalQualityFair},
+ {-130, -20, SignalQualityBad},
+ }
+
+ for _, test := range tests {
+ actual := GetSignalQuality(test.rssi, test.snr)
+ if actual != test.expected {
+ t.Errorf("GetSignalQuality(%v, %v) = %v; want %v", test.rssi, test.snr, actual, test.expected)
+ }
+ }
+}
+
+// TestGetDiagnosticNotes tests the GetDiagnosticNotes function with different RSSI and SNR values.
+func TestGetDiagnosticNotes(t *testing.T) {
+ tests := []struct {
+ rssi float64
+ snr float64
+ expected string
+ }{
+ {-115, -7, "RF level is optimal to get a good reception reliability."},
+ {-126, -15, "RF level is not optimal but must be sufficient. Try to improve your device position if possible. You will have to monitor the stability of the RF level."},
+ {-130, -20, "NOISY environment. Try to put device out of electromagnetic sources."},
+ }
+
+ for _, test := range tests {
+ actual := GetDiagnosticNotes(test.rssi, test.snr)
+ if actual != test.expected {
+ t.Errorf("GetDiagnosticNotes(%v, %v) = %v; want %v", test.rssi, test.snr, actual, test.expected)
+ }
+ }
+}
diff --git a/mqtt/client.go b/mqtt/client.go
index 913b8af..c3746b5 100644
--- a/mqtt/client.go
+++ b/mqtt/client.go
@@ -26,7 +26,7 @@ var DefaultClient = Client{
server: "tcp://mqtt.meshtastic.org:1883",
username: "meshdev",
password: "large4cats",
- topicRoot: "msh/2",
+ topicRoot: "msh", //TODO: this will need to change
channelHandlers: make(map[string][]HandlerFunc),
}
diff --git a/transport/handlers.go b/transport/handlers.go
new file mode 100644
index 0000000..b50dc03
--- /dev/null
+++ b/transport/handlers.go
@@ -0,0 +1,62 @@
+package transport
+
+import (
+ "fmt"
+ "google.golang.org/protobuf/proto"
+ "sync"
+)
+
+// MessageHandler defines the function signature for a handler that processes a protobuf message.
+type MessageHandler func(msg proto.Message)
+
+// HandlerRegistry holds registered handlers for protobuf messages.
+type HandlerRegistry struct {
+ errorOnNoHandlers bool
+ mu sync.RWMutex
+ handlers map[string][]MessageHandler
+}
+
+// New creates a new instance of HandlerRegistry. Set errorOnNoHandler to true if you want HandleMessage to return
+// an error if there are no handlers registered for a given msg when HandleMessage is called.
+func NewHandlerRegistry(errorOnNoHandler bool) *HandlerRegistry {
+ return &HandlerRegistry{
+ errorOnNoHandlers: errorOnNoHandler,
+ handlers: make(map[string][]MessageHandler),
+ }
+}
+
+// RegisterHandler registers a handler for a specific protobuf message type.
+func (r *HandlerRegistry) RegisterHandler(msg proto.Message, handler MessageHandler) {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ msgName := proto.MessageName(msg)
+ if msgName == "" {
+ return // Could not get message name; consider logging or handling the error
+ }
+ name := string(msgName)
+ r.handlers[name] = append(r.handlers[name], handler)
+}
+
+// HandleMessage invokes all registered handlers for the provided protobuf message, in the order they were registered.
+func (r *HandlerRegistry) HandleMessage(msg proto.Message) error {
+ r.mu.RLock()
+ defer r.mu.RUnlock()
+
+ msgName := proto.MessageName(msg)
+ if msgName == "" {
+ return fmt.Errorf("failed to get message name for type: %T", msg) // Could not get message name; consider logging or handling the error
+ }
+ name := string(msgName)
+
+ if handlers, exists := r.handlers[name]; exists {
+ for _, handler := range handlers {
+ go handler(msg)
+ }
+
+ } else if r.errorOnNoHandlers {
+ return fmt.Errorf("no handlers registered for message: %s", msgName)
+ }
+
+ return nil
+}
diff --git a/transport/serial/serial.go b/transport/serial/serial.go
index cea1dcc..618c16d 100644
--- a/transport/serial/serial.go
+++ b/transport/serial/serial.go
@@ -5,6 +5,7 @@ import (
"encoding/binary"
"fmt"
"github.com/charmbracelet/log"
+ "github.com/crypto-smoke/meshtastic-go/transport"
"go.bug.st/serial"
"google.golang.org/protobuf/proto"
"io"
@@ -20,32 +21,141 @@ const (
PORT_SPEED = 115200 //921600
)
+type HandlerFunc func(message proto.Message)
+
// Serial connection to a node
type Conn struct {
serialPort string
serialConn serial.Port
+ handlers *transport.HandlerRegistry
+
+ config struct {
+ complete bool
+ configID uint32
+ *meshtastic.MyNodeInfo
+ *meshtastic.DeviceMetadata
+ nodes []*meshtastic.NodeInfo
+ channels []*meshtastic.Channel
+ config []*meshtastic.Config
+ modules []*meshtastic.ModuleConfig
+ }
}
-func NewConn(port string) *Conn {
- var c = Conn{serialPort: port}
+func NewConn(port string, errorOnNoHandler bool) *Conn {
+ var c = Conn{serialPort: port,
+ handlers: transport.NewHandlerRegistry(errorOnNoHandler)}
return &c
}
// You have to send this first to get the radio into protobuf mode and have it accept and send packets via serial
func (c *Conn) sendGetConfig() {
r := rand.Uint32()
-
+ c.config.configID = r
//log.Info("want config id", r)
msg := &meshtastic.ToRadio{
PayloadVariant: &meshtastic.ToRadio_WantConfigId{
WantConfigId: r,
},
}
- c.sendToRadio(msg)
+ c.SendToRadio(msg)
}
+func (c *Conn) Handle(kind proto.Message, handler transport.MessageHandler) {
+ c.handlers.RegisterHandler(kind, handler)
+}
+
+func (c *Conn) Connect() error {
+ mode := &serial.Mode{
+ BaudRate: PORT_SPEED,
+ }
+ port, err := serial.Open(c.serialPort, mode)
+ if err != nil {
+ return err
+ }
+ c.serialConn = port
+ ch := make(chan *meshtastic.FromRadio)
+ go c.decodeProtos(false, ch)
+ go func() {
+ for {
+ msg := <-ch
+ 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:
+ // done getting config info
+ //fmt.Println("config complete")
+ c.config.complete = true
+ /*
+ out, err := json.MarshalIndent(c.config, "", " ")
+ if err != nil {
+ log.Error("failed marshalling", "err", err)
+ continue
+ }
+ fmt.Println(string(out))
+ out, err = json.MarshalIndent(c.config.config, "", " ")
+ if err != nil {
+ log.Error("failed marshalling", "err", err)
+ continue
+ }
+ fmt.Println(string(out))
+
+ */
+ 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
+ fmt.Print("rebooted", msg.GetRebooted())
+ continue
+ case *meshtastic.FromRadio_XmodemPacket:
+ variant = msg.GetXmodemPacket()
+
+ case *meshtastic.FromRadio_Packet:
+ variant = msg.GetPacket()
+ default:
+ log.Error("unhandled protobuf from radio")
+ }
+ if !c.config.complete {
+ continue
+ }
+ err = c.handlers.HandleMessage(variant)
+ if err != nil {
+ log.Error("error handling message", "err", err)
+ }
+ }
+ }()
-// TODO: refactor this to lose the channels
-func (c *Conn) Connect(ch chan *meshtastic.FromRadio, ch2 chan *meshtastic.ToRadio) error {
+ c.sendGetConfig()
+ return nil
+}
+func (c *Conn) ConnectOld(ch chan *meshtastic.FromRadio, ch2 chan *meshtastic.ToRadio) error {
mode := &serial.Mode{
BaudRate: PORT_SPEED,
}
@@ -59,7 +169,7 @@ func (c *Conn) Connect(ch chan *meshtastic.FromRadio, ch2 chan *meshtastic.ToRad
go func() {
for {
msg := <-ch2
- c.sendToRadio(msg)
+ c.SendToRadio(msg)
}
}()
c.sendGetConfig()
@@ -220,7 +330,7 @@ func (c *Conn) flushPort() error {
}
return nil
}
-func (c *Conn) sendToRadio(msg *meshtastic.ToRadio) error {
+func (c *Conn) SendToRadio(msg *meshtastic.ToRadio) error {
err := c.flushPort()
if err != nil {
return err