mirror of
https://github.com/brianshea2/meshmap.net.git
synced 2024-11-09 23:24:09 -08:00
228 lines
5.5 KiB
Go
228 lines
5.5 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"`
|
|
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 int32, precision uint32) {
|
|
node.Latitude = latitude
|
|
node.Longitude = longitude
|
|
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
|
|
}
|