mirror of
https://github.com/ajvpot/meshexplorer.git
synced 2026-06-29 14:20:59 +02:00
Merge pull request #51 from ajvpot/ajvpot/add-cascadiamesh-broker
Accept numeric SNR/len/RSSI in MeshCore packets messages
This commit is contained in:
@@ -177,25 +177,51 @@ func parseMeshCoreRawMessage(payload []byte) (origin string, originPubkey []byte
|
||||
return cleanOrigin, originPubkeyBytes, meshTimestamp, packet, nil
|
||||
}
|
||||
|
||||
// flexString unmarshals a JSON value that may be encoded as either a string or
|
||||
// a number into a string. Depending on gateway firmware, the numeric metric
|
||||
// fields below — SNR, len, etc. — arrive as JSON numbers (including negative
|
||||
// and fractional values) rather than strings; without this, a single numeric
|
||||
// field would fail the whole packet's unmarshal and drop the message, including
|
||||
// the raw packet bytes we actually need.
|
||||
type flexString string
|
||||
|
||||
func (f *flexString) UnmarshalJSON(data []byte) error {
|
||||
if len(data) == 0 || string(data) == "null" {
|
||||
*f = ""
|
||||
return nil
|
||||
}
|
||||
if data[0] == '"' {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
*f = flexString(s)
|
||||
return nil
|
||||
}
|
||||
// Non-string (number, bool): keep the raw JSON token as its text form.
|
||||
*f = flexString(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseMeshCorePacketsMessage(payload []byte) (origin string, originPubkey []byte, meshTimestamp time.Time, packet []byte, err error) {
|
||||
type PacketMessage struct {
|
||||
Origin string `json:"origin"`
|
||||
OriginID string `json:"origin_id"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Type string `json:"type"`
|
||||
Direction string `json:"direction"`
|
||||
Time string `json:"time"`
|
||||
Date string `json:"date"`
|
||||
Len string `json:"len"`
|
||||
PacketType string `json:"packet_type"`
|
||||
Route string `json:"route"`
|
||||
PayloadLen string `json:"payload_len"`
|
||||
Raw string `json:"raw"`
|
||||
SNR string `json:"SNR"`
|
||||
RSSI string `json:"RSSI"`
|
||||
Score string `json:"score"`
|
||||
Duration string `json:"duration"`
|
||||
Hash string `json:"hash"`
|
||||
Origin string `json:"origin"`
|
||||
OriginID string `json:"origin_id"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Type string `json:"type"`
|
||||
Direction string `json:"direction"`
|
||||
Time string `json:"time"`
|
||||
Date string `json:"date"`
|
||||
Len flexString `json:"len"`
|
||||
PacketType flexString `json:"packet_type"`
|
||||
Route string `json:"route"`
|
||||
PayloadLen flexString `json:"payload_len"`
|
||||
Raw string `json:"raw"`
|
||||
SNR flexString `json:"SNR"`
|
||||
RSSI flexString `json:"RSSI"`
|
||||
Score flexString `json:"score"`
|
||||
Duration flexString `json:"duration"`
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
var pkt PacketMessage
|
||||
if err = json.Unmarshal(payload, &pkt); err != nil {
|
||||
|
||||
@@ -265,3 +265,54 @@ func TestExtractGatewayID(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Some gateways encode the numeric metric fields — SNR, RSSI, len, etc. — as
|
||||
// bare JSON numbers, including negative and fractional values, rather than
|
||||
// strings. These must parse rather than dropping the whole packet (and the raw
|
||||
// bytes we need with it).
|
||||
func TestParseMeshCorePacketsMessage_NumericMetrics(t *testing.T) {
|
||||
payload := []byte(`{
|
||||
"timestamp": "2026-06-19T07:03:43.000000",
|
||||
"origin": "PDX Gateway Bridge 14",
|
||||
"origin_id": "FACE69B85A0D184C7D122164BDFADE87F25069455C85DD2824CAD3F6AA0B1929",
|
||||
"type": "PACKET", "direction": "rx", "len": 38, "payload_len": 20,
|
||||
"packet_type": 1, "route": "F",
|
||||
"raw": "05481D6B54CA61006000AEE498916968452A7994F827AFB6CE312721FFBE377BA3D113F924C6",
|
||||
"SNR": -9.25, "RSSI": -95, "score": 1000, "duration": 0
|
||||
}`)
|
||||
origin, originPubkey, meshTimestamp, packet, err := parseMeshCorePacketsMessage(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if origin != "PDX Gateway Bridge 14" {
|
||||
t.Errorf("unexpected origin: %s", origin)
|
||||
}
|
||||
if hex.EncodeToString(originPubkey) != strings.ToLower("FACE69B85A0D184C7D122164BDFADE87F25069455C85DD2824CAD3F6AA0B1929") {
|
||||
t.Errorf("unexpected origin_pubkey: %s", hex.EncodeToString(originPubkey))
|
||||
}
|
||||
if meshTimestamp.Format(time.RFC3339Nano) != "2026-06-19T07:03:43Z" {
|
||||
t.Errorf("unexpected meshTimestamp: %s", meshTimestamp)
|
||||
}
|
||||
if len(packet) == 0 {
|
||||
t.Error("packet should not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
// The same fields quoted as strings (other gateways) must keep working too.
|
||||
func TestParseMeshCorePacketsMessage_StringMetrics(t *testing.T) {
|
||||
payload := []byte(`{
|
||||
"timestamp": "2026-06-19T07:03:43.000000",
|
||||
"origin": "PDX Gateway Bridge 14",
|
||||
"origin_id": "FACE69B85A0D184C7D122164BDFADE87F25069455C85DD2824CAD3F6AA0B1929",
|
||||
"type": "PACKET", "direction": "rx", "len": "38", "payload_len": "20",
|
||||
"raw": "05481D6B54CA61006000AEE498916968452A7994F827AFB6CE312721FFBE377BA3D113F924C6",
|
||||
"SNR": "11.8", "RSSI": "-38"
|
||||
}`)
|
||||
_, _, _, packet, err := parseMeshCorePacketsMessage(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(packet) == 0 {
|
||||
t.Error("packet should not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user