NMEA pass-through mode
This commit is contained in:
42
README.md
42
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
|
||||
|
171
gpsd.go
171
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
42
main.go
42
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 {
|
||||
|
Reference in New Issue
Block a user