2 * Copyright © 2024 Keith Packard <keithp@keithp.com>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
25 #include "ao_gps_mosaic.h"
26 #include "ao_crc_ccitt.h"
31 #define AO_MOSAIC_DEBUG 0
39 static uint8_t mosaic_dbg_enable = 0;
41 static void mosaic_dbg(int level, char *format, ...) {
44 if (level & mosaic_dbg_enable) {
53 #define mosaic_dbg(fmt, ...)
56 static inline uint8_t mosaic_byte(void) {
57 uint8_t c = (uint8_t) ao_mosaic_getchar();
59 if (' ' <= c && c <= '~')
60 mosaic_dbg(DBG_CHAR, "%c", c);
62 mosaic_dbg(DBG_CHAR, " %02x", c);
67 static inline void mosaic_putc(char c) {
69 if (' ' <= c && c <= '~')
70 mosaic_dbg(DBG_CHAR, "%c", c);
72 mosaic_dbg(DBG_CHAR, " (%02x)", c);
77 static void mosaic_puts(const char *s) {
79 while ((c = *s++) != '\0')
83 extern uint8_t ao_gps_new;
84 extern uint8_t ao_gps_mutex;
85 extern AO_TICK_TYPE ao_gps_tick;
86 extern AO_TICK_TYPE ao_gps_utc_tick;
87 extern struct ao_telemetry_location ao_gps_data;
88 extern struct ao_telemetry_satellite ao_gps_tracking_data;
91 mosaic_wait_idle(void)
95 while(mosaic_byte() != '$')
97 if (mosaic_byte() != 'R')
108 mosaic_command(const char *cmd)
117 ao_mosaic_set_speed(AO_SERIAL_SPEED_115200);
118 /* Send messages always */
119 mosaic_command("smrf, OnlyRef \r");
120 /* Set receiver dynamics mode */
121 mosaic_command("srd, High, Unlimited \r");
122 /* Report position once per second */
123 /* Report time once per second */
124 /* Report sat info once per second */
125 mosaic_command("sso, Stream1, COM3, ReceiverTime+MeasEpoch+PVTGeodetic+DOP, sec1 \r");
128 #if AO_MOSAIC_DEBUG && !defined(AO_GPS_TEST)
129 static void mosaic_option(void)
131 uint8_t r = (uint8_t) ao_cmd_hex();
132 if (ao_cmd_status != ao_cmd_success) {
133 ao_cmd_status = ao_cmd_success;
136 mosaic_dbg_enable = r;
137 printf ("gps debug set to %d\n", mosaic_dbg_enable);
141 #define mosaic_option ao_gps_show
144 const struct ao_cmds ao_mosaic_cmds[] = {
145 { mosaic_option, "g\0Display Mosaic GPS" },
149 static struct sbf sbf;
152 mosaic_read(void *buf, size_t len)
156 *b++ = mosaic_byte();
160 mosaic_read_crc(void *buf, size_t len, uint16_t crc)
165 uint8_t byte = mosaic_byte();
166 crc = ao_crc16_ccitt(crc, byte);
172 static struct ao_telemetry_location new_location;
173 static struct ao_telemetry_satellite new_satellite;
177 clip_value(float value, float low, float high)
181 else if (value > high)
183 return (int32_t) roundf(value);
189 AO_TICK_TYPE packet_start_tick;
190 AO_TICK_TYPE solution_tick = 0;
193 ao_cmd_register(&ao_mosaic_cmds[0]);
198 uint16_t crc_computed;
200 while(mosaic_byte() != '$')
202 if (mosaic_byte() != '@')
204 packet_start_tick = ao_tick_count;
206 mosaic_read(&sbf.header.crc, sizeof(sbf.header.crc));
207 crc_computed = mosaic_read_crc(&sbf.header.h, sizeof(sbf.header.h), 0);
208 if (sbf.header.h.length > sizeof(sbf) + 2) {
209 mosaic_dbg(DBG_PROTO, "too long %d > %ld\n",
210 sbf.header.h.length, sizeof(sbf) + 2);
213 crc_computed = mosaic_read_crc(sbf.data, sbf.header.h.length - 8, crc_computed);
214 if (crc_computed != sbf.header.crc) {
215 mosaic_dbg(DBG_PROTO, "crc error (computed 0x%04x)\n", crc_computed);
219 bool gps_ready = false;
221 switch (SBF_BLOCK_NUMBER(sbf.header.h.id)) {
222 case SBF_RECEIVER_TIME:
223 solution_tick = packet_start_tick;
224 memset(&new_location, 0, sizeof(new_location));
225 memset(&new_satellite, 0, sizeof(new_satellite));
226 new_location.year = (uint8_t) sbf.receiver_time.utcyear;
227 new_location.month = (uint8_t) sbf.receiver_time.utcmonth;
228 new_location.day = (uint8_t) sbf.receiver_time.utcday;
229 new_location.hour = (uint8_t) sbf.receiver_time.utchour;
230 new_location.minute = (uint8_t) sbf.receiver_time.utcmin;
231 new_location.second = (uint8_t) sbf.receiver_time.utcsec;
233 if (sbf.receiver_time.utcyear != -128 &&
234 sbf.receiver_time.utcmonth != -128 &&
235 sbf.receiver_time.utcday != -128 &&
236 sbf.receiver_time.utchour != -128 &&
237 sbf.receiver_time.utcmin != -128 &&
238 sbf.receiver_time.utcsec != -128)
240 new_location.flags |= AO_GPS_DATE_VALID;
243 mosaic_dbg(DBG_PROTO, "ReceiverTime year %d month %d day %d hour %d min %d sec %d\n",
244 sbf.receiver_time.utcyear,
245 sbf.receiver_time.utcmonth,
246 sbf.receiver_time.utcday,
247 sbf.receiver_time.utchour,
248 sbf.receiver_time.utcmin,
249 sbf.receiver_time.utcsec);
252 mosaic_dbg(DBG_PROTO, "MeasEpoch sb1len %d sb2len %d\n",
253 sbf.meas_epoch.sb1length,
254 sbf.meas_epoch.sb2length);
257 uint8_t nsat = 0, nsol = 0;
258 struct sbf_meas_epoch_channel_type1 *type1;
259 struct sbf_meas_epoch_channel_type2 *type2;
261 type1 = (void *) (&sbf.meas_epoch + 1);
262 for (i1 = 0; i1 < sbf.meas_epoch.n1; i1++) {
263 uint8_t signal_type = type1->type & 0x1f;
266 if (signal_type == 1 || signal_type == 2)
267 cn0 = type1->cn0 / 4;
269 cn0 = type1->cn0 / 4 + 10;
270 if (nsat < AO_TELEMETRY_SATELLITE_MAX_SAT) {
271 new_satellite.sats[nsat].svid = type1->svid;
272 new_satellite.sats[nsat].c_n_1 = cn0;
273 new_satellite.channels = nsat + 1;
276 if (type1->locktime != 65535)
278 mosaic_dbg(DBG_PROTO, " Type1 type 0x%x channel %d svid %d cn0 %d locktime %u\n",
279 type1->type, type1->rxchannel, type1->svid, cn0, type1->locktime);
280 type2 = (void *) ((uint8_t *) type1 + sbf.meas_epoch.sb1length);
281 for (i2 = 0; i2 < type1->n2; i2++) {
282 mosaic_dbg(DBG_PROTO, " Type2 type %d cn0 %d\n",
283 type2->type, type2->cn0);
284 type2 = (void *) ((uint8_t *) type2 + sbf.meas_epoch.sb1length);
286 type1 = (void *) type2;
290 new_location.flags |= nsol;
293 case SBF_PVT_GEODETIC:
294 mosaic_dbg(DBG_PROTO, "PVTGeodetic mode 0x%02x error %d lat %f lon %f height %f\n",
297 sbf.geodetic.latitude * 180.0/M_PI,
298 sbf.geodetic.longitude * 180.0/M_PI,
299 sbf.geodetic.height);
301 /* Need to use double here to preserve all of the available precision */
302 new_location.latitude = (int32_t) round(sbf.geodetic.latitude * (1e7 * 180.0/M_PI));
303 new_location.longitude = (int32_t) round(sbf.geodetic.longitude * (1e7 * 180.0f/M_PI));
304 gps_alt_t altitude = (gps_alt_t) round(sbf.geodetic.height);
305 AO_TELEMETRY_LOCATION_SET_ALTITUDE(&new_location, altitude);
306 if (sbf.geodetic.latitude != -2e10 &&
307 sbf.geodetic.longitude != -2e10 &&
308 sbf.geodetic.height != -2e10)
310 new_location.flags |= AO_GPS_VALID;
312 if (sbf.geodetic.vn != -2e10 &&
313 sbf.geodetic.ve != -2e10 &&
314 sbf.geodetic.vu != -2e10)
316 new_location.flags |= AO_GPS_COURSE_VALID;
318 float ground_speed = hypotf((float) sbf.geodetic.vn, (float) sbf.geodetic.ve);
319 float course = atan2f((float) sbf.geodetic.ve, (float) sbf.geodetic.vn);
320 new_location.ground_speed = (uint16_t) clip_value(ground_speed, 0, 65535.0f);
321 new_location.climb_rate = (int16_t) clip_value(sbf.geodetic.vu * 100, -32768.0f, 32767.0f);
322 new_location.course = (uint8_t) clip_value(course * (90.0f/(float)M_PI), 0.0f, 255.0f);
325 mosaic_dbg(DBG_PROTO, "DOP pdop%d tdop %d hdop %d vdop %d\n",
330 new_location.pdop = (uint8_t) (sbf.dop.pdop / 10);
331 new_location.hdop = (uint8_t) (sbf.dop.hdop / 10);
332 new_location.vdop = (uint8_t) (sbf.dop.vdop / 10);
336 mosaic_dbg(DBG_PROTO, "block %d revision %d length %d avail %ld\n",
337 SBF_BLOCK_NUMBER(sbf.header.h.id),
338 SBF_BLOCK_REVISION(sbf.header.h.id),
347 new_location.flags |= AO_GPS_RUNNING;
349 ao_mutex_get(&ao_gps_mutex);
350 ao_gps_data = new_location;
351 ao_gps_tracking_data = new_satellite;
352 ao_gps_tick = solution_tick;
354 ao_mutex_put(&ao_gps_mutex);
355 ao_gps_new = AO_GPS_NEW_DATA | AO_GPS_NEW_TRACKING;
356 ao_wakeup(&ao_gps_new);