From 17d07b9faeded770028f168601c3ad4efe6066f0 Mon Sep 17 00:00:00 2001 From: Juan Pablo Pino Bravo Date: Fri, 28 Aug 2026 15:01:39 +0200 Subject: [PATCH 1/3] feat: add GnssStatusTel telemetry for the Blueye GNSS receiver Adds a GnssStatus message carrying fix quality, satellite counts, HDOP, per-axis standard deviations, COG/SOG, per-satellite SNR, and driver health (connection state, baud rate, reconnect and error counters) so GNSS behaviour can be diagnosed from dive logs. Co-Authored-By: Claude Fable 5 --- protobuf_definitions/message_formats.proto | 36 ++++++++++++++++++++++ protobuf_definitions/telemetry.proto | 5 +++ 2 files changed, 41 insertions(+) diff --git a/protobuf_definitions/message_formats.proto b/protobuf_definitions/message_formats.proto index 32b3ea61..7efda456 100644 --- a/protobuf_definitions/message_formats.proto +++ b/protobuf_definitions/message_formats.proto @@ -1261,6 +1261,42 @@ message NavigationSensorStatus { LatLongPosition global_position = 8; // Global position from sensor. } +// GNSS constellation a satellite belongs to. +enum GnssConstellation { + GNSS_CONSTELLATION_UNSPECIFIED = 0; // Unspecified. + GNSS_CONSTELLATION_GPS = 1; // GPS. + GNSS_CONSTELLATION_GLONASS = 2; // GLONASS. + GNSS_CONSTELLATION_GALILEO = 3; // Galileo. + GNSS_CONSTELLATION_BEIDOU = 4; // BeiDou. +} + +// Satellite tracked by the Blueye GNSS receiver. +message GnssSatellite { + GnssConstellation constellation = 1; // Constellation the satellite belongs to. + uint32 satellite_id = 2; // Satellite id within the constellation. + uint32 snr = 3; // Carrier to noise ratio (dB-Hz). +} + +// Status of the onboard Blueye GNSS receiver and its driver. +message GnssStatus { + bool receiver_connected = 1; // True when the receiver is connected and producing NMEA sentences. + uint32 fix_quality = 2; // NMEA GGA fix quality indicator (0 = no fix). + bool is_valid = 3; // True when the receiver reports a usable position fix. + uint32 satellites_used = 4; // Number of satellites used in the position fix. + uint32 satellites_in_view = 5; // Number of satellites currently tracked. + float hdop = 6; // Horizontal dilution of precision. + LatLongPosition global_position = 7; // Reported global position. + float stddev_latitude = 8; // Standard deviation of the latitude (m). + float stddev_longitude = 9; // Standard deviation of the longitude (m). + float course_over_ground = 10; // Course over ground (°). + float speed_over_ground = 11; // Speed over ground (m/s). + uint32 baud_rate = 12; // Serial baud rate in use (bit/s). + uint32 reconnect_count = 13; // Times the driver has reconnected to the receiver since boot. + uint32 receive_error_count = 14; // Receive errors seen by the driver since boot. + float time_since_last_sentence = 15; // Time since the last NMEA sentence was received (s). + repeated GnssSatellite satellites = 16; // Satellites currently tracked with signal strength. +} + // Status for guest port devices that can be attached and detached. enum GuestPortDetachStatus { GUEST_PORT_DETACH_STATUS_UNSPECIFIED = 0; // Unspecified (Default for non-detachable devices). diff --git a/protobuf_definitions/telemetry.proto b/protobuf_definitions/telemetry.proto index db8dc0db..ccd5496c 100644 --- a/protobuf_definitions/telemetry.proto +++ b/protobuf_definitions/telemetry.proto @@ -97,6 +97,11 @@ message PilotGPSPositionTel { LatLongPosition position = 1; // The GPS position of the pilot. } +// Status of the onboard Blueye GNSS receiver and its driver. +message GnssStatusTel { + GnssStatus gnss_status = 1; // Status of the Blueye GNSS receiver. +} + // Record state from the drone. message RecordStateTel { RecordState record_state = 1; // Record state. From 1c25d36e0f4e5199e250eec793139f87db5cdfbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Folles=C3=B8?= Date: Sat, 29 Aug 2026 12:30:08 +0200 Subject: [PATCH 2/3] feat: carry satellite geometry and validity bits in GnssStatus Add the fields the driver already knows but the message dropped: - GnssSatellite gains elevation and azimuth, so a client can plot the sky the receiver sees rather than a bare list of signal strengths. Both use -1 for "not reported", which is unambiguous because neither value is ever negative. - satellites_in_view now carries the receiver's own in-view count. Field 5 kept its number but is renamed satellites_tracked, which is what it actually holds: the length of the satellites list, pruned to those that reported a signal within the last 30 s. - is_accuracy_valid distinguishes "no GST sentence yet" from a real zero-metre deviation, which the two stddev fields cannot express on their own. - is_course_valid mirrors the bit the driver derives but the status message dropped: NMEA RMC leaves the course empty when the receiver is stationary, so course_over_ground goes stale without it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01SVWisosuY9Cep5iN5huuTc --- protobuf_definitions/message_formats.proto | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/protobuf_definitions/message_formats.proto b/protobuf_definitions/message_formats.proto index 7efda456..f5d3513d 100644 --- a/protobuf_definitions/message_formats.proto +++ b/protobuf_definitions/message_formats.proto @@ -1275,6 +1275,8 @@ message GnssSatellite { GnssConstellation constellation = 1; // Constellation the satellite belongs to. uint32 satellite_id = 2; // Satellite id within the constellation. uint32 snr = 3; // Carrier to noise ratio (dB-Hz). + int32 elevation = 4; // Elevation above the horizon (°, 0..90), or -1 when the receiver did not report it. + int32 azimuth = 5; // Azimuth clockwise from true north (°, 0..359), or -1 when the receiver did not report it. } // Status of the onboard Blueye GNSS receiver and its driver. @@ -1283,12 +1285,15 @@ message GnssStatus { uint32 fix_quality = 2; // NMEA GGA fix quality indicator (0 = no fix). bool is_valid = 3; // True when the receiver reports a usable position fix. uint32 satellites_used = 4; // Number of satellites used in the position fix. - uint32 satellites_in_view = 5; // Number of satellites currently tracked. + uint32 satellites_tracked = 5; // Satellites in the satellites list: those with a signal, seen within the last 30 s. + uint32 satellites_in_view = 17; // Satellites the receiver reports in view, summed over the constellations. float hdop = 6; // Horizontal dilution of precision. LatLongPosition global_position = 7; // Reported global position. - float stddev_latitude = 8; // Standard deviation of the latitude (m). - float stddev_longitude = 9; // Standard deviation of the longitude (m). - float course_over_ground = 10; // Course over ground (°). + float stddev_latitude = 8; // Standard deviation of the latitude (m). Meaningless unless is_accuracy_valid. + float stddev_longitude = 9; // Standard deviation of the longitude (m). Meaningless unless is_accuracy_valid. + bool is_accuracy_valid = 18; // True once the receiver has reported a GST sentence to derive the deviations from. + float course_over_ground = 10; // Course over ground (°). Meaningless unless is_course_valid. + bool is_course_valid = 19; // True when the fix is valid and the receiver actually reported a course. float speed_over_ground = 11; // Speed over ground (m/s). uint32 baud_rate = 12; // Serial baud rate in use (bit/s). uint32 reconnect_count = 13; // Times the driver has reconnected to the receiver since boot. From c562a3df89ebaecad12716b48a0acab4ba526388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Folles=C3=B8?= Date: Sat, 29 Aug 2026 12:49:52 +0200 Subject: [PATCH 3/3] feat: carry fix membership, dilution and the error ellipse in GnssStatus Everything here is parsed by the driver and was being discarded before the wire, and all of it is what support needs to read a bad fix out of a dive log rather than reproducing it on a bench. - GnssSatellite.used_in_fix, gated by is_used_in_fix_valid. This is the distinction between a satellite the receiver is tracking and one actually contributing, which is what a sky plot draws as filled against hollow. The gate is needed because a GSA sentence without a system id cannot be attributed to a constellation, and a client must not read that as "nothing is in the fix". - fix_mode, pdop and vdop, from the same GSA sentence. HDOP alone says nothing about vertical geometry, and 2D against 3D explains an altitude that looks wrong. - The horizontal error ellipse (stddev_semi_major, stddev_semi_minor, error_ellipse_orientation), stddev_altitude and range_rms. Two axis-aligned deviations cannot describe a tilted uncertainty, which is the normal shape when half the sky is behind a quay wall. - altitude, which is the cheapest sanity check on a surface receiver: a multipath fix often keeps a respectable HDOP but puts the antenna hundreds of metres off vertically. - differential_age and differential_station, without which a fix quality of 2, 4 or 5 cannot be verified after the fact. Both use -1 for absent, which is unambiguous as neither is ever negative. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01SVWisosuY9Cep5iN5huuTc --- protobuf_definitions/message_formats.proto | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/protobuf_definitions/message_formats.proto b/protobuf_definitions/message_formats.proto index f5d3513d..bb61fb3f 100644 --- a/protobuf_definitions/message_formats.proto +++ b/protobuf_definitions/message_formats.proto @@ -1261,6 +1261,14 @@ message NavigationSensorStatus { LatLongPosition global_position = 8; // Global position from sensor. } +// Position fix mode reported by the NMEA GSA sentence. +enum GnssFixMode { + GNSS_FIX_MODE_UNSPECIFIED = 0; // No GSA sentence has been received. + GNSS_FIX_MODE_NO_FIX = 1; // The receiver has no position fix. + GNSS_FIX_MODE_2D = 2; // Horizontal fix only, no usable altitude. + GNSS_FIX_MODE_3D = 3; // Full three dimensional fix. +} + // GNSS constellation a satellite belongs to. enum GnssConstellation { GNSS_CONSTELLATION_UNSPECIFIED = 0; // Unspecified. @@ -1277,6 +1285,7 @@ message GnssSatellite { uint32 snr = 3; // Carrier to noise ratio (dB-Hz). int32 elevation = 4; // Elevation above the horizon (°, 0..90), or -1 when the receiver did not report it. int32 azimuth = 5; // Azimuth clockwise from true north (°, 0..359), or -1 when the receiver did not report it. + bool used_in_fix = 6; // Whether GSA lists the satellite as used in the fix. Only meaningful if is_used_in_fix_valid. } // Status of the onboard Blueye GNSS receiver and its driver. @@ -1287,14 +1296,26 @@ message GnssStatus { uint32 satellites_used = 4; // Number of satellites used in the position fix. uint32 satellites_tracked = 5; // Satellites in the satellites list: those with a signal, seen within the last 30 s. uint32 satellites_in_view = 17; // Satellites the receiver reports in view, summed over the constellations. + bool is_used_in_fix_valid = 20; // True when GSA could be attributed to a constellation, making used_in_fix usable. + GnssFixMode fix_mode = 21; // Fix mode from GSA (2D/3D). float hdop = 6; // Horizontal dilution of precision. + float pdop = 22; // Position (3D) dilution of precision, from GSA. + float vdop = 23; // Vertical dilution of precision, from GSA. LatLongPosition global_position = 7; // Reported global position. + float altitude = 24; // Altitude above mean sea level (m), from GGA. float stddev_latitude = 8; // Standard deviation of the latitude (m). Meaningless unless is_accuracy_valid. float stddev_longitude = 9; // Standard deviation of the longitude (m). Meaningless unless is_accuracy_valid. + float stddev_altitude = 25; // Standard deviation of the altitude (m). Meaningless unless is_accuracy_valid. + float stddev_semi_major = 26; // Semi-major axis of the horizontal error ellipse (m). + float stddev_semi_minor = 27; // Semi-minor axis of the horizontal error ellipse (m). + float error_ellipse_orientation = 28; // Orientation of the semi-major axis, clockwise from true north (°). + float range_rms = 29; // RMS of the range residuals (m), from GST. bool is_accuracy_valid = 18; // True once the receiver has reported a GST sentence to derive the deviations from. float course_over_ground = 10; // Course over ground (°). Meaningless unless is_course_valid. bool is_course_valid = 19; // True when the fix is valid and the receiver actually reported a course. float speed_over_ground = 11; // Speed over ground (m/s). + float differential_age = 30; // Age of the differential corrections (s), or -1 without DGPS/RTK. + int32 differential_station = 31; // Id of the station supplying the differential corrections, or -1 when absent. uint32 baud_rate = 12; // Serial baud rate in use (bit/s). uint32 reconnect_count = 13; // Times the driver has reconnected to the receiver since boot. uint32 receive_error_count = 14; // Receive errors seen by the driver since boot.