package hamview import ( "database/sql" "encoding/json" "errors" "fmt" "io" "net" "os" "time" "github.com/Vaniog/go-postgis" "github.com/tarm/serial" "go.yaml.in/yaml/v3" "git.maze.io/go/ham/protocol" "git.maze.io/go/ham/protocol/meshcore" ) type MeshCoreConfig struct { Type string `yaml:"type"` Conf yaml.Node `yaml:"conf"` } type MeshCoreCompanionConfig struct { Port string `yaml:"port"` Baud int `yaml:"baud"` Addr string `yaml:"addr"` } type MeshCorePrefix byte func (prefix *MeshCorePrefix) MarshalJSON() ([]byte, error) { s := fmt.Sprintf("%02x", *prefix) return json.Marshal(s) } func (prefix *MeshCorePrefix) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } if n, err := fmt.Sscanf(s, "%02x", &prefix); err != nil { return err } else if n != 1 { return errors.New("no prefix could be decoded") } return nil } func NewMeshCoreReceiver(config *MeshCoreConfig) (protocol.PacketReceiver, error) { switch config.Type { case "companion", "": return newMeshCoreCompanionReceiver(config.Conf) default: return nil, fmt.Errorf("hamview: unsupported MeshCore node type %q", config.Type) } } func newMeshCoreCompanionReceiver(node yaml.Node) (protocol.PacketReceiver, error) { var config MeshCoreCompanionConfig if err := node.Decode(&config); err != nil { return nil, err } var ( conn io.ReadWriteCloser err error ) switch { case config.Addr != "": Logger.Infof("receiver: connecting to MeshCore companion at tcp://%s", config.Addr) conn, err = net.Dial("tcp", config.Addr) default: if config.Port == "" { // TODO: detect serial ports config.Port = "/dev/ttyUSB0" } if config.Baud == 0 { config.Baud = 115200 } Logger.Infof("receiver: connecting to MeshCore companion on %s at %d baud", config.Port, config.Baud) conn, err = serial.OpenPort(&serial.Config{ Name: config.Port, Baud: config.Baud, }) } if err != nil { return nil, err } receiver, err := meshcore.NewCompanion(conn) if err != nil { _ = conn.Close() Logger.Warnf("receiver: error connecting to companion: %v", err) return nil, err } info := receiver.Info() Logger.Infof("receiver: connected to MeshCore Companion %q model %q version %q", info.Name, info.Manufacturer, info.FirmwareVersion) return receiver, nil } type meshCoreNode struct { Name string `json:"name"` PublicKey []byte `json:"public_key"` Prefix MeshCorePrefix `json:"prefix"` NodeType meshcore.NodeType `json:"node_type"` FirstHeard time.Time `json:"first_heard"` LastHeard time.Time `json:"last_heard"` Position *meshcore.Position `json:"position"` } type meshCoreNodeDistance struct { meshCoreNode Distance float64 `json:"distance"` } func meshCoreRepeaterWithPrefixCloseTo(db *sql.DB, prefix MeshCorePrefix, position *meshcore.Position) (node *meshCoreNodeDistance, err error) { if position == nil { return nil, os.ErrNotExist } node = &meshCoreNodeDistance{ meshCoreNode: meshCoreNode{ Position: new(meshcore.Position), }, } var nodePrefix []byte if err = db.QueryRow(` SELECT n.name, n.public_key, n.prefix, n.first_heard, n.last_heard, n.last_latitude, n.last_longitude, ST_DistanceSphere( last_position, GeomFromEWKB($2) ) AS distance FROM meshcore_node n WHERE n.prefix = $1 AND n.last_latitude IS NOT NULL AND n.last_longitude IS NOT NULL AND n.last_position IS NOT NULL ORDER BY distance ASC LIMIT 1 `, []byte{byte(prefix)}, postgis.PointS{ SRID: 4326, X: position.Latitude, Y: position.Longitude, }, ).Scan( &node.Name, &node.PublicKey, &nodePrefix, &node.FirstHeard, &node.LastHeard, &node.Position.Latitude, &node.Position.Longitude, &node.Distance, ); err != nil { if err == sql.ErrNoRows { return nil, os.ErrNotExist } return } if len(nodePrefix) > 0 { node.Prefix = MeshCorePrefix(nodePrefix[0]) } return }