NMEA pass-through mode

This commit is contained in:
Torsten Harenberg
2025-02-21 14:25:36 +01:00
parent 80f55af864
commit 89c19cf9b8
3 changed files with 152 additions and 103 deletions

View File

@@ -42,16 +42,23 @@ cmdline_init: ""
vara_mode: false vara_mode: false
``` ```
| Variable | Meaning | Next time ptb will start using the settings in your config file.
|-------------------|-----------------------------------------------------------------------------------------------------------------------------|
| `device` | Path to the serial device where the modem is connected **or** `tcp://address:port` (useful for Android) | ## Settings
| `baudrate` | baud rate of the modem |
| `mycall` | The callsign to be used on HF | The meaning of the vables in the config file are as follows:
| `server_address` | server socket address for the **commands** |
| `data_address` | server socket address for the **data** | | Variable | Meaning |
| `gpsd_address` | **optional** See the chapter about GPS below | |-------------------|----------------------------------------------------------------------------------------------------------------|
| `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` | | `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 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: 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 ## How to run
As a rule of thumb, you have to start the PACTOR-TCP-bridge before you want to use As a rule of thumb, you have to start the PACTOR-TCP-bridge before you want to use

171
gpsd.go
View File

@@ -130,24 +130,37 @@ func isNetConnClosedErr(err error) bool {
} }
func addClient(client net.Conn) { 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 s.NMEAPassthrough {
if err != nil { go func() {
writeDebug(fmt.Sprintf("Error writing to client: %v\n", err), 1) defer func() {
} client.Close()
removeClient(client)
go func() { writeDebug(fmt.Sprintf("GPSd Client disconnected: %v\n", client.RemoteAddr()), 0)
defer func() { }()
client.Close() rd := bufio.NewScanner(client)
removeClient(client) for rd.Scan() {
writeDebug(fmt.Sprintf("GPSd Client disconnected: %v\n", client.RemoteAddr()), 0) 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) } else {
for { _, err := client.Write([]byte("{\"class\":\"VERSION\",\"release\":\"3.25\",\"rev\":\"3.25\",\"proto_major\":3,\"proto_minor\":15}\n"))
time.Sleep(100 * time.Millisecond) if err != nil {
// looks like gpsd does not expect \n terminated lines so read what is there from the socket writeDebug(fmt.Sprintf("Error writing to client: %v\n", err), 1)
client.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) }
if true {
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) buff := make([]byte, 1024)
n, err := rd.Read(buff) n, err := rd.Read(buff)
if isNetConnClosedErr(err) { if isNetConnClosedErr(err) {
@@ -197,9 +210,8 @@ func addClient(client net.Conn) {
} }
} }
} }()
}() }
// register the client, so it gets updates from now on // register the client, so it gets updates from now on
clientMutex.Lock() clientMutex.Lock()
clients[client] = struct{}{} clients[client] = struct{}{}
@@ -224,65 +236,76 @@ func publishTPV() {
func readAndBroadcast() { func readAndBroadcast() {
device := s.DeviceType device := s.DeviceType
for { if s.NMEAPassthrough {
nmeaSentence, err := s.GPSStream.DequeueOrWait() for {
if err != nil { nmeaSentence, err := s.GPSStream.DequeueOrWait()
writeDebug(fmt.Sprintf("Error dequeuing GPS sentence: %v\n", err), 0) if err != nil {
continue writeDebug(fmt.Sprintf("Error dequeuing GPS sentence: %v\n", err), 0)
continue
}
broadcastToClients(nmeaSentence)
} }
writeDebug(fmt.Sprintf("gpsd: received NMEA: %s", nmeaSentence), 1) } else {
//broadcastToClients(string(nmeaSentence) + "\n") for {
nmeaSentence, err := s.GPSStream.DequeueOrWait()
// NMEA-Satz parsen if err != nil {
sentence, err := nmea.Parse(nmeaSentence) writeDebug(fmt.Sprintf("Error dequeuing GPS sentence: %v\n", err), 0)
if err != nil { continue
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),
})
} }
sky := SKY{ writeDebug(fmt.Sprintf("gpsd: received NMEA: %s", nmeaSentence), 1)
Class: "SKY", //broadcastToClients(string(nmeaSentence) + "\n")
Device: device,
Satellites: sats, // NMEA-Satz parsen
} sentence, err := nmea.Parse(nmeaSentence)
if jsonOut, err := json.Marshal(sky); err == nil { if err != nil {
broadcastToClients(string(jsonOut)) writeDebug(fmt.Sprintf("gpsd: error parsing NMEA sentence '%s': %v", nmeaSentence, err), 1)
//fmt.Println(string(jsonOut)) continue
} else {
writeDebug(fmt.Sprintf("gpsd: error serializing SKY object: %v", err), 1)
} }
// GSA (z.B. GPGSA oder auch ohne Talker-ID) switch s := sentence.(type) {
case nmea.GSA:
updateTPVFromGSA(s)
default: // RMC (Mindestdaten: Zeit, Position, Kurs, Geschwindigkeit)
writeDebug(fmt.Sprintf("unsupported NMEA type: %T", s), 1) 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)
}
} }
} }
} }

42
main.go
View File

@@ -29,15 +29,16 @@ const (
) )
type TCPServer struct { type TCPServer struct {
Protocol chan string Protocol chan string
ToPactor *ByteFIFO ToPactor *ByteFIFO
FromPactor *ByteFIFO FromPactor *ByteFIFO
VARAMode bool VARAMode bool
DaemonMode bool DaemonMode bool
GPSdMode bool GPSdMode bool
DeviceType string NMEAPassthrough bool
Status uint8 DeviceType string
Command struct { Status uint8
Command struct {
Cmd *StringFIFO Cmd *StringFIFO
Response *StringFIFO Response *StringFIFO
} }
@@ -48,16 +49,17 @@ type TCPServer struct {
GPSStream *StringFIFO // NMEA steam from PTC to gpsd server, see gpsd.go 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{ return &TCPServer{
Protocol: make(chan string, 1024), Protocol: make(chan string, 1024),
ToPactor: NewByteFIFO(1024), ToPactor: NewByteFIFO(1024),
FromPactor: NewByteFIFO(1024), FromPactor: NewByteFIFO(1024),
VARAMode: varamode, VARAMode: varamode,
DaemonMode: daemonmode, DaemonMode: daemonmode,
GPSdMode: gpsdmode, GPSdMode: gpsdmode,
DeviceType: "", NMEAPassthrough: nmeapassthrough,
Status: 0, DeviceType: "",
Status: 0,
Command: struct { Command: struct {
Cmd *StringFIFO Cmd *StringFIFO
Response *StringFIFO Response *StringFIFO
@@ -77,6 +79,7 @@ type Userconfig struct {
ServerAddress string `yaml:"server_address"` ServerAddress string `yaml:"server_address"`
DataAddress string `yaml:"data_address"` DataAddress string `yaml:"data_address"`
GPSdAddress string `yaml:"gpsd_address"` GPSdAddress string `yaml:"gpsd_address"`
NMEAPassthrough bool `yaml:"nmeapassthrough"`
CmdLineInit string `yaml:"cmdline_init"` CmdLineInit string `yaml:"cmdline_init"`
StartwithVaraMode bool `yaml:"vara_mode"` StartwithVaraMode bool `yaml:"vara_mode"`
} }
@@ -94,6 +97,7 @@ func configmanage(Config *Userconfig, path string) error {
ServerAddress: "127.0.0.1:8300", ServerAddress: "127.0.0.1:8300",
DataAddress: "127.0.0.1:8301", DataAddress: "127.0.0.1:8301",
GPSdAddress: "", GPSdAddress: "",
NMEAPassthrough: false,
CmdLineInit: "", CmdLineInit: "",
StartwithVaraMode: false} StartwithVaraMode: false}
@@ -158,7 +162,7 @@ func main() {
os.Exit(1) 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...") fmt.Println("Initializing PACTOR modem, please wait...")
m, err := OpenModem(Config.Device, Config.Baudrate, Config.Mycall, "", Config.CmdLineInit) m, err := OpenModem(Config.Device, Config.Baudrate, Config.Mycall, "", Config.CmdLineInit)
if err != nil { if err != nil {