mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
0e2fb829ef
This contains some improvements and corrections for the gps driver It adds some additional functions for different modes (automobile, bike, etc) and also ignores the return results from any config commands. Basically due to the fact that there is a constant stream of updates coming from NMEA messages, the results from sending any UBX commands are getting lost. Better to just ignore them for now. Signed-off-by: deadprogram <ron@hybridgroup.com>
255 lines
6.8 KiB
Go
255 lines
6.8 KiB
Go
package gps
|
|
|
|
import (
|
|
"time"
|
|
)
|
|
|
|
// FlightModeCmd is a UBX-CFG-NAV5 command
|
|
var nav5Cmd = CfgNav5{
|
|
Mask: CfgNav5Dyn | CfgNav5MinEl | CfgNav5PosFixMode,
|
|
DynModel: DynModeAirborne1g, // Airborne with <1g acceleration
|
|
FixMode: FixModeAuto, // Auto 2D/3D
|
|
MinElev_deg: 5, // Minimum elevation 5 degrees
|
|
FixedAlt_me2: 0, // Not used
|
|
FixedAltVar_m2e4: 0, // Not used
|
|
PDop: 100, // 10.0
|
|
TDop: 100, // 10.0
|
|
PAcc_m: 5000, // 5 meters
|
|
TAcc_m: 5000, // 5 meters
|
|
StaticHoldThresh_cm_s: 0, // Not used
|
|
DgnssTimeout_s: 0, // Not used
|
|
CnoThreshNumSVs: 0, // Not used
|
|
CnoThresh_dbhz: 0, // Not used
|
|
StaticHoldMaxDist_m: 0, // Not used
|
|
UtcStandard: 0, // Automatic
|
|
Reserved1: [2]byte{},
|
|
Reserved2: [5]byte{},
|
|
}
|
|
|
|
// SetFlightMode sends UBX-CFG-NAV5 command to set GPS into flight mode
|
|
func (d *Device) SetFlightMode() (err error) {
|
|
nav5Cmd.DynModel = DynModeAirborne1g
|
|
nav5Cmd.FixMode = FixModeAuto
|
|
nav5Cmd.Put42Bytes(d.buffer[:])
|
|
|
|
return d.SendCommand(d.buffer[:42])
|
|
}
|
|
|
|
// SetPedestrianMode sends UBX-CFG-NAV5 command to set GPS into pedestrian mode
|
|
func (d *Device) SetPedestrianMode() (err error) {
|
|
nav5Cmd.DynModel = DynModePedestrian
|
|
nav5Cmd.FixMode = FixModeAuto
|
|
nav5Cmd.Put42Bytes(d.buffer[:])
|
|
|
|
return d.SendCommand(d.buffer[:42])
|
|
}
|
|
|
|
// SetAutomotiveMode sends UBX-CFG-NAV5 command to set GPS into automotive mode
|
|
func (d *Device) SetAutomotiveMode() (err error) {
|
|
nav5Cmd.DynModel = DynModeAutomotive
|
|
nav5Cmd.FixMode = FixModeAuto
|
|
nav5Cmd.Put42Bytes(d.buffer[:])
|
|
|
|
return d.SendCommand(d.buffer[:42])
|
|
}
|
|
|
|
// SetBikeMode sends UBX-CFG-NAV5 command to set GPS into bike mode
|
|
func (d *Device) SetBikeMode() (err error) {
|
|
nav5Cmd.DynModel = DynModeBike
|
|
nav5Cmd.FixMode = FixModeAuto
|
|
nav5Cmd.Put42Bytes(d.buffer[:])
|
|
|
|
return d.SendCommand(d.buffer[:42])
|
|
}
|
|
|
|
var (
|
|
// GGA (time, lat/lng, altitude)
|
|
messageRateGGACmd = CfgMsg1{
|
|
MsgClass: 0xF0,
|
|
MsgID: 0x00,
|
|
Rate: 1, // Every position fix
|
|
}
|
|
// GLL (time, lat/lng)
|
|
messageRateGLLCmd = CfgMsg1{
|
|
MsgClass: 0xF0,
|
|
MsgID: 0x01,
|
|
Rate: 1, // Every position fix
|
|
}
|
|
// GSA (satellite id list)
|
|
messageRateGSACmd = CfgMsg1{
|
|
MsgClass: 0xF0,
|
|
MsgID: 0x02,
|
|
Rate: 0, // Disabled
|
|
}
|
|
// GSV (satellite locations)
|
|
messageRateGSVCmd = CfgMsg1{
|
|
MsgClass: 0xF0,
|
|
MsgID: 0x03,
|
|
Rate: 0, // Every position fix
|
|
}
|
|
// RMC (time, lat/lng, speed, course)
|
|
messageRateRMCCmd = CfgMsg1{
|
|
MsgClass: 0xF0,
|
|
MsgID: 0x04,
|
|
Rate: 1, // Every position fix
|
|
}
|
|
// VTG (speed, course)
|
|
messageRateVTGCmd = CfgMsg1{
|
|
MsgClass: 0xF0,
|
|
MsgID: 0x05,
|
|
Rate: 0, // Disabled
|
|
}
|
|
// ZDA (time, timezone)
|
|
messageRateZDACmd = CfgMsg1{
|
|
MsgClass: 0xF0,
|
|
MsgID: 0x08,
|
|
Rate: 0, // Disabled
|
|
}
|
|
// TXT (text transmission)
|
|
messageRateTXTCmd = CfgMsg1{
|
|
MsgClass: 0xF0,
|
|
MsgID: 0x41,
|
|
Rate: 0, // Disabled
|
|
}
|
|
)
|
|
|
|
// SetMessageRatesMinimal configures the GPS to output a minimal set of NMEA sentences:
|
|
// GSV, GGA, GLL, and RMC only.
|
|
func SetMessageRatesMinimal(d *Device) (err error) {
|
|
commands := []CfgMsg1{
|
|
messageRateGSACmd,
|
|
messageRateGLLCmd,
|
|
messageRateVTGCmd,
|
|
messageRateZDACmd,
|
|
messageRateTXTCmd,
|
|
}
|
|
for i := range commands {
|
|
commands[i].Rate = 0 // Disable
|
|
}
|
|
return setCfg1s(d, commands)
|
|
}
|
|
|
|
// SetMessageRatesAllEnabled configures the GPS to output all NMEA sentences
|
|
func SetMessageRatesAllEnabled(d *Device) (err error) {
|
|
commands := []CfgMsg1{
|
|
messageRateGSACmd,
|
|
messageRateGGACmd,
|
|
messageRateGLLCmd,
|
|
messageRateGSVCmd,
|
|
messageRateRMCCmd,
|
|
messageRateVTGCmd,
|
|
messageRateZDACmd,
|
|
messageRateTXTCmd,
|
|
}
|
|
for i := range commands {
|
|
commands[i].Rate = 1 // Enable
|
|
}
|
|
return setCfg1s(d, commands)
|
|
}
|
|
|
|
func setCfg1s(d *Device, commands []CfgMsg1) (err error) {
|
|
var buf [9]byte
|
|
for _, cmd := range commands {
|
|
cmd.Put9Bytes(buf[:])
|
|
// TODO handle errors differently here?
|
|
// This implementation just saves the last error and continues.
|
|
// Due to the GPS modules sending updates asynchronously
|
|
// the response is interleaved along with regular ASCII
|
|
// NMEA messages.
|
|
err = d.SendCommand(buf[:])
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// gnssDisableCmd is a UBX-CFG-GNSS command to disable all GNSS but GPS
|
|
// Needed for MAX8's, not needed for MAX7
|
|
var gnssDisableCmd = CfgGnss{
|
|
MsgVer: 0x00,
|
|
NumTrkChHw: 0x20, // 32 channels
|
|
NumTrkChUse: 0x20,
|
|
ConfigBlocks: []CfgGnssConfigBlocksType{
|
|
{GnssId: 0, ResTrkCh: 8, MaxTrkCh: 16, Flags: CfgGnssEnable | 0x010000}, // GPS enabled
|
|
{GnssId: 1, ResTrkCh: 1, MaxTrkCh: 3, Flags: 0x010000}, // SBAS disabled
|
|
{GnssId: 3, ResTrkCh: 8, MaxTrkCh: 16, Flags: 0x010000}, // BeiDou disabled
|
|
{GnssId: 5, ResTrkCh: 0, MaxTrkCh: 3, Flags: 0x010000}, // QZSS disabled
|
|
{GnssId: 6, ResTrkCh: 8, MaxTrkCh: 14, Flags: 0x010000}, // GLONASS disabled
|
|
},
|
|
}
|
|
|
|
// SetGNSSDisable sends UBX-CFG-GNSS command to disable all GNSS but GPS
|
|
func (d *Device) SetGNSSDisable() (err error) {
|
|
err = gnssDisableCmd.Put(d.buffer[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return d.SendCommand(d.buffer[:])
|
|
}
|
|
|
|
// SendCommand sends a UBX command and waits for ACK/NAK response
|
|
func (d *Device) SendCommand(command []byte) error {
|
|
// Calculate and append checksum
|
|
checksummed := appendChecksum(command)
|
|
d.WriteBytes(checksummed)
|
|
|
|
start := time.Now()
|
|
for time.Since(start) < time.Second {
|
|
// Look for UBX sync sequence
|
|
if d.readNextByte() != ubxSyncChar1 {
|
|
continue
|
|
}
|
|
if d.readNextByte() != ubxSyncChar2 {
|
|
continue
|
|
}
|
|
|
|
// Read message class and ID
|
|
msgClass := d.readNextByte()
|
|
msgID := d.readNextByte()
|
|
|
|
// Check if it's an ACK class message
|
|
if msgClass != ubxClassACK {
|
|
continue
|
|
}
|
|
|
|
// Read length (2 bytes, little-endian) - ACK is always 2 bytes payload
|
|
lenLo := d.readNextByte()
|
|
lenHi := d.readNextByte()
|
|
length := uint16(lenLo) | uint16(lenHi)<<8
|
|
|
|
if length != 2 {
|
|
continue
|
|
}
|
|
|
|
// Read ACK payload: class and ID of acknowledged message
|
|
ackClass := d.readNextByte()
|
|
ackID := d.readNextByte()
|
|
|
|
// Verify ACK is for our command (command[2] = class, command[3] = ID)
|
|
if ackClass != command[2] || ackID != command[3] {
|
|
continue
|
|
}
|
|
|
|
if msgID == ubxACK_ACK {
|
|
return nil
|
|
}
|
|
if msgID == ubxACK_NAK {
|
|
return errGPSCommandRejected
|
|
}
|
|
}
|
|
|
|
return errNoACKToGPSCommand
|
|
}
|
|
|
|
// appendChecksum calculates UBX checksum and appends it to the message
|
|
func appendChecksum(msg []byte) []byte {
|
|
var ckA, ckB byte
|
|
// Checksum covers class, ID, length, and payload (skip sync chars)
|
|
for i := 2; i < len(msg); i++ {
|
|
ckA += msg[i]
|
|
ckB += ckA
|
|
}
|
|
return append(msg, ckA, ckB)
|
|
}
|