From 89c19cf9b81002ce4d2ffdb15039884ea5f656fc Mon Sep 17 00:00:00 2001 From: Torsten Harenberg Date: Fri, 21 Feb 2025 14:25:36 +0100 Subject: [PATCH] NMEA pass-through mode --- README.md | 42 ++++++++++---- gpsd.go | 171 +++++++++++++++++++++++++++++++----------------------- main.go | 42 ++++++++------ 3 files changed, 152 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 5bbbd93..4274e81 100644 --- a/README.md +++ b/README.md @@ -42,16 +42,23 @@ cmdline_init: "" vara_mode: false ``` -| Variable | Meaning | -|-------------------|-----------------------------------------------------------------------------------------------------------------------------| -| `device` | Path to the serial device where the modem is connected **or** `tcp://address:port` (useful for Android) | -| `baudrate` | baud rate of the modem | -| `mycall` | The callsign to be used on HF | -| `server_address` | server socket address for the **commands** | -| `data_address` | server socket address for the **data** | -| `gpsd_address` | **optional** See the chapter about GPS below | +Next time ptb will start using the settings in your config file. + +## Settings + +The meaning of the vables in the config file are as follows: + +| Variable | Meaning | +|-------------------|----------------------------------------------------------------------------------------------------------------| +| `device` | Path to the serial device where the modem is connected **or** `tcp://address:port` (useful for Android) | +| `baudrate` | baud rate of the modem | +| `mycall` | The callsign to be used on HF | +| `server_address` | server socket address for the **commands** | +| `data_address` | server socket address for the **data** | +| `gpsd_address` | **optional** See the chapter about GPS below | +| `nmeapassthrough` | **only when gpsd_address has been set** bool value: if true pass NMEA data trough instead of interpreting them | | `cmdline_init` | extra commands sent to the modem before going into hostmode, separated by semicolons, Ex: `DISP BR 1;DISP A 1;DISP DIMMOFF` | -| `vara_mode` | see the chapter about the VARA mode | +| `vara_mode` | see the chapter about the VARA mode | @@ -97,7 +104,9 @@ to make your position visible to Pat (or any other gpsd client). To configure it you just need to add a line like -`gpsd_address: 0.0.0.0:2947` +```yaml +gpsd_address: 0.0.0.0:2947` +``` to the config file. In Pat, a possible counterpart could look like this: @@ -110,6 +119,19 @@ to the config file. In Pat, a possible counterpart could look like this: }, ``` +### NMEA Pass-Through + +If you need the **raw** NMEA data (for example if your GPS is not supported by the built-in mini-gpsd, or you want to +run your own gpsd), set `nmeapassthough` to `true`, for example: + +```yaml +gpsd_address: 0.0.0.0:8888 +nmeapassthough: true +``` + +Note that if you plan to use Pat, you'd need to run `gpsd` to use +the position of the GNSS receiver. + ## How to run As a rule of thumb, you have to start the PACTOR-TCP-bridge before you want to use diff --git a/gpsd.go b/gpsd.go index d6ba420..ffcbd46 100644 --- a/gpsd.go +++ b/gpsd.go @@ -130,24 +130,37 @@ func isNetConnClosedErr(err error) bool { } func addClient(client net.Conn) { - _, err := client.Write([]byte("{\"class\":\"VERSION\",\"release\":\"3.25\",\"rev\":\"3.25\",\"proto_major\":3,\"proto_minor\":15}\n")) - if err != nil { - writeDebug(fmt.Sprintf("Error writing to client: %v\n", err), 1) - } - - go func() { - defer func() { - client.Close() - removeClient(client) - writeDebug(fmt.Sprintf("GPSd Client disconnected: %v\n", client.RemoteAddr()), 0) + if s.NMEAPassthrough { + go func() { + defer func() { + client.Close() + removeClient(client) + writeDebug(fmt.Sprintf("GPSd Client disconnected: %v\n", client.RemoteAddr()), 0) + }() + rd := bufio.NewScanner(client) + for rd.Scan() { + writeDebug(fmt.Sprintf("GPSd Client received message: %v\n", rd.Text()), 0) + } }() - writeDebug(fmt.Sprintf("gpsd: starting conversation with %v\n", client.RemoteAddr()), 0) - rd := bufio.NewReader(client) - for { - time.Sleep(100 * time.Millisecond) - // looks like gpsd does not expect \n terminated lines so read what is there from the socket - client.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) - if true { + + } else { + _, err := client.Write([]byte("{\"class\":\"VERSION\",\"release\":\"3.25\",\"rev\":\"3.25\",\"proto_major\":3,\"proto_minor\":15}\n")) + if err != nil { + writeDebug(fmt.Sprintf("Error writing to client: %v\n", err), 1) + } + + go func() { + defer func() { + client.Close() + removeClient(client) + writeDebug(fmt.Sprintf("GPSd Client disconnected: %v\n", client.RemoteAddr()), 0) + }() + writeDebug(fmt.Sprintf("gpsd: starting conversation with %v\n", client.RemoteAddr()), 0) + rd := bufio.NewReader(client) + for { + time.Sleep(100 * time.Millisecond) + // looks like gpsd does not expect \n terminated lines so read what is there from the socket + client.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) buff := make([]byte, 1024) n, err := rd.Read(buff) if isNetConnClosedErr(err) { @@ -197,9 +210,8 @@ func addClient(client net.Conn) { } } - } - }() - + }() + } // register the client, so it gets updates from now on clientMutex.Lock() clients[client] = struct{}{} @@ -224,65 +236,76 @@ func publishTPV() { func readAndBroadcast() { device := s.DeviceType - for { - nmeaSentence, err := s.GPSStream.DequeueOrWait() - if err != nil { - writeDebug(fmt.Sprintf("Error dequeuing GPS sentence: %v\n", err), 0) - continue + if s.NMEAPassthrough { + for { + nmeaSentence, err := s.GPSStream.DequeueOrWait() + if err != nil { + writeDebug(fmt.Sprintf("Error dequeuing GPS sentence: %v\n", err), 0) + continue + } + broadcastToClients(nmeaSentence) } - writeDebug(fmt.Sprintf("gpsd: received NMEA: %s", nmeaSentence), 1) - //broadcastToClients(string(nmeaSentence) + "\n") - - // NMEA-Satz parsen - sentence, err := nmea.Parse(nmeaSentence) - if err != nil { - writeDebug(fmt.Sprintf("gpsd: error parsing NMEA sentence '%s': %v", nmeaSentence, err), 1) - continue - } - - switch s := sentence.(type) { - - // RMC (Mindestdaten: Zeit, Position, Kurs, Geschwindigkeit) - case nmea.RMC: - updateTPVFromRMC(s, device) - - // GGA (Positions- und Höheninformation) - case nmea.GGA: - updateTPVFromGGA(s, device) - - // VTG: Aktualisierung von Kurs und Geschwindigkeit. - case nmea.VTG: - updateTPVFromVTG(s) - - // GSV (Satelliten in Sicht) - case nmea.GSV: - sats := make([]SatelliteInfo, 0, len(s.Info)) - for _, sat := range s.Info { - sats = append(sats, SatelliteInfo{ - PRN: int(sat.SVPRNNumber), - Elevation: int(sat.Elevation), - Azimuth: int(sat.Azimuth), - SNR: int(sat.SNR), - }) + } else { + for { + nmeaSentence, err := s.GPSStream.DequeueOrWait() + if err != nil { + writeDebug(fmt.Sprintf("Error dequeuing GPS sentence: %v\n", err), 0) + continue } - sky := SKY{ - Class: "SKY", - Device: device, - Satellites: sats, - } - if jsonOut, err := json.Marshal(sky); err == nil { - broadcastToClients(string(jsonOut)) - //fmt.Println(string(jsonOut)) - } else { - writeDebug(fmt.Sprintf("gpsd: error serializing SKY object: %v", err), 1) + writeDebug(fmt.Sprintf("gpsd: received NMEA: %s", nmeaSentence), 1) + //broadcastToClients(string(nmeaSentence) + "\n") + + // NMEA-Satz parsen + sentence, err := nmea.Parse(nmeaSentence) + if err != nil { + writeDebug(fmt.Sprintf("gpsd: error parsing NMEA sentence '%s': %v", nmeaSentence, err), 1) + continue } - // GSA (z.B. GPGSA oder auch ohne Talker-ID) - case nmea.GSA: - updateTPVFromGSA(s) + switch s := sentence.(type) { - default: - writeDebug(fmt.Sprintf("unsupported NMEA type: %T", s), 1) + // RMC (Mindestdaten: Zeit, Position, Kurs, Geschwindigkeit) + case nmea.RMC: + updateTPVFromRMC(s, device) + + // GGA (Positions- und Höheninformation) + case nmea.GGA: + updateTPVFromGGA(s, device) + + // VTG: Aktualisierung von Kurs und Geschwindigkeit. + case nmea.VTG: + updateTPVFromVTG(s) + + // GSV (Satelliten in Sicht) + case nmea.GSV: + sats := make([]SatelliteInfo, 0, len(s.Info)) + for _, sat := range s.Info { + sats = append(sats, SatelliteInfo{ + PRN: int(sat.SVPRNNumber), + Elevation: int(sat.Elevation), + Azimuth: int(sat.Azimuth), + SNR: int(sat.SNR), + }) + } + sky := SKY{ + Class: "SKY", + Device: device, + Satellites: sats, + } + if jsonOut, err := json.Marshal(sky); err == nil { + broadcastToClients(string(jsonOut)) + //fmt.Println(string(jsonOut)) + } else { + writeDebug(fmt.Sprintf("gpsd: error serializing SKY object: %v", err), 1) + } + + // GSA (z.B. GPGSA oder auch ohne Talker-ID) + case nmea.GSA: + updateTPVFromGSA(s) + + default: + writeDebug(fmt.Sprintf("unsupported NMEA type: %T", s), 1) + } } } } diff --git a/main.go b/main.go index 8e3fb9c..1a8ce1e 100644 --- a/main.go +++ b/main.go @@ -29,15 +29,16 @@ const ( ) type TCPServer struct { - Protocol chan string - ToPactor *ByteFIFO - FromPactor *ByteFIFO - VARAMode bool - DaemonMode bool - GPSdMode bool - DeviceType string - Status uint8 - Command struct { + Protocol chan string + ToPactor *ByteFIFO + FromPactor *ByteFIFO + VARAMode bool + DaemonMode bool + GPSdMode bool + NMEAPassthrough bool + DeviceType string + Status uint8 + Command struct { Cmd *StringFIFO Response *StringFIFO } @@ -48,16 +49,17 @@ type TCPServer struct { GPSStream *StringFIFO // NMEA steam from PTC to gpsd server, see gpsd.go } -func NewTCPServer(varamode bool, daemonmode bool, gpsdmode bool) *TCPServer { +func NewTCPServer(varamode bool, daemonmode bool, gpsdmode bool, nmeapassthrough bool) *TCPServer { return &TCPServer{ - Protocol: make(chan string, 1024), - ToPactor: NewByteFIFO(1024), - FromPactor: NewByteFIFO(1024), - VARAMode: varamode, - DaemonMode: daemonmode, - GPSdMode: gpsdmode, - DeviceType: "", - Status: 0, + Protocol: make(chan string, 1024), + ToPactor: NewByteFIFO(1024), + FromPactor: NewByteFIFO(1024), + VARAMode: varamode, + DaemonMode: daemonmode, + GPSdMode: gpsdmode, + NMEAPassthrough: nmeapassthrough, + DeviceType: "", + Status: 0, Command: struct { Cmd *StringFIFO Response *StringFIFO @@ -77,6 +79,7 @@ type Userconfig struct { ServerAddress string `yaml:"server_address"` DataAddress string `yaml:"data_address"` GPSdAddress string `yaml:"gpsd_address"` + NMEAPassthrough bool `yaml:"nmeapassthrough"` CmdLineInit string `yaml:"cmdline_init"` StartwithVaraMode bool `yaml:"vara_mode"` } @@ -94,6 +97,7 @@ func configmanage(Config *Userconfig, path string) error { ServerAddress: "127.0.0.1:8300", DataAddress: "127.0.0.1:8301", GPSdAddress: "", + NMEAPassthrough: false, CmdLineInit: "", StartwithVaraMode: false} @@ -158,7 +162,7 @@ func main() { os.Exit(1) } - s = NewTCPServer(Config.StartwithVaraMode, daemonMode, Config.GPSdAddress != "") + s = NewTCPServer(Config.StartwithVaraMode, daemonMode, Config.GPSdAddress != "", Config.NMEAPassthrough) fmt.Println("Initializing PACTOR modem, please wait...") m, err := OpenModem(Config.Device, Config.Baudrate, Config.Mycall, "", Config.CmdLineInit) if err != nil {