package main import ( "context" "encoding/base64" "encoding/binary" "fmt" "os" "os/signal" "time" "github.com/charmbracelet/log" "github.com/meshnet-gophers/meshtastic-go" pb "github.com/meshnet-gophers/meshtastic-go/meshtastic" flag "github.com/spf13/pflag" "golang.org/x/crypto/curve25519" ) func main() { var args struct { DbDir string LogsDir string ConfigPath string Interface string Relentless bool } flag.StringVarP(&args.DbDir, "dbdir", "", "./data/", "The path to the database directory") flag.StringVarP(&args.LogsDir, "logsdir", "", "./logs/", "The path to the logs directory") flag.StringVarP(&args.ConfigPath, "config", "c", "./config.json", "The path to the config.json") flag.StringVarP(&args.Interface, "interface", "i", "kava", "Mesh interface (kava, mqtt)") flag.BoolVarP(&args.Relentless, "relentless", "r", false, "Relentless reconnect to modem") flag.Parse() log.SetLevel(log.DebugLevel) cfg, err := ReadConfig(args.ConfigPath) if err != nil { log.Fatal(err) } pbChannels := make([]*pb.ChannelSettings, len(cfg.Channels)) for i, channel := range cfg.Channels { var pbChannelSettings = pb.ChannelSettings{ Name: channel.Name, Psk: channel.PSK, } pbChannels[i] = &pbChannelSettings } var nodeID meshtastic.NodeID if len(cfg.NodeID) == 4 { nodeID = meshtastic.NodeID(binary.BigEndian.Uint32(cfg.NodeID)) } else if len(cfg.NodeID) == 0 { nodeID, err = meshtastic.RandomNodeID() if err != nil { log.Fatal(err) } } log.Print("Config:") log.Print(" Database", "directory", args.DbDir) log.Print(" Logs", "directory", args.LogsDir) log.Print(" Node", "id", nodeID, "short", cfg.ShortName, "name", cfg.Name) nodeConfig := NodeConfig{ DatabaseDir: args.DbDir, LogsDir: args.LogsDir, NodeID: nodeID, LongName: cfg.Name, ShortName: cfg.ShortName, DefaultHops: cfg.DefaultHops, MaxHops: cfg.MaxHops, Channels: &pb.ChannelSet{Settings: pbChannels}, BroadcastNodeInfoInterval: time.Duration(cfg.NodeBroadcastInterval * float64(time.Second)), BroadcastPositionInterval: time.Duration(cfg.Position.BroadcastInterval * float64(time.Second)), // Position paramters PositionLatitudeI: int32(cfg.Position.Latitude * 1.e7), PositionLongitudeI: int32(cfg.Position.Longitude * 1.e7), PositionAltitude: int32(cfg.Position.Altitude), // Device Metrics DeviceMetricsBroadcastInterval: time.Duration(cfg.DeviceMetrics.BroadcastInterval * float64(time.Second)), DeviceMetricsUPSAddress: cfg.DeviceMetrics.UPSAddress, TCPListenAddr: "0.0.0.0:4403", } if len(cfg.X25519Key) == 32 { nodeConfig.X25519SecretKey = cfg.X25519Key nodeConfig.X25519PublicKey, err = curve25519.X25519(nodeConfig.X25519SecretKey, curve25519.Basepoint) if err != nil { log.Fatal(err) } log.Print(" X25519PublicKey", "base64", base64.StdEncoding.EncodeToString(nodeConfig.X25519PublicKey), "hex", fmt.Sprintf("%x", nodeConfig.X25519PublicKey)) } else if len(cfg.X25519Key) != 0 { log.Fatal("invalid x25519 key") } if args.Relentless { log.Warn("Starting Node in 'relentless' mode...") } else { log.Info("Starting Node...") } n, err := NewNode(nodeConfig) if err != nil { log.Fatal(err) } defer n.Close() ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt) for { var iface MeshIf switch args.Interface { case "kava": iface = NewKavaConn(cfg.KavaModem.Address, 10*time.Second, n.cfg.Channels) case "mqtt": iface = NewMqttIf(cfg.MqttIf.Address, cfg.MqttIf.Username, cfg.MqttIf.Password, cfg.MqttIf.Root, cfg.MqttIf.ClientID, meshtastic.NodeID(nodeID), n.cfg.Channels) default: log.Fatal("invalid argument", "interface", args.Interface) } if err := n.Run(ctx, iface); err != nil { log.Error("!! Node.Run()", "err", err) } if err := ctx.Err(); err != nil { log.Error("!! Caught interrupt!") break } if !args.Relentless { break } log.Warn("Reconnecting in 30 seconds...") ticker := time.NewTicker(30 * time.Second) select { case <-ctx.Done(): return case <-ticker.C: } } }