package main import ( "context" "database/sql" "encoding/hex" "encoding/json" "os" "time" "github.com/sirupsen/logrus" "github.com/urfave/cli/v3" "git.maze.io/go/ham/protocol/meshcore" "git.maze.io/ham/hamview" "git.maze.io/ham/hamview/cmd" _ "github.com/cridenour/go-postgis" // PostGIS support _ "github.com/lib/pq" // PostgreSQL support ) var logger = logrus.New() /* { "public_key": "E119666239EE254E8E7B2937A99FE9DB7CBB58040B5D0E995B719C598CD261F6", "name": "~ Jonzy Heltec Repeater", "device_role": 2, "regions": [ "OMA" ], "first_seen": "2026-01-18T04:31:21.694Z", "last_seen": "2026-02-20T10:30:16.144Z", "is_mqtt_connected": true, "decoded_payload": { "lat": 41.28516, "lon": -96.13876, "mode": "Repeater", "name": "~ Jonzy Heltec Repeater", "flags": 146, "is_valid": true, "signature": "F08599E4D7357E9276B5F78246C698BFFCF14EC83D8A70CAB6F8E63EDF3FEB5CB692A60C3072593ABE0261B164709F9E012AC526B5EF08407B3520C13719900E", "timestamp": 1771583405, "public_key": "E119666239EE254E8E7B2937A99FE9DB7CBB58040B5D0E995B719C598CD261F6" }, "location": { "latitude": 41.28516, "longitude": -96.13876 }, "node_settings": { "show_neighbors": true, "show_adverts": true } }, */ type node struct { PublicKey string `json:"public_key"` Name string `json:"name"` Type int `json:"device_role"` FirstHeard time.Time `json:"first_seen"` LastHeard time.Time `json:"last_seen"` Position *meshcore.Position `json:"location"` Payload payload `json:"decoded_payload` } type payload struct { Timestamp int64 `json:"timestamp"` } func main() { cmd := &cli.Command{ Name: "import-letsmesh-nodes", Action: run, Before: cmd.ConfigureLogging(&logger), Flags: append([]cli.Flag{ &cli.StringFlag{ Name: "dump", Usage: "letsmesh node json", Value: "letsmeshnodes.json", }, }, cmd.AllFlags("hamview-collector.yaml")...), } if err := cmd.Run(context.Background(), os.Args); err != nil { logger.Fatal(err) } } type collectorConfig struct { hamview.CollectorConfig `yaml:",inline"` Broker map[string]any `yaml:"broker"` Meshcore map[string]any `yaml:"meshcore"` Include []string } func (config *collectorConfig) Includes() []string { includes := config.Include config.Include = nil return includes } func run(ctx context.Context, command *cli.Command) error { var config collectorConfig if err := cmd.Load(logger, command.String(cmd.FlagConfig), &config); err != nil { return err } db, err := sql.Open(config.Database.Type, config.Database.Conf) if err != nil { return err } defer db.Close() b, err := os.ReadFile(command.String("dump")) if err != nil { return err } var nodes struct { Nodes []*node `json:"nodes"` } if err = json.Unmarshal(b, &nodes); err != nil { return err } logger.Infof("found %d nodes", len(nodes.Nodes)) for _, node := range nodes.Nodes { k, err := hex.DecodeString(node.PublicKey) if err != nil { logger.Warnf("node %s has incorrect public key: %v", node.Name, err) continue } logger.Infof("node %s at %s", node.Name, node.Position) var latitude, longitude *float64 if node.Position != nil { latitude = &node.Position.Latitude longitude = &node.Position.Longitude } if _, err = db.Exec( `INSERT INTO meshcore_node ( node_type, public_key, name, local_time, first_heard, last_heard, last_latitude, last_longitude ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8 ) ON CONFLICT (public_key) DO UPDATE SET name = $3, local_time = $4, last_heard = $6, last_latitude = $7, last_longitude = $8 `, node.Type, k, node.Name, time.Unix(node.Payload.Timestamp, 0), node.FirstHeard, node.LastHeard, latitude, longitude, ); err != nil { logger.Fatalf("node %s insert failed: %v", node.Name, err) } } return nil }