Skip to content

Commit be61d25

Browse files
committed
gps: improve implementation for UBX config commands
Signed-off-by: deadprogram <ron@hybridgroup.com>
1 parent 2a42fa7 commit be61d25

5 files changed

Lines changed: 995 additions & 40 deletions

File tree

gps/gps.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ var (
1818
errInvalidGGASentence = errors.New("invalid GGA NMEA sentence")
1919
errInvalidRMCSentence = errors.New("invalid RMC NMEA sentence")
2020
errInvalidGLLSentence = errors.New("invalid GLL NMEA sentence")
21+
errGPSCommandRejected = errors.New("GPS command rejected (NAK)")
22+
errNoACKToGPSCommand = errors.New("no ACK to GPS command")
2123
)
2224

2325
type GPSError struct {

gps/ublox.go

Lines changed: 214 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,227 @@
11
package gps
22

33
import (
4-
"errors"
54
"time"
65
)
76

8-
// flight mode disables the GPS COCOM limits
9-
var flight_mode_cmd = [...]byte{
10-
0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0xFF, 0xFF, 0x06, 0x03, 0x00, 0x00, 0x00,
11-
0x00, 0x10, 0x27, 0x00, 0x00, 0x05, 0x00, 0xFA, 0x00, 0xFA, 0x00, 0x64, 0x00,
12-
0x2C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
13-
0x00, 0x00, 0x00, 0x16, 0xDC}
14-
15-
// Sets CFG-GNSS to disable everything other than GPS GNSS
16-
// solution. Failure to do this means GPS power saving
17-
// doesn't work. Not needed for MAX7, needed for MAX8's
18-
var cfg_gnss_cmd = [...]byte{
19-
0xB5, 0x62, 0x06, 0x3E, 0x2C, 0x00, 0x00, 0x00,
20-
0x20, 0x05, 0x00, 0x08, 0x10, 0x00, 0x01, 0x00,
21-
0x01, 0x01, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00,
22-
0x01, 0x01, 0x03, 0x08, 0x10, 0x00, 0x00, 0x00,
23-
0x01, 0x01, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00,
24-
0x01, 0x01, 0x06, 0x08, 0x0E, 0x00, 0x00, 0x00,
25-
0x01, 0x01, 0xFC, 0x11}
26-
27-
func FlightMode(d Device) (err error) {
28-
err = sendCommand(d, flight_mode_cmd[:])
29-
return err
7+
// FlightModeCmd is a UBX-CFG-NAV5 command to set the GPS into
8+
// flight mode (airborne <1g)
9+
var FlightModeCmd = CfgNav5{
10+
Mask: CfgNav5Dyn | CfgNav5MinEl | CfgNav5PosFixMode,
11+
DynModel: DynModeAirborne1g, // Airborne with <1g acceleration
12+
FixMode: FixModeAuto, // Auto 2D/3D
13+
MinElev_deg: 5, // Minimum elevation 5 degrees
14+
FixedAlt_me2: 0, // Not used
15+
FixedAltVar_m2e4: 0, // Not used
16+
PDop: 100, // 10.0
17+
TDop: 100, // 10.0
18+
PAcc_m: 5000, // 5 meters
19+
TAcc_m: 5000, // 5 meters
20+
StaticHoldThresh_cm_s: 0, // Not used
21+
DgnssTimeout_s: 0, // Not used
22+
CnoThreshNumSVs: 0, // Not used
23+
CnoThresh_dbhz: 0, // Not used
24+
StaticHoldMaxDist_m: 0, // Not used
25+
UtcStandard: 0, // Automatic
26+
Reserved1: [2]byte{},
27+
Reserved2: [5]byte{},
3028
}
3129

32-
func SetCfgGNSS(d Device) (err error) {
33-
err = sendCommand(d, cfg_gnss_cmd[:])
30+
// SetFlightMode sends UBX-CFG-NAV5 command to set GPS into flight mode
31+
func SetFlightMode(d Device) (err error) {
32+
if _, err = FlightModeCmd.Write(d.buffer[:]); err != nil {
33+
return err
34+
}
35+
err = SendCommand(d, d.buffer[:])
3436
return err
3537
}
3638

37-
func sendCommand(d Device, command []byte) (err error) {
38-
d.WriteBytes(command)
39+
var (
40+
// GGA (time, lat/lng, altitude)
41+
MessageRateGGACmd = CfgMsg1{
42+
MsgClass: 0xF0,
43+
MsgID: 0x00,
44+
Rate: 1, // Every position fix
45+
}
46+
// GLL (time, lat/lng)
47+
MessageRateGLLCmd = CfgMsg1{
48+
MsgClass: 0xF0,
49+
MsgID: 0x01,
50+
Rate: 0, // Disabled
51+
}
52+
// GSA (satellite id list)
53+
MessageRateGSACmd = CfgMsg1{
54+
MsgClass: 0xF0,
55+
MsgID: 0x02,
56+
Rate: 1, // Every position fix
57+
}
58+
// GSV (satellite locations)
59+
MessageRateGSVCmd = CfgMsg1{
60+
MsgClass: 0xF0,
61+
MsgID: 0x03,
62+
Rate: 1, // Every position fix
63+
}
64+
// RMC (time, lat/lng, speed, course)
65+
MessageRateRMCCmd = CfgMsg1{
66+
MsgClass: 0xF0,
67+
MsgID: 0x04,
68+
Rate: 1, // Every position fix
69+
}
70+
// VTG (speed, course)
71+
MessageRateVTGCmd = CfgMsg1{
72+
MsgClass: 0xF0,
73+
MsgID: 0x05,
74+
Rate: 0, // Disabled
75+
}
76+
// ZDA (time, timezone)
77+
MessageRateZDACmd = CfgMsg1{
78+
MsgClass: 0xF0,
79+
MsgID: 0x08,
80+
Rate: 0, // Disabled
81+
}
82+
// TXT (text transmission)
83+
MessageRateTXTCmd = CfgMsg1{
84+
MsgClass: 0xF0,
85+
MsgID: 0x41,
86+
Rate: 0, // Disabled
87+
}
88+
)
89+
90+
// SetMessageRatesMinimal configures the GPS to output a minimal set of NMEA sentences
91+
func SetMessageRatesMinimal(d Device) (err error) {
92+
commands := []CfgMsg1{
93+
MessageRateGSACmd,
94+
MessageRateGGACmd,
95+
MessageRateGLLCmd,
96+
MessageRateGSVCmd,
97+
MessageRateRMCCmd,
98+
MessageRateVTGCmd,
99+
MessageRateZDACmd,
100+
MessageRateTXTCmd,
101+
}
102+
103+
for _, cmd := range commands {
104+
if _, err = cmd.Write(d.buffer[:]); err != nil {
105+
return err
106+
}
107+
if err = SendCommand(d, d.buffer[:]); err != nil {
108+
return err
109+
}
110+
}
111+
112+
return nil
113+
}
114+
115+
// SetMessageRatesAllEnabled configures the GPS to output all NMEA sentences
116+
func SetMessageRatesAllEnabled(d Device) (err error) {
117+
commands := []CfgMsg1{
118+
MessageRateGSACmd,
119+
MessageRateGGACmd,
120+
MessageRateGLLCmd,
121+
MessageRateGSVCmd,
122+
MessageRateRMCCmd,
123+
MessageRateVTGCmd,
124+
MessageRateZDACmd,
125+
MessageRateTXTCmd,
126+
}
127+
128+
for _, cmd := range commands {
129+
cmd.Rate = 1 // Enable all messages at 1 Hz
130+
if _, err = cmd.Write(d.buffer[:]); err != nil {
131+
return err
132+
}
133+
if err = SendCommand(d, d.buffer[:]); err != nil {
134+
return err
135+
}
136+
}
137+
138+
return nil
139+
}
140+
141+
// GNSSDisableCmd is a UBX-CFG-GNSS command to disable all GNSS but GPS
142+
// Needed for MAX8's, not needed for MAX7
143+
var GNSSDisableCmd = CfgGnss{
144+
MsgVer: 0x00,
145+
NumTrkChHw: 0x20, // 32 channels
146+
NumTrkChUse: 0x20,
147+
ConfigBlocks: []*CfgGnssConfigBlocksType{
148+
{GnssId: 0, ResTrkCh: 8, MaxTrkCh: 16, Flags: CfgGnssEnable | 0x010000}, // GPS enabled
149+
{GnssId: 1, ResTrkCh: 1, MaxTrkCh: 3, Flags: 0x010000}, // SBAS disabled
150+
{GnssId: 3, ResTrkCh: 8, MaxTrkCh: 16, Flags: 0x010000}, // BeiDou disabled
151+
{GnssId: 5, ResTrkCh: 0, MaxTrkCh: 3, Flags: 0x010000}, // QZSS disabled
152+
{GnssId: 6, ResTrkCh: 8, MaxTrkCh: 14, Flags: 0x010000}, // GLONASS disabled
153+
},
154+
}
155+
156+
// SetGNSSDisable sends UBX-CFG-GNSS command to disable all GNSS but GPS
157+
func SetGNSSDisable(d Device) (err error) {
158+
if _, err = GNSSDisableCmd.Write(d.buffer[:]); err != nil {
159+
return err
160+
}
161+
return SendCommand(d, d.buffer[:])
162+
}
163+
164+
// SendCommand sends a UBX command and waits for ACK/NAK response
165+
func SendCommand(d Device, command []byte) error {
166+
// Calculate and append checksum
167+
checksummed := appendChecksum(command)
168+
d.WriteBytes(checksummed)
169+
39170
start := time.Now()
40-
for time.Now().Sub(start) < 1000 {
41-
if d.readNextByte() == '\n' {
42-
if d.readNextByte() == 0xB5 {
43-
d.readNextByte()
44-
if d.readNextByte() == 0x05 {
45-
if d.readNextByte() == 0x01 {
46-
return
47-
}
48-
}
49-
}
50-
}
51-
}
52-
return errors.New("no ACK to GPS command")
171+
for time.Since(start) < time.Second {
172+
// Look for UBX sync sequence
173+
if d.readNextByte() != ubxSyncChar1 {
174+
continue
175+
}
176+
if d.readNextByte() != ubxSyncChar2 {
177+
continue
178+
}
179+
180+
// Read message class and ID
181+
msgClass := d.readNextByte()
182+
msgID := d.readNextByte()
183+
184+
// Check if it's an ACK class message
185+
if msgClass != ubxClassACK {
186+
continue
187+
}
188+
189+
// Read length (2 bytes, little-endian) - ACK is always 2 bytes payload
190+
lenLo := d.readNextByte()
191+
lenHi := d.readNextByte()
192+
length := uint16(lenLo) | uint16(lenHi)<<8
193+
194+
if length != 2 {
195+
continue
196+
}
197+
198+
// Read ACK payload: class and ID of acknowledged message
199+
ackClass := d.readNextByte()
200+
ackID := d.readNextByte()
201+
202+
// Verify ACK is for our command (command[2] = class, command[3] = ID)
203+
if ackClass != command[2] || ackID != command[3] {
204+
continue
205+
}
206+
207+
if msgID == ubxACK_ACK {
208+
return nil
209+
}
210+
if msgID == ubxACK_NAK {
211+
return errGPSCommandRejected
212+
}
213+
}
214+
215+
return errNoACKToGPSCommand
216+
}
217+
218+
// appendChecksum calculates UBX checksum and appends it to the message
219+
func appendChecksum(msg []byte) []byte {
220+
var ckA, ckB byte
221+
// Checksum covers class, ID, length, and payload (skip sync chars)
222+
for i := 2; i < len(msg); i++ {
223+
ckA += msg[i]
224+
ckB += ckA
225+
}
226+
return append(msg, ckA, ckB)
53227
}

0 commit comments

Comments
 (0)