Update decoder to match correct Meshtastic MQTT topic structure

This commit is contained in:
Daniel Pupius
2025-04-18 16:28:09 -07:00
parent c6013d1038
commit 5537556d22
3 changed files with 94 additions and 23 deletions
+52 -1
View File
@@ -1 +1,52 @@
- Never run the program, you may build it and run tests, but I will run the program manually.
# Meshtastic MQTT Protocol Structure
## Topic Structure
The Meshtastic MQTT topic structure follows this pattern:
```
msh/REGION/2/e/CHANNELNAME/USERID
```
- `msh`: Fixed prefix for all Meshtastic topics
- `REGION`: Geographic region code (e.g., `US`, `EU`, `AU`)
- `2`: Protocol version
- `e`: Encrypted channel indicator (versions before 2.3.0 used `/c/` instead)
- `CHANNELNAME`: The channel name used in the Meshtastic network
- `USERID`: Unique identifier for the device, formats include:
- `!` followed by hex characters for MAC address based IDs
- `+` followed by phone number for Signal-based IDs
## Message Types
### Protobuf Messages (Binary)
Topic pattern: `msh/REGION/2/e/CHANNELNAME/USERID`
- Raw MeshPacket transmissions in protobuf format
- Can be encrypted or unencrypted based on channel settings
### JSON Messages
Topic pattern: `msh/REGION/2/json/CHANNELNAME/USERID`
- Structured JSON payloads with fields like:
- `id`: Message ID
- `from`: Node ID of sender
- `to`: Node ID of recipient (or blank for broadcast)
- `payload`: The actual message content
- `timestamp`: Time the message was received
Note: JSON format is not supported on nRF52 platform devices.
### Special Topics
- MQTT Downlink: `msh/REGION/2/json/mqtt/`
- Used for sending instructions to gateway nodes
## Important Notes
- The public MQTT server implements a zero-hop policy (only direct messages)
- JSON messages may include specific data types like:
- NodeInfo
- Telemetry
- Text Messages
- Position Updates
- Position data on public servers has reduced precision for privacy
- Binary messages use the protocol buffers format defined in the Meshtastic codebase
This corrects the previous assumption that the topic structure was `msh/REGION/STATE/NAME`, which was incorrect.
+38 -21
View File
@@ -19,7 +19,9 @@ const (
// DecodedPacket contains information about a decoded packet
type DecodedPacket struct {
Topic string
Region string
Channel string
UserID string
Type PacketType
JSONData map[string]interface{}
FromNode string
@@ -38,20 +40,40 @@ func DecodePacket(topic string, payload []byte) (*DecodedPacket, error) {
}
// Extract channel and other info from topic
// Format: msh/REGION/2/e/CHANNELNAME/USERID
// or: msh/REGION/2/json/CHANNELNAME/USERID
parts := strings.Split(topic, "/")
if len(parts) < 4 {
return packet, fmt.Errorf("invalid topic format: %s", topic)
}
// Set channel info (typically msh/REGION/STATE/NAME)
packet.Channel = strings.Join(parts[1:4], "/")
// Set the region
if len(parts) >= 2 {
packet.Region = parts[1]
}
// Try to determine type from topic structure
// Extract channel name - should be part 4 for binary or part 5 for JSON
if len(parts) >= 5 {
typeIndicator := parts[4]
switch typeIndicator {
case "json":
if parts[3] == "e" || parts[3] == "c" {
// Binary format: msh/REGION/2/e/CHANNELNAME/USERID
if len(parts) >= 5 {
packet.Channel = parts[4]
}
if len(parts) >= 6 {
packet.UserID = parts[5]
}
// This is a binary protobuf packet
packet.Type = TypeBinary
} else if parts[3] == "json" {
// JSON format: msh/REGION/2/json/CHANNELNAME/USERID
if len(parts) >= 5 {
packet.Channel = parts[4]
}
if len(parts) >= 6 {
packet.UserID = parts[5]
}
// This is a JSON packet
packet.Type = TypeJSON
if err := json.Unmarshal(payload, &packet.JSONData); err != nil {
return packet, fmt.Errorf("failed to parse JSON: %v", err)
@@ -70,19 +92,8 @@ func DecodePacket(topic string, payload []byte) (*DecodedPacket, error) {
if ts, ok := packet.JSONData["timestamp"].(string); ok {
packet.Timestamp = ts
}
case "binary":
// Binary protobuf payload
packet.Type = TypeBinary
// Note: Actual protobuf decoding would be done here
case "text":
// Plain text payload
packet.Type = TypeText
packet.Text = string(payload)
default:
// Try to infer type from payload
} else {
// Unknown format, try to infer from content
if len(payload) > 0 && payload[0] == '{' {
// Looks like JSON
packet.Type = TypeJSON
@@ -113,7 +124,7 @@ func DecodePacket(topic string, payload []byte) (*DecodedPacket, error) {
}
}
}
return packet, nil
}
@@ -139,7 +150,13 @@ func FormatPacket(packet *DecodedPacket) string {
var builder strings.Builder
builder.WriteString(fmt.Sprintf("Topic: %s\n", packet.Topic))
// Show basic topic structure
builder.WriteString(fmt.Sprintf("Region: %s\n", packet.Region))
builder.WriteString(fmt.Sprintf("Channel: %s\n", packet.Channel))
if packet.UserID != "" {
builder.WriteString(fmt.Sprintf("User ID: %s\n", packet.UserID))
}
switch packet.Type {
case TypeJSON:
+4 -1
View File
@@ -67,7 +67,10 @@ func main() {
log.Fatalf("Error connecting to MQTT broker: %v", token.Error())
}
// Subscribe to topic
// Subscribe to all topics for this region
// This will capture:
// - msh/US/CA/Motherlode/2/e/# (binary protobuf data)
// - msh/US/CA/Motherlode/2/json/# (JSON formatted data)
topic := mqttTopicPrefix + "/#"
token := client.Subscribe(topic, 0, nil)
token.Wait()