meshmap.net/internal/meshtastic/node.go

230 lines
5.6 KiB
Go

package meshtastic
import (
"encoding/json"
"os"
"path/filepath"
"time"
)
const (
SeenByLimit = 10
NeighborLimit = 100
)
type NeighborInfo struct {
Snr float32 `json:"snr,omitempty"`
Updated int64 `json:"updated"`
}
type Node struct {
// User
LongName string `json:"longName"`
ShortName string `json:"shortName"`
HwModel string `json:"hwModel"`
Role string `json:"role"`
// MapReport
FwVersion string `json:"fwVersion,omitempty"`
Region string `json:"region,omitempty"`
ModemPreset string `json:"modemPreset,omitempty"`
HasDefaultCh bool `json:"hasDefaultCh,omitempty"`
OnlineLocalNodes uint32 `json:"onlineLocalNodes,omitempty"`
LastMapReport int64 `json:"lastMapReport,omitempty"`
// Position
Latitude int32 `json:"latitude"`
Longitude int32 `json:"longitude"`
Altitude int32 `json:"altitude,omitempty"`
Precision uint32 `json:"precision,omitempty"`
// DeviceMetrics
BatteryLevel uint32 `json:"batteryLevel,omitempty"`
Voltage float32 `json:"voltage,omitempty"`
ChUtil float32 `json:"chUtil,omitempty"`
AirUtilTx float32 `json:"airUtilTx,omitempty"`
Uptime uint32 `json:"uptime,omitempty"`
LastDeviceMetrics int64 `json:"lastDeviceMetrics,omitempty"`
// NeighborInfo
Neighbors map[uint32]*NeighborInfo `json:"neighbors,omitempty"`
// key=mqtt topic, value=first seen/last position update
SeenBy map[string]int64 `json:"seenBy"`
}
func NewNode(topic string) *Node {
return &Node{
SeenBy: map[string]int64{topic: time.Now().Unix()},
}
}
func (node *Node) ClearDeviceMetrics() {
node.BatteryLevel = 0
node.Voltage = 0
node.ChUtil = 0
node.AirUtilTx = 0
node.Uptime = 0
node.LastDeviceMetrics = 0
}
func (node *Node) ClearMapReportData() {
node.FwVersion = ""
node.Region = ""
node.ModemPreset = ""
node.HasDefaultCh = false
node.OnlineLocalNodes = 0
node.LastMapReport = 0
}
func (node *Node) IsValid() bool {
if len(node.SeenBy) == 0 {
return false
}
if len(node.LongName) == 0 {
return false
}
if node.Latitude == 0 && node.Longitude == 0 {
return false
}
return true
}
func (node *Node) Prune(seenByTtl, neighborTtl, deviceMetricsTtl, mapReportTtl int64) {
now := time.Now().Unix()
// SeenBy
for topic, lastSeen := range node.SeenBy {
if lastSeen+seenByTtl < now {
delete(node.SeenBy, topic)
}
}
for len(node.SeenBy) > SeenByLimit {
var toDelete string
for topic, lastSeen := range node.SeenBy {
if len(toDelete) == 0 || lastSeen < node.SeenBy[toDelete] {
toDelete = topic
}
}
delete(node.SeenBy, toDelete)
}
// Neighbors
for neighborNum, neighbor := range node.Neighbors {
if neighbor.Updated+neighborTtl < now {
delete(node.Neighbors, neighborNum)
}
}
if len(node.Neighbors) == 0 {
node.Neighbors = nil
}
for len(node.Neighbors) > NeighborLimit {
var toDelete uint32
for neighborNum, neighbor := range node.Neighbors {
if toDelete == 0 || neighbor.Updated < node.Neighbors[toDelete].Updated {
toDelete = neighborNum
}
}
delete(node.Neighbors, toDelete)
}
// DeviceMetrics
if node.LastDeviceMetrics > 0 && node.LastDeviceMetrics+deviceMetricsTtl < now {
node.ClearDeviceMetrics()
}
// MapReport
if node.LastMapReport > 0 && node.LastMapReport+mapReportTtl < now {
node.ClearMapReportData()
}
}
func (node *Node) UpdateDeviceMetrics(batteryLevel uint32, voltage, chUtil, airUtilTx float32, uptime uint32) {
node.BatteryLevel = batteryLevel
node.Voltage = voltage
node.ChUtil = chUtil
node.AirUtilTx = airUtilTx
node.Uptime = uptime
node.LastDeviceMetrics = time.Now().Unix()
}
func (node *Node) UpdateMapReport(fwVersion, region, modemPreset string, hasDefaultCh bool, onlineLocalNodes uint32) {
node.FwVersion = fwVersion
node.Region = region
node.ModemPreset = modemPreset
node.HasDefaultCh = hasDefaultCh
node.OnlineLocalNodes = onlineLocalNodes
node.LastMapReport = time.Now().Unix()
}
func (node *Node) UpdateNeighborInfo(neighborNum uint32, snr float32) {
if node.Neighbors == nil {
node.Neighbors = make(map[uint32]*NeighborInfo)
}
node.Neighbors[neighborNum] = &NeighborInfo{
Snr: snr,
Updated: time.Now().Unix(),
}
}
func (node *Node) UpdatePosition(latitude, longitude, altitude int32, precision uint32) {
node.Latitude = latitude
node.Longitude = longitude
node.Altitude = altitude
node.Precision = precision
}
func (node *Node) UpdateSeenBy(topic string) {
node.SeenBy[topic] = time.Now().Unix()
}
func (node *Node) UpdateUser(longName, shortName, hwModel, role string) {
node.LongName = longName
node.ShortName = shortName
node.HwModel = hwModel
node.Role = role
}
type NodeDB map[uint32]*Node
func (db NodeDB) Prune(seenByTtl, neighborTtl, deviceMetricsTtl, mapReportTtl int64) {
for nodeNum, node := range db {
node.Prune(seenByTtl, neighborTtl, deviceMetricsTtl, mapReportTtl)
if len(node.SeenBy) == 0 {
delete(db, nodeNum)
}
}
}
func (db NodeDB) GetValid() NodeDB {
valid := make(NodeDB)
for nodeNum, node := range db {
if node.IsValid() {
valid[nodeNum] = node
}
}
return valid
}
func (db *NodeDB) LoadFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
return json.NewDecoder(f).Decode(db)
}
func (db NodeDB) WriteFile(path string) error {
dir, file := filepath.Split(path)
f, err := os.CreateTemp(dir, file)
if err != nil {
return err
}
err = json.NewEncoder(f).Encode(db)
if err1 := f.Close(); err1 != nil && err == nil {
err = err1
}
if err == nil {
err = os.Chmod(f.Name(), 0644)
}
if err == nil {
err = os.Rename(f.Name(), path)
}
if err != nil {
os.Remove(f.Name())
}
return err
}