diff options
| author | Smoke <[email protected]> | 2024-01-22 15:56:16 -1000 |
|---|---|---|
| committer | Smoke <[email protected]> | 2024-01-22 15:56:16 -1000 |
| commit | 5fa965d66db226df3b94131c9b0933822d55c985 (patch) | |
| tree | 432f82aec644c24c6d325d4407801d37e9b2a75e | |
| parent | 70bb2c77356d349165ba46ea98f8346284c2e44e (diff) | |
updates
| -rw-r--r-- | .idea/.gitignore | 8 | ||||
| -rw-r--r-- | .idea/meshtastic-go.iml | 9 | ||||
| -rw-r--r-- | .idea/modules.xml | 8 | ||||
| -rw-r--r-- | .idea/vcs.xml | 6 | ||||
| -rw-r--r-- | lora/helpers.go | 54 | ||||
| -rw-r--r-- | lora/lora_test.go | 43 | ||||
| -rw-r--r-- | mqtt/client.go | 2 | ||||
| -rw-r--r-- | transport/handlers.go | 62 | ||||
| -rw-r--r-- | transport/serial/serial.go | 126 |
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 |
