Skip to main content

stm_32/peripherals/
ublox.rs

1// ******************************************************************************
2// * File     : platforms/stm_32/src/peripherals/ublox.rs
3// * Date     : June 28, 2026
4// ******************************************************************************
5// *
6// * Copyright (c) 2023, AeroVironment, Inc.
7// * All rights reserved.
8// *
9// * Redistribution and use in source and binary forms, with or without
10// * modification, are permitted provided that the following conditions are met:
11// *
12// * 1.Redistributions of source code must retain the above copyright notice, this
13// * list of conditions and the following disclaimer.
14// *
15// * 2.Redistributions in binary form must reproduce the above copyright notice,
16// * this list of conditions and the following disclaimer in the documentation
17// * and/or other materials provided with the distribution.
18// *
19// * 3.Neither the name of the copyright holder nor the names of its
20// * contributors may be used to endorse or promote products derived from
21// * this software without specific prior written permission.
22// *
23// * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24// * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25// * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26// * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27// * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28// * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29// * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30// * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31// * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32// * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33// *
34// ******************************************************************************
35
36use embassy_stm32::mode::Async;
37use embassy_stm32::usart;
38use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
39use embassy_sync::signal::Signal;
40use embassy_time::Duration;
41use embassy_time::Instant;
42use embassy_time::with_timeout;
43
44use super::pps;
45use veloxity_core::errors;
46use veloxity_core::packets;
47
48const BUFFER_LEN: usize = 512;
49
50fn unix_seconds_from_utc(year: u16, month: u8, day: u8, hour: u8, min: u8, sec: u8) -> i64 {
51    let mut y = year as i32;
52    let m = month as i32;
53    y -= (m <= 2) as i32;
54    let era = if y >= 0 { y } else { y - 399 } / 400;
55    let yoe = y - era * 400;
56    let mp = m + if m > 2 { -3 } else { 9 };
57    let doy = (153 * mp + 2) / 5 + day as i32 - 1;
58    let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
59    let days = era * 146097 + doe - 719468;
60    days as i64 * 86_400 + hour as i64 * 3_600 + min as i64 * 60 + sec as i64
61}
62
63pub static GNSS_SIGNAL: Signal<
64    CriticalSectionRawMutex,
65    Result<packets::GNSSPacket, errors::SensorError>,
66> = Signal::<CriticalSectionRawMutex, Result<packets::GNSSPacket, errors::SensorError>>::new();
67
68fn publish_gnss(result: Result<packets::GNSSPacket, errors::SensorError>) {
69    #[cfg(feature = "runtime-diagnostics")]
70    crate::runtime_diagnostics::record_signal_publish(
71        crate::runtime_diagnostics::SensorKind::Gnss,
72        GNSS_SIGNAL.signaled(),
73        result.is_err(),
74    );
75    GNSS_SIGNAL.signal(result);
76}
77
78#[repr(C, packed)]
79#[derive(Copy, Clone)]
80pub struct PvtPayload {
81    pub i_tow: u32, // ms, GPS time of week
82    pub year: u16,
83    pub month: u8,
84    pub day: u8,
85    pub hour: u8,
86    pub min: u8,
87    pub sec: u8, // UTC
88
89    pub valid: u8,    // validity flags
90    pub t_acc: u32,   // ns, time accuracy estimate
91    pub nano: i32,    // ns, Fraction of second -1e9 to 1e9 (UTC)
92    pub fix_type: u8, // 0 none, 1 dead reckoning, 2 2D, 3 3D, 4 GNS+dead reckoning combined, 5 time only fix
93
94    pub flags: u8,
95    pub flags2: u8,
96    pub num_sv: u8, // satellites used in solution
97    pub lon: i32,
98    pub lat: i32, // degx10^-7
99    pub height: i32,
100    pub h_msl: i32, // mm
101    pub h_acc: u32,
102    pub v_acc: u32, // mm
103    pub vel_n: i32,
104    pub vel_e: i32,
105    pub vel_d: i32,
106    pub g_speed: i32,  // mm/s velocity
107    pub head_mot: i32, // degx10^-5
108    pub s_acc: u32,    // mm/s speed accuracy estimate
109    pub head_acc: u32, // degx10^-5
110    pub p_dop: u16,    // 0.01 (percent)
111
112    flags3: u16,
113    pub reserved1: u8,
114    pub head_veh: i32, // degx10^-5, vehicle heading
115    pub mag_dec: i16,  // degx 10^-2
116    pub mag_acc: u32,  // degx 10^-2
117}
118
119#[repr(C, packed)]
120#[derive(Copy, Clone)]
121pub union PvtUnion {
122    pub packet: PvtPayload,
123    pub payload: [u8; 92],
124}
125
126#[repr(u32)]
127#[derive(Clone, Copy)]
128pub enum Bitrate {
129    Baud9600 = 9600u32,
130    Baud38400 = 38400u32,
131    Baud57600 = 57600u32,
132    Baud115200 = 115200u32,
133    Baud230400 = 230400u32,
134}
135
136pub enum Protocol {
137    M8,
138    M9,
139}
140static UBX_MAX_PAYLOAD_BYTES: usize = 256;
141
142#[repr(C, packed)]
143#[derive(Copy, Clone)]
144pub struct UbxFrame {
145    cl: u8,
146    id: u8,
147    length: usize,
148    a: u8, // checksum
149    b: u8, // checksum
150    payload: [u8; UBX_MAX_PAYLOAD_BYTES],
151}
152
153pub struct UbloxSensor {
154    pub uart: usart::Uart<'static, Async>,
155    pub protocol: Protocol,
156    pub baudrate: Bitrate,
157    pub nav_period_ms: u16,
158}
159
160static PPS_PERIOD_US: u32 = 1000000u32;
161
162// checksum is over class, id, length, payload only
163fn checksum(buffer: &[u8]) -> (u8, u8) {
164    let mut ck_a: u8 = 0;
165    let mut ck_b: u8 = 0;
166
167    for &byte in buffer {
168        ck_a = ck_a.wrapping_add(byte);
169        ck_b = ck_b.wrapping_add(ck_a);
170    }
171
172    (ck_a, ck_b)
173}
174
175fn make_packet(class: u8, id: u8, payload: &[u8], buffer: &mut [u8]) -> bool {
176    let length = payload.len();
177
178    // Check for payload too big
179    if length + 8 > buffer.len() {
180        return false;
181    }
182
183    // header
184    buffer[0] = 0xB5;
185    buffer[1] = 0x62;
186    buffer[2] = class;
187    buffer[3] = id;
188    buffer[4..6].copy_from_slice(&(length as u16).to_le_bytes());
189
190    // payload
191    buffer[6..length + 6].copy_from_slice(payload);
192
193    // checksum
194    let (ck_a, ck_b) = checksum(&buffer[2..length + 6]);
195    buffer[length + 6] = ck_a;
196    buffer[length + 7] = ck_b;
197
198    true
199}
200
201impl UbloxSensor {
202    async fn tx(&mut self, class: u8, id: u8, payload: &[u8]) -> bool {
203        let mut buffer = [0u8; BUFFER_LEN]; // largest ubx packet length we will support
204        // Make packet
205        if make_packet(class, id, payload, &mut buffer) {
206            // Make the expected ack packet
207            let mut ack: [u8; 10] = [0u8; 10];
208            let _result = make_packet(0x05, 0x01, &[class, id], &mut ack); // ack packet is class=0x05 id=0x01
209
210            // send packet
211            let result = self.uart.write(&buffer[0..payload.len() + 8]).await;
212            if let Ok(_size) = result {
213                // check it it was successful
214                let result = self.look_for_ack(&ack).await;
215                return result;
216            }
217        }
218        false
219    }
220
221    async fn cfg_prt(&mut self, _baud: u32) -> bool {
222        match self.protocol {
223            Protocol::M8 => {
224                let mut payload = [0u8; 20]; // #define CFG_PRT_LENGTH 20
225                payload[0] = 0x01; // Port 1 is the UART
226                //payload[1] = 0x00; // Reserved
227                //payload[2] = 0x00; // txReady
228                //payload[3] = 0x00; // txReady
229                payload[4] = 0xC0; // mode 1100 0000 (8-bit character length)
230                payload[5] = 0x08; // mode 0000 1000 (No parity, 1 stop bit)
231                //payload[6] = 0x00; // mode
232                //payload[7] = 0x00; // mode
233                payload[8..12].copy_from_slice(&(self.baudrate as u32).to_le_bytes()); // meas rate
234                payload[12] = 0x01; // inProtoMask (ubx)
235                //payload[13] = 0x00; // inProtoMask
236                payload[14] = 0x01; // outProtoMask (ubx)
237                //payload[15] = 0x00; // outProtoMask
238                //payload[16] = 0x00; // flags
239                //payload[17] = 0x00; // flags
240                //payload[18] = 0x00; // reserved2
241                //payload[19] = 0x00; // reserved2
242
243                self.tx(0x06, 0x00, &payload).await
244            }
245            Protocol::M9 => {
246                let mut payload = [0u8; 34];
247                //payload[0] = 0x00; // Message Version (1 bytes)
248                payload[1] = 0x01; // Write to RAM bit 1 is ram, 2 is bbr layer, 3 is flash (1 bytes)
249                //payload[2] = 0x00; // transaction/action (1 bytes)
250                //payload[3] = 0x00; // reserved0 (1 bytes)
251
252                // Key-Value pairs
253                // baud rate (8 bytes)
254                payload[4..8].copy_from_slice(&0x40520001u32.to_le_bytes());
255                payload[8..12].copy_from_slice(&(self.baudrate as u32).to_le_bytes());
256
257                // output rate in milliseconds (6 bytes)
258                payload[12..16].copy_from_slice(&0x30210001u32.to_le_bytes());
259                payload[16..18].copy_from_slice(&self.nav_period_ms.to_le_bytes());
260
261                // 1 data output per nav measurement (6 bytes)
262                payload[18..22].copy_from_slice(&0x30210002u32.to_le_bytes());
263                payload[22..24].copy_from_slice(&1u16.to_le_bytes());
264
265                // CFG-NAVSPG-DYNMODEL 8 = 4G Airborne (5 bytes)
266                payload[24..28].copy_from_slice(&0x20110021u32.to_le_bytes());
267                payload[28] = 8;
268
269                // CFG-NAVSPG-FIXMODE 3 = Auto 2/3D (5 bytes)
270                payload[29..33].copy_from_slice(&0x20110011u32.to_le_bytes());
271                payload[33] = 3u8;
272
273                self.tx(0x06, 0x8A, &payload).await
274            }
275        }
276    }
277
278    async fn cfg_rate(&mut self) -> bool {
279        let mut payload = [0u8; 6]; // #define CFG_RATE_LENGTH 6
280        payload[0..2].copy_from_slice(&self.nav_period_ms.to_le_bytes()); // meas rate
281        payload[2..4].copy_from_slice(&0x0001u16.to_le_bytes()); // nav rate = meas rate
282        payload[4..6].copy_from_slice(&0x0000u16.to_le_bytes()); // UTC time reference
283
284        self.tx(0x06, 0x08, &payload).await
285    }
286
287    async fn cfg_tp5(&mut self) -> bool {
288        let mut payload = [0u8; 32]; // #define SFG_TP5_LENGTH 32
289        payload[0] = 0; // Timepulse pin 0
290        payload[1] = 1; // Version 1
291        //payload[2] = 0; // reserved
292        //payload[3] = 0; // reserved
293        //payload[4..6].copy_from_slice(& 0u16.to_le_bytes()); // antenna delay
294        //payload[6..8].copy_from_slice(& 0u16.to_le_bytes()); // rf group delay
295        let pps_period_us = PPS_PERIOD_US; //(pps_period_ms as u32)*1000u32;
296        payload[8..12].copy_from_slice(&pps_period_us.to_le_bytes()); // pulse period
297        payload[12..16].copy_from_slice(&pps_period_us.to_le_bytes()); // pulse period if locked other set
298        let pulse_len_us = 1000u32;
299        payload[16..20].copy_from_slice(&pulse_len_us.to_le_bytes()); // pulse high time
300        payload[20..24].copy_from_slice(&pulse_len_us.to_le_bytes()); // pulse high time
301        // payload[24..28].copy_from_slice(& 0u32.to_le_bytes()); // pulse high time
302        payload[28..32].copy_from_slice(&0x01F7u32.to_le_bytes()); // pulse high time
303
304        self.tx(0x06, 0x31, &payload).await
305    }
306
307    async fn cfg_nav5(&mut self) -> bool {
308        let mut payload = [0u8; 36]; // #define CFG_NAV5_LENGTH 36
309        payload[0] = 5; // Parameters bitmask
310        payload[1] = 8; // Airbourne navigatin < 4G's
311        payload[2] = 3; // Auto 2d/3d fix mode
312        self.tx(0x06, 0x24, &payload).await
313    }
314
315    async fn cfg_msg(&mut self, class: u8, id: u8, decimation_rate: u8) -> bool {
316        let payload = [class, id, decimation_rate];
317        self.tx(0x06, 0x01, &payload).await
318    }
319
320    async fn look_for_ack(&mut self, ack: &[u8]) -> bool {
321        let mut buffer = [0u8; 256];
322
323        // Expected ack packet [0xB5u8, 0x62u8, 0x05u8, 0x01u8, 0x02u8, 0x00u8, 0x06u8, 0x00u8, 0x0Eu8, 0x37u8,]
324        // Read data block with 2 second timeout
325        match with_timeout(
326            Duration::from_secs(2),
327            self.uart.read_until_idle(&mut buffer),
328        )
329        .await
330        {
331            Ok(Ok(_size)) => {
332                for subarray in buffer.windows(ack.len()) {
333                    if ack == subarray {
334                        return true;
335                    }
336                }
337            }
338            Ok(Err(_read_err)) => {
339                // underlying UART/read error from self.uart.read(...)
340            }
341            Err(_timeout_err) => {
342                // with_timeout timed out
343            }
344        }
345        false
346    }
347
348    async fn sync_baudrate(&mut self) -> bool {
349        // Determine baud rate
350        let bauds = [
351            Bitrate::Baud9600 as u32,
352            Bitrate::Baud38400 as u32,
353            Bitrate::Baud57600 as u32,
354            Bitrate::Baud115200 as u32,
355            Bitrate::Baud230400 as u32,
356        ]; //, 460800u32, 921600u32];
357
358        for _retries in 0..30 {
359            for baud in bauds {
360                // try baud rate
361                // set stm32 baud rate
362                let _result: Result<(), usart::ConfigError> = self.uart.set_baudrate(baud);
363
364                // set ublox the desired baud rate
365                let result = self.cfg_prt(self.baudrate as u32).await;
366
367                if result {
368                    let _result: Result<(), usart::ConfigError> =
369                        self.uart.set_baudrate(self.baudrate as u32);
370                    return true;
371                }
372            }
373        }
374        false
375    }
376
377    pub async fn run(&mut self) {
378        let _synced = self.sync_baudrate().await;
379
380        // Disable these messages
381        self.cfg_msg(0x0A, 0x09, 0).await; // MON-HW
382        self.cfg_msg(0x0A, 0x0B, 0).await; // MON-HW2
383        self.cfg_msg(0x01, 0x04, 0).await; // NAV-DOP
384        self.cfg_msg(0x01, 0x03, 0).await; // NAV-STATUS
385        self.cfg_msg(0x01, 0x35, 0).await; // NAV-SAT
386        self.cfg_msg(0x01, 0x20, 0).await; // NAV-TIMEGPS
387        self.cfg_msg(0x01, 0x01, 0).await; // NAV-POSECEF (length 20)
388        self.cfg_msg(0x01, 0x11, 0).await; // NAV-VELECEF (length 20)
389
390        // These are needed if you want ECEF, but disable for now
391        self.cfg_msg(0x01, 0x20, 0).await; // NAV-TIMEGPS (length 16)
392        self.cfg_msg(0x01, 0x01, 0).await; // NAV-POSECEF (length 20)
393        self.cfg_msg(0x01, 0x11, 0).await; // NAV-VELECEF (length 20)
394
395        // Enable this messages
396        self.cfg_msg(0x01, 0x07, 1).await; // NAV-PVT (length 92)
397
398        // Set GPS Configuration (already done in cfg_prt() for UBX_M9)
399        if let Protocol::M8 = self.protocol {
400            self.cfg_rate().await;
401            self.cfg_tp5().await;
402            self.cfg_nav5().await;
403        }
404        pub struct UbxFrame {
405            cl: u8,
406            id: u8,
407            length: usize,
408            a: u8, // checksum
409            b: u8, // checksum
410            payload: [u8; UBX_MAX_PAYLOAD_BYTES],
411        }
412
413        let mut p: UbxFrame = UbxFrame {
414            cl: 0u8,
415            id: 0u8,
416            length: 0usize,
417            a: 0u8,
418            b: 0u8,
419            payload: [0u8; UBX_MAX_PAYLOAD_BYTES],
420        };
421        let mut n = 0usize;
422        let mut pps_timestamp = 0;
423        loop {
424            // get most recent pps timestamp
425            match pps::PPS_SIGNAL.try_take() {
426                Some(packet) => {
427                    pps_timestamp = packet.header.timestamp;
428                }
429                None => {}
430            }
431
432            let mut buffer = [0u8; BUFFER_LEN];
433
434            let result = self.uart.read_until_idle(&mut buffer).await;
435            if let Ok(size) = result {
436                // This could be a function, but might as well just chug through this here
437                for &c in buffer[0..size].iter() {
438                    // special case where we get 0xB5 randomly duplicated at the start (DMA wierdness).
439
440                    if (c == 0xB5) && (n == 1) {
441                        n = 0;
442                    }
443
444                    if n == 0
445                    // header byte 1 "mu" character
446                    {
447                        if c == 0xB5 {
448                            n += 1;
449                        } else {
450                            n = 0;
451                        }
452                    } else if n == 1
453                    // header byte 2
454                    {
455                        if c == 0x62 {
456                            n += 1;
457                        } else if c == 0xB5 {
458                            n = 1;
459                        }
460                        // repeated 'mu'
461                        else {
462                            n = 0;
463                        }
464                    } else if n == 2
465                    // Class
466                    {
467                        p.a = 0;
468                        p.b = 0; // Reset the checksum calculation
469                        p.cl = c;
470                        n += 1;
471                        p.a = p.a.wrapping_add(c);
472                        p.b = p.b.wrapping_add(p.a);
473                    } else if n == 3
474                    // ID, allow all
475                    {
476                        p.id = c;
477                        n += 1;
478                        p.a = p.a.wrapping_add(c);
479                        p.b = p.b.wrapping_add(p.a);
480                    } else if n == 4
481                    // length LSB
482                    {
483                        p.length = c as usize;
484                        n += 1;
485                        p.a = p.a.wrapping_add(c);
486                        p.b = p.b.wrapping_add(p.a);
487                    } else if n == 5
488                    // length MSB
489                    {
490                        p.length |= (c as usize) << 8;
491                        if p.length > UBX_MAX_PAYLOAD_BYTES {
492                            n = 0;
493                        } else {
494                            n += 1;
495                            p.a = p.a.wrapping_add(c);
496                            p.b = p.b.wrapping_add(p.a);
497                        }
498                    } else if n < p.length + 6
499                    // Packet Payload bytes and first byte of checksum.
500                    {
501                        p.payload[n - 6] = c;
502                        n += 1;
503                        p.a = p.a.wrapping_add(c);
504                        p.b = p.b.wrapping_add(p.a);
505                    } else if n == p.length + 6
506                    // Checksum A
507                    {
508                        if p.a != c {
509                            n = 0;
510                        } else {
511                            n += 1;
512                        }
513                    } else {
514                        n = 0;
515                        if p.b == c {
516                            // we found a valid packet
517                            if (p.cl == 0x01) || (p.id == 0x07)
518                            // pvt packet
519                            {
520                                let end_of_packet_timestamp = Instant::now();
521
522                                // map the payload into the pvt union
523                                let mut payload = [0u8; 92];
524                                payload.copy_from_slice(&p.payload[0..92]);
525                                let pvt = PvtUnion { payload };
526
527                                // build up the device specific status register.
528                                // (from Bitfield valid) [0] = validDate, [1] = validTime, [2] = fullyResolved, [3] validMag, none ignored
529                                // (from Bitfield flags) [4] = gnssFixOK , [5] = diffSoln, [6] = psmState, [7] = headVehValid, [8] = carrSoln, none ignored
530                                // (from Bitfield flags2) [9] = confirmedAvai, [10] = confirmedDate, [11] = confirmedTime, none ignored
531                                // (from Bitfield flags3) [12] = invalidLlh, [13] = lastCorrectionAge
532
533                                let mut status = 0u16;
534                                let valid = unsafe { pvt.packet }.valid;
535                                let flags = unsafe { pvt.packet }.flags;
536                                let flags2 = unsafe { pvt.packet }.flags2;
537                                let flags3 = unsafe { pvt.packet }.flags3;
538                                let utc_nanos = unsafe { pvt.packet }.nano;
539
540                                if (valid & 0x01) != 0 {
541                                    status |= 0x0001
542                                }; // validDate
543                                if (valid & 0x02) != 0 {
544                                    status |= 0x0002
545                                }; // validTime
546                                if (valid & 0x04) != 0 {
547                                    status |= 0x0004
548                                }; // fullyResolved
549                                if (valid & 0x08) != 0 {
550                                    status |= 0x0008
551                                }; // validMag
552
553                                if (flags & 0x01) != 0 {
554                                    status |= 0x0010
555                                }; // gnssFixOK
556                                if (flags & 0x02) != 0 {
557                                    status |= 0x0020
558                                }; // diffSoln
559                                if (flags & 0x10) != 0 {
560                                    status |= 0x0020
561                                }; // pmsState
562                                if (flags & 0x20) != 0 {
563                                    status |= 0x0040
564                                }; // headVehValid
565                                if (flags & 0x80) != 0 {
566                                    status |= 0x0080
567                                }; // carrSoln
568
569                                if (flags2 & 0x20) != 0 {
570                                    status |= 0x0100
571                                }; // confirmedAvai
572                                if (flags2 & 0x40) != 0 {
573                                    status |= 0x0200
574                                }; // confirmedDate
575                                if (flags2 & 0x80) != 0 {
576                                    status |= 0x0400
577                                }; // confirmedTime
578
579                                if (flags3 & 0x0001) != 0 {
580                                    status |= 0x0800
581                                }; // invalidLlh
582                                if (flags3 & 0x0010) != 0 {
583                                    status |= 0x1000
584                                }; // lastCorrectionAge
585
586                                // PPS may arrive while the UART read is pending;
587                                // consume it again here so the C-compatible timing
588                                // calculation uses the latest edge.
589                                if let Some(packet) = pps::PPS_SIGNAL.try_take() {
590                                    pps_timestamp = packet.header.timestamp;
591                                }
592                                let completion_timestamp_us = end_of_packet_timestamp.as_micros();
593                                let time_and_fix_valid =
594                                    (valid & 0x07) == 0x07 && (flags & 0x01) == 0x01;
595                                let timestamp_us = packets::rosflight_c_gnss_timestamp(
596                                    pps_timestamp as u64,
597                                    completion_timestamp_us,
598                                    utc_nanos,
599                                    time_and_fix_valid,
600                                );
601
602                                let header = packets::RosflightPacketHeader {
603                                    timestamp: timestamp_us,
604                                    status,
605                                };
606
607                                let _fix_type =
608                                    packets::GNSSFixType::from_u8(unsafe { pvt.packet }.fix_type);
609
610                                let _pi = 3.141592654;
611                                let mut unix_seconds = unix_seconds_from_utc(
612                                    unsafe { pvt.packet }.year,
613                                    unsafe { pvt.packet }.month,
614                                    unsafe { pvt.packet }.day,
615                                    unsafe { pvt.packet }.hour,
616                                    unsafe { pvt.packet }.min,
617                                    unsafe { pvt.packet }.sec,
618                                );
619                                let mut unix_nanos = utc_nanos;
620                                if unix_nanos < 0 {
621                                    unix_seconds -= 1;
622                                    unix_nanos += 1_000_000_000;
623                                }
624
625                                let pvt_packet = packets::GNSSPacket {
626                                    header: header,
627                                    unix_seconds,
628                                    unix_nanos,
629                                    // UBX NAV-PVT and ROSFLIGHT_GNSS both use decimal degrees.
630                                    lat: (unsafe { pvt.packet }.lat as f64) * 1.0e-7,
631                                    lon: (unsafe { pvt.packet }.lon as f64) * 1.0e-7,
632                                    // Match ROSflight C: publish NAV-PVT hMSL, not ellipsoid height.
633                                    height_msl: (unsafe { pvt.packet }.h_msl as f32) / 1000.0,
634                                    vel_n: (unsafe { pvt.packet }.vel_n as f32) / 1000.0,
635                                    vel_e: (unsafe { pvt.packet }.vel_e as f32) / 1000.0,
636                                    vel_d: (unsafe { pvt.packet }.vel_d as f32) / 1000.0,
637                                    h_acc: (unsafe { pvt.packet }.h_acc as f32) / 1000.0,
638                                    v_acc: (unsafe { pvt.packet }.v_acc as f32) / 1000.0,
639                                    s_acc: (unsafe { pvt.packet }.s_acc as f32) / 1000.0,
640                                    month: unsafe { pvt.packet }.month,
641                                    day: unsafe { pvt.packet }.day,
642                                    year: unsafe { pvt.packet }.year,
643                                    hour: unsafe { pvt.packet }.hour,
644                                    min: unsafe { pvt.packet }.min,
645                                    sec: unsafe { pvt.packet }.sec,
646                                    nano: unsafe { pvt.packet }.nano,
647                                    fix_type: packets::GNSSFixType::from_u8(
648                                        unsafe { pvt.packet }.fix_type,
649                                    ),
650                                    num_sats: unsafe { pvt.packet }.num_sv,
651                                    mag_dec: (unsafe { pvt.packet }.mag_dec as f32)
652                                        * 1.7453292519943296e-4,
653                                    time_correction: 0,
654                                };
655                                publish_gnss(Ok(pvt_packet));
656                            }
657                        }
658                    }
659                }
660            }
661        }
662    }
663}
664
665#[embassy_executor::task]
666pub async fn task(mut ublox: UbloxSensor) {
667    ublox.run().await;
668}