diff --git a/python/fusion_engine_client/analysis/analyzer.py b/python/fusion_engine_client/analysis/analyzer.py
index 3ef47a18..0d084c75 100755
--- a/python/fusion_engine_client/analysis/analyzer.py
+++ b/python/fusion_engine_client/analysis/analyzer.py
@@ -1943,24 +1943,24 @@ def _plot_imu_data(self, message_cls, filename, figure_title):
self._add_figure(name=filename, figure=figure, title=figure_title)
- def plot_heading_measurements(self):
+ def plot_gnss_attitude_measurements(self):
"""!
- @brief Generate time series plots for heading (degrees) and baseline distance (meters) data.
+ @brief Generate time series plots for GNSS attitude (degrees) and baseline distance (meters) data.
"""
if self.output_dir is None:
return
- # Read the heading measurement data.
- result = self.reader.read(message_types=[RawHeadingOutput, HeadingOutput], **self.params)
- raw_heading_data = result[RawHeadingOutput.MESSAGE_TYPE]
- heading_data = result[HeadingOutput.MESSAGE_TYPE]
+ # Read the attitude measurement data.
+ result = self.reader.read(message_types=[RawGNSSAttitudeOutput, GNSSAttitudeOutput], **self.params)
+ raw_heading_data = result[RawGNSSAttitudeOutput.MESSAGE_TYPE]
+ heading_data = result[GNSSAttitudeOutput.MESSAGE_TYPE]
if (len(heading_data.p1_time) == 0) and (len(raw_heading_data.p1_time) == 0):
- self.logger.info('No heading measurement data available. Skipping plot.')
+ self.logger.info('No GNSS attitude measurement data available. Skipping plot.')
return
- # Note that we read the pose data after heading, that way we don't bother reading pose data from disk if there's
- # no heading data in the log.
+ # Note that we read the pose data after attitude, that way we don't bother reading pose data from disk if
+ # there's no heading data in the log.
result = self.reader.read(message_types=[PoseMessage], source_ids=self.default_source_id, **self.params)
primary_pose_data = result[PoseMessage.MESSAGE_TYPE]
@@ -1987,7 +1987,6 @@ def plot_heading_measurements(self):
fig.update_layout(title='Heading Plots', legend_traceorder='normal', modebar_add=['v1hovermode'])
-
# Display the navigation engine's heading estimate, if available, for comparison with the heading sensor
# measurement.
if primary_pose_data is not None:
@@ -2003,7 +2002,7 @@ def plot_heading_measurements(self):
customdata=primary_pose_data.p1_time,
mode='lines',
line={'color': 'yellow'},
- name='Primary Device Heading Estimate',
+ name='Navigation Engine Heading Estimate',
hovertemplate='Time: %{x:.3f} sec (%{customdata:.3f} sec)'
'
Heading: %{y:.2f} deg'
),
@@ -2013,10 +2012,11 @@ def plot_heading_measurements(self):
# Corrected heading plot
if len(heading_data.p1_time) > 0:
heading_time = heading_data.p1_time - float(self.t0)
+ heading_deg = 90.0 - heading_data.ypr_deg[0, :]
fig.add_trace(
go.Scatter(
x=heading_time,
- y=heading_data.heading_true_north_deg,
+ y=heading_deg,
customdata=heading_data.p1_time,
mode='markers',
marker={'size': 2, "color": "green"},
@@ -2028,9 +2028,26 @@ def plot_heading_measurements(self):
row=1, col=1
)
+ fig.add_trace(
+ go.Scatter(
+ x=heading_time,
+ y=heading_data.baseline_distance_m,
+ customdata=heading_data.p1_time,
+ marker={'size': 2, "color": "green"},
+ hovertemplate='Time: %{x:.3f} sec (%{customdata:.3f} sec)'
+ '
Baseline: %{y:.2f} m',
+ name='Baseline'
+ ),
+ row=2, col=1
+ )
+
# Uncorrected heading plot
if len(raw_heading_data.p1_time) > 0:
raw_heading_time = raw_heading_data.p1_time - float(self.t0)
+ raw_heading_deg = np.degrees(np.arctan2(raw_heading_data.relative_position_enu_m[1, :],
+ raw_heading_data.relative_position_enu_m[0, :]))
+ raw_baseline_distance_m = np.linalg.norm(raw_heading_data.relative_position_enu_m, axis=0)
+
# Compute heading uncertainty envelop.
denom = raw_heading_data.relative_position_enu_m[0]**2 + raw_heading_data.relative_position_enu_m[1]**2
dh_e = raw_heading_data.relative_position_enu_m[0] / denom
@@ -2042,13 +2059,13 @@ def plot_heading_measurements(self):
)
envelope = np.arctan(
- (2 * heading_std / raw_heading_data.baseline_distance_m)
+ (2 * heading_std / raw_baseline_distance_m)
)
envelope *= 180. / np.pi
fig.add_trace(
go.Scatter(
x=raw_heading_time,
- y=raw_heading_data.heading_true_north_deg,
+ y=raw_heading_deg,
customdata=raw_heading_data.p1_time,
mode='markers',
marker={'size': 2, "color": "red"},
@@ -2059,12 +2076,12 @@ def plot_heading_measurements(self):
),
row=1, col=1
)
- idx = ~np.isnan(raw_heading_data.heading_true_north_deg)
+ idx = ~np.isnan(raw_heading_deg)
fig.add_trace(
go.Scatter(
x=raw_heading_time[idx],
- y=raw_heading_data.heading_true_north_deg[idx] + envelope[idx],
+ y=raw_heading_deg[idx] + envelope[idx],
mode='lines',
marker={'size': 2, "color": "red"},
line=dict(width=0),
@@ -2078,7 +2095,7 @@ def plot_heading_measurements(self):
fig.add_trace(
go.Scatter(
x=raw_heading_time[idx],
- y=raw_heading_data.heading_true_north_deg[idx] - envelope[idx],
+ y=raw_heading_deg[idx] - envelope[idx],
mode='lines',
marker={'size': 2, "color": "red"},
line=dict(width=0),
@@ -2131,7 +2148,7 @@ def plot_heading_measurements(self):
fig.add_trace(
go.Scatter(
x=raw_heading_time,
- y=raw_heading_data.baseline_distance_m,
+ y=raw_baseline_distance_m,
customdata=raw_heading_data.p1_time,
marker={'size': 2, "color": "red"},
hovertemplate='Time: %{x:.3f} sec (%{customdata:.3f} sec)'
@@ -2188,7 +2205,7 @@ def plot_heading_measurements(self):
row=3, col=1
)
- self._add_figure(name='heading_measurement', figure=fig, title='Measurements: Heading')
+ self._add_figure(name='gnss_attitude_measurement', figure=fig, title='Measurements: GNSS Attitude')
def plot_system_status_profiling(self):
"""!
@@ -3002,9 +3019,9 @@ def main(args=None):
analyzer.plot_gnss_corrections_status()
analyzer.plot_dop()
- # By default, we always plot heading measurements (i.e., output from a secondary heading device like an
+ # By default, we always plot attitude measurements (i.e., output from a secondary GNSS attitude sensor like an
# LG69T-AH), separate from other sensor measurements controlled by --measurements.
- analyzer.plot_heading_measurements()
+ analyzer.plot_gnss_attitude_measurements()
if truth_lla_deg is not None:
analyzer.plot_stationary_position_error(truth_lla_deg)
diff --git a/python/fusion_engine_client/messages/defs.py b/python/fusion_engine_client/messages/defs.py
index 8fcfb0c2..84fab7dc 100644
--- a/python/fusion_engine_client/messages/defs.py
+++ b/python/fusion_engine_client/messages/defs.py
@@ -99,10 +99,12 @@ class MessageType(IntEnum):
# Sensor measurement messages.
IMU_OUTPUT = 11000
- RAW_HEADING_OUTPUT = 11001
+ DEPRECATED_RAW_HEADING_OUTPUT = 11001
RAW_IMU_OUTPUT = 11002
- HEADING_OUTPUT = 11003
+ DEPRECATED_HEADING_OUTPUT = 11003
IMU_INPUT = 11004
+ GNSS_ATTITUDE_OUTPUT = 11005
+ RAW_GNSS_ATTITUDE_OUTPUT = 11006
# Vehicle measurement messages.
DEPRECATED_WHEEL_SPEED_MEASUREMENT = 11101
@@ -587,6 +589,13 @@ def get_p1_time(self) -> Timestamp:
else:
return getattr(self, 'p1_time', None)
+ def get_gps_time(self) -> Timestamp:
+ measurement_details = getattr(self, 'details', None)
+ if isinstance(measurement_details, MeasurementDetails):
+ if measurement_details.measurement_time_source == SystemTimeSource.GPS_TIME:
+ return measurement_details.measurement_time
+ return getattr(self, 'gps_time', None)
+
def get_system_time_ns(self) -> float:
measurement_details = getattr(self, 'details', None)
if isinstance(measurement_details, MeasurementDetails):
@@ -785,3 +794,11 @@ def PackedDataToBuffer(packed_data: bytes, buffer: Optional[bytes] = None, offse
return buffer
else:
return len(packed_data)
+
+
+def yaw_to_heading(yaw_deg: Union[float, np.ndarray]):
+ return 90.0 - yaw_deg
+
+
+def heading_to_yaw(heading_deg: Union[float, np.ndarray]):
+ return 90.0 - heading_deg
diff --git a/python/fusion_engine_client/messages/measurement_details.py b/python/fusion_engine_client/messages/measurement_details.py
index ac17b288..08280f9e 100644
--- a/python/fusion_engine_client/messages/measurement_details.py
+++ b/python/fusion_engine_client/messages/measurement_details.py
@@ -131,6 +131,12 @@ def to_numpy(cls, messages):
'p1_time': p1_time,
}
+ idx = time_source == SystemTimeSource.GPS_TIME
+ if np.any(idx):
+ gps_time = np.full_like(time_source, np.nan)
+ gps_time[idx] = measurement_time[idx]
+ result['gps_time'] = gps_time
+
idx = time_source == SystemTimeSource.TIMESTAMPED_ON_RECEPTION
if np.any(idx):
system_time = np.full_like(time_source, np.nan)
diff --git a/python/fusion_engine_client/messages/measurements.py b/python/fusion_engine_client/messages/measurements.py
index 2847e8f5..cc6382d5 100644
--- a/python/fusion_engine_client/messages/measurements.py
+++ b/python/fusion_engine_client/messages/measurements.py
@@ -1,3 +1,4 @@
+import math
import struct
from typing import Sequence
@@ -1165,18 +1166,18 @@ def to_numpy(cls, messages):
return result
################################################################################
-# Heading Sensor Definitions
+# GNSS Heading Sensor Definitions
################################################################################
-class HeadingOutput(MessagePayload):
+class GNSSAttitudeOutput(MessagePayload):
"""!
- @brief Heading sensor measurement output with heading bias corrections applied.
+ @brief Multi-antenna GNSS attitude sensor measurement output with offset corrections applied.
"""
- MESSAGE_TYPE = MessageType.HEADING_OUTPUT
+ MESSAGE_TYPE = MessageType.GNSS_ATTITUDE_OUTPUT
MESSAGE_VERSION = 0
- _STRUCT = struct.Struct(' (bytes, int):
if buffer is None:
@@ -1216,7 +1222,11 @@ def pack(self, buffer: bytes = None, offset: int = 0, return_buffer: bool = True
self.ypr_deg[0],
self.ypr_deg[1],
self.ypr_deg[2],
- self.heading_true_north_deg)
+ self.ypr_std_deg[0],
+ self.ypr_std_deg[1],
+ self.ypr_std_deg[2],
+ self.baseline_distance_m,
+ self.baseline_distance_std_m)
offset += self._STRUCT.size
if return_buffer:
@@ -1234,7 +1244,11 @@ def unpack(self, buffer: bytes, offset: int = 0, message_version: int = MessageP
self.ypr_deg[0],
self.ypr_deg[1],
self.ypr_deg[2],
- self.heading_true_north_deg) = \
+ self.ypr_std_deg[0],
+ self.ypr_std_deg[1],
+ self.ypr_std_deg[2],
+ self.baseline_distance_m,
+ self.baseline_distance_std_m) = \
self._STRUCT.unpack_from(buffer, offset)
offset += self._STRUCT.size
@@ -1244,46 +1258,61 @@ def unpack(self, buffer: bytes, offset: int = 0, message_version: int = MessageP
def __repr__(self):
result = super().__repr__()[:-1]
- result += f', solution_type={self.solution_type}, heading={self.heading_true_north_deg:.1f} deg]'
+ ypr_str = '(%.1f, %.1f, %.1f)' % tuple(self.ypr_deg)
+ result += f', solution_type={self.solution_type}, ypr={ypr_str} deg, ' \
+ f'baseline={self.baseline_distance_m} m]'
return result
def __str__(self):
+ gps_time = self.get_gps_time()
+ if gps_time is not None:
+ gps_str = f'{str(gps_time).replace("GPS: ", "")}'
+ utc_str = f'{datetime_to_string(gps_time.as_utc())}'
+ else:
+ gps_str = 'None'
+ utc_str = 'None'
return f"""\
-Heading Output @ {str(self.details.p1_time)}
+GNSS Attitude Output @ {str(self.details.p1_time)}
+ GPS time: {gps_str}
+ UTC time: {utc_str}
Solution Type: {self.solution_type}
- YPR (ENU) (deg): {self.ypr_deg[0]:.2f}, {self.ypr_deg[1]:.2f}, {self.ypr_deg[2]:.2f}
- Heading (deg): {self.heading_true_north_deg:.2f}"""
+ YPR (deg): {self.ypr_deg[0]:.2f}, {self.ypr_deg[1]:.2f}, {self.ypr_deg[2]:.2f}
+ YPR std (deg): {self.ypr_std_deg[0]:.2f}, {self.ypr_std_deg[1]:.2f}, {self.ypr_std_deg[2]:.2f}
+ Baseline distance (m): {self.baseline_distance_m:.2f}
+ Baseline std (m): {self.baseline_distance_std_m:.2f}"""
@classmethod
def calcsize(cls) -> int:
return cls._STRUCT.size + MeasurementDetails.calcsize()
@classmethod
- def to_numpy(cls, messages: Sequence['HeadingOutput']):
+ def to_numpy(cls, messages: Sequence['GNSSAttitudeOutput']):
result = {
'solution_type': np.array([int(m.solution_type) for m in messages], dtype=int),
'flags': np.array([int(m.flags) for m in messages], dtype=np.uint32),
'ypr_deg': np.array([m.ypr_deg for m in messages]).T,
- 'heading_true_north_deg': np.array([m.heading_true_north_deg for m in messages], dtype=float),
+ 'ypr_std_deg': np.array([m.ypr_std_deg for m in messages]).T,
+ 'baseline_distance_m': np.array([float(m.baseline_distance_m) for m in messages]),
+ 'baseline_distance_std_m': np.array([float(m.baseline_distance_std_m) for m in messages]),
}
result.update(MeasurementDetails.to_numpy([m.details for m in messages]))
return result
-class RawHeadingOutput(MessagePayload):
+class RawGNSSAttitudeOutput(MessagePayload):
"""!
- @brief Raw (uncorrected) heading sensor measurement output.
+ @brief Raw (uncorrected) GNSS attitude sensor measurement output.
"""
- MESSAGE_TYPE = MessageType.RAW_HEADING_OUTPUT
+ MESSAGE_TYPE = MessageType.RAW_GNSS_ATTITUDE_OUTPUT
MESSAGE_VERSION = 0
- _STRUCT = struct.Struct(' (bytes, int):
if buffer is None:
buffer = bytearray(self.calcsize())
@@ -1329,8 +1358,8 @@ def pack(self, buffer: bytes = None, offset: int = 0, return_buffer: bool = True
self.position_std_enu_m[0],
self.position_std_enu_m[1],
self.position_std_enu_m[2],
- self.heading_true_north_deg,
- self.baseline_distance_m)
+ self.baseline_distance_m,
+ self.baseline_distance_std_m)
offset += self._STRUCT.size
if return_buffer:
@@ -1350,9 +1379,7 @@ def unpack(self, buffer: bytes, offset: int = 0, message_version: int = MessageP
self.relative_position_enu_m[2],
self.position_std_enu_m[0],
self.position_std_enu_m[1],
- self.position_std_enu_m[2],
- self.heading_true_north_deg,
- self.baseline_distance_m) = self._STRUCT.unpack_from(buffer, offset)
+ self.position_std_enu_m[2]) = self._STRUCT.unpack_from(buffer, offset)
offset += self._STRUCT.size
self.solution_type = SolutionType(solution_type_int)
@@ -1361,36 +1388,47 @@ def unpack(self, buffer: bytes, offset: int = 0, message_version: int = MessageP
def __repr__(self):
result = super().__repr__()[:-1]
- result += f', solution_type={self.solution_type}, heading={self.heading_true_north_deg:.1f} deg, ' \
- f'baseline={self.baseline_distance_m} m]'
+ enu_str = '(%.2f, %.2f, %.3f)' % tuple(self.relative_position_enu_m)
+ heading_deg = self.get_heading_deg()
+ result += f', solution_type={self.solution_type}, enu={enu_str} m, heading={heading_deg:.1f} deg]'
return result
def __str__(self):
+ gps_time = self.get_gps_time()
+ if gps_time is not None:
+ gps_str = f'{str(gps_time).replace("GPS: ", "")}'
+ utc_str = f'{datetime_to_string(gps_time.as_utc())}'
+ else:
+ gps_str = 'None'
+ utc_str = 'None'
return f"""\
-Raw Heading Output @ {str(self.details.p1_time)}
+Raw GNSS Attitude Output @ {str(self.details.p1_time)}
+ GPS time: {gps_str}
+ UTC time: {utc_str}
Solution Type: {self.solution_type}
Relative position (ENU) (m): {self.relative_position_enu_m[0]:.2f}, {self.relative_position_enu_m[1]:.2f}, {self.relative_position_enu_m[2]:.2f}
Position std (ENU) (m): {self.position_std_enu_m[0]:.2f}, {self.position_std_enu_m[1]:.2f}, {self.position_std_enu_m[2]:.2f}
- Heading (deg): {self.heading_true_north_deg:.2f}
- Baseline distance (m): {self.baseline_distance_m:.2f}"""
+ Heading (deg): {self.get_heading_deg():.2f}"""
@classmethod
def calcsize(cls) -> int:
return cls._STRUCT.size + MeasurementDetails.calcsize()
@classmethod
- def to_numpy(cls, messages: Sequence['RawHeadingOutput']):
+ def to_numpy(cls, messages: Sequence['RawGNSSAttitudeOutput']):
result = {
'solution_type': np.array([int(m.solution_type) for m in messages], dtype=int),
'flags': np.array([int(m.flags) for m in messages], dtype=np.uint32),
'relative_position_enu_m': np.array([m.relative_position_enu_m for m in messages]).T,
'position_std_enu_m': np.array([m.position_std_enu_m for m in messages]).T,
- 'heading_true_north_deg': np.array([float(m.heading_true_north_deg) for m in messages]),
- 'baseline_distance_m': np.array([float(m.baseline_distance_m) for m in messages]),
}
result.update(MeasurementDetails.to_numpy([m.details for m in messages]))
return result
+################################################################################
+# Binary Sensor Data Definitions
+################################################################################
+
class InputDataWrapperMessage(MessagePayload):
"""!
diff --git a/python/fusion_engine_client/messages/solution.py b/python/fusion_engine_client/messages/solution.py
index efaafa18..e6b910be 100644
--- a/python/fusion_engine_client/messages/solution.py
+++ b/python/fusion_engine_client/messages/solution.py
@@ -813,6 +813,12 @@ def to_numpy(cls, messages: Sequence['CalibrationStatus']):
class RelativeENUPositionMessage(MessagePayload):
"""!
@brief Relative ENU position to base station.
+
+ @note
+ This message represents the relationship between the navigation engine's
+ position solution and a nearby RTK base station. It is not used to convey
+ unfiltered vehicle body orientation measurements generated using multiple
+ GNSS antennas. See @ref GNSSAttitudeOutput instead.
"""
MESSAGE_TYPE = MessageType.RELATIVE_ENU_POSITION
MESSAGE_VERSION = 0
diff --git a/src/point_one/fusion_engine/messages/configuration.h b/src/point_one/fusion_engine/messages/configuration.h
index f73cee94..042160d6 100644
--- a/src/point_one/fusion_engine/messages/configuration.h
+++ b/src/point_one/fusion_engine/messages/configuration.h
@@ -1222,7 +1222,7 @@ struct P1_ALIGNAS(4) HardwareTickConfig {
* If one value is NOT set, the system will not output the corrected
* heading message.
*
- * @ref HeadingOutput
+ * @ref GNSSAttitudeOutput
*/
struct P1_ALIGNAS(4) HeadingBias {
/**
diff --git a/src/point_one/fusion_engine/messages/defs.h b/src/point_one/fusion_engine/messages/defs.h
index 26e964a4..b91c4da7 100644
--- a/src/point_one/fusion_engine/messages/defs.h
+++ b/src/point_one/fusion_engine/messages/defs.h
@@ -47,10 +47,12 @@ enum class MessageType : uint16_t {
// Sensor measurement messages.
IMU_OUTPUT = 11000, ///< @ref IMUOutput
- RAW_HEADING_OUTPUT = 11001, ///< @ref RawHeadingOutput
+ DEPRECATED_RAW_HEADING_OUTPUT = 11001,
RAW_IMU_OUTPUT = 11002, ///< @ref RawIMUOutput
- HEADING_OUTPUT = 11003, ///< @ref HeadingOutput
+ DEPRECATED_HEADING_OUTPUT = 11003,
IMU_INPUT = 11004, ///< @ref IMUInput
+ GNSS_ATTITUDE_OUTPUT = 11005, ///< @ref GNSSAttitudeOutput
+ RAW_GNSS_ATTITUDE_OUTPUT = 11006, ///< @ref RawGNSSAttitudeOutput
// Vehicle measurement messages.
DEPRECATED_WHEEL_SPEED_MEASUREMENT =
@@ -151,18 +153,24 @@ P1_CONSTEXPR_FUNC const char* to_string(MessageType type) {
case MessageType::IMU_OUTPUT:
return "IMU Output";
- case MessageType::RAW_HEADING_OUTPUT:
- return "Raw heading output";
+ case MessageType::DEPRECATED_RAW_HEADING_OUTPUT:
+ return "Raw GNSS Heading Output";
case MessageType::RAW_IMU_OUTPUT:
return "Raw IMU Output";
- case MessageType::HEADING_OUTPUT:
- return "Heading Output";
+ case MessageType::DEPRECATED_HEADING_OUTPUT:
+ return "GNSS Heading Output";
case MessageType::IMU_INPUT:
return "IMU Input";
+ case MessageType::GNSS_ATTITUDE_OUTPUT:
+ return "GNSS Attitude Output";
+
+ case MessageType::RAW_GNSS_ATTITUDE_OUTPUT:
+ return "Raw GNSS Attitude Output";
+
case MessageType::DEPRECATED_WHEEL_SPEED_MEASUREMENT:
return "Wheel Speed Measurement";
@@ -320,6 +328,7 @@ P1_CONSTEXPR_FUNC bool IsCommand(MessageType message_type) {
case MessageType::GET_MESSAGE_RATE:
case MessageType::STA5635_COMMAND:
return true;
+
case MessageType::INVALID:
case MessageType::POSE:
case MessageType::GNSS_INFO:
@@ -329,10 +338,12 @@ P1_CONSTEXPR_FUNC bool IsCommand(MessageType message_type) {
case MessageType::RELATIVE_ENU_POSITION:
case MessageType::SYSTEM_STATUS:
case MessageType::IMU_OUTPUT:
- case MessageType::RAW_HEADING_OUTPUT:
+ case MessageType::DEPRECATED_RAW_HEADING_OUTPUT:
case MessageType::RAW_IMU_OUTPUT:
- case MessageType::HEADING_OUTPUT:
+ case MessageType::DEPRECATED_HEADING_OUTPUT:
case MessageType::IMU_INPUT:
+ case MessageType::GNSS_ATTITUDE_OUTPUT:
+ case MessageType::RAW_GNSS_ATTITUDE_OUTPUT:
case MessageType::DEPRECATED_WHEEL_SPEED_MEASUREMENT:
case MessageType::DEPRECATED_VEHICLE_SPEED_MEASUREMENT:
case MessageType::WHEEL_TICK_INPUT:
diff --git a/src/point_one/fusion_engine/messages/device.h b/src/point_one/fusion_engine/messages/device.h
index 56c3e497..d4fdda71 100644
--- a/src/point_one/fusion_engine/messages/device.h
+++ b/src/point_one/fusion_engine/messages/device.h
@@ -286,11 +286,6 @@ struct P1_ALIGNAS(4) EventNotificationMessage : public MessagePayload {
* @brief System status information (@ref
* MessageType::SYSTEM_STATUS, version 1.0).
* @ingroup device_status
- *
- * @note
- * All data is timestamped using the Point One Time, which is a monotonic
- * timestamp referenced to the start of the device. Corresponding messages (@ref
- * SystemStatusMessage) may be associated using their @ref p1_time values.
*/
struct P1_ALIGNAS(4) SystemStatusMessage : public MessagePayload {
diff --git a/src/point_one/fusion_engine/messages/measurements.h b/src/point_one/fusion_engine/messages/measurements.h
index f414293e..d25ad09a 100644
--- a/src/point_one/fusion_engine/messages/measurements.h
+++ b/src/point_one/fusion_engine/messages/measurements.h
@@ -1134,23 +1134,33 @@ struct P1_ALIGNAS(4) DeprecatedVehicleSpeedMeasurement : public MessagePayload {
};
////////////////////////////////////////////////////////////////////////////////
-// Heading Sensor Definitions
+// Attitude Sensor Definitions
////////////////////////////////////////////////////////////////////////////////
/**
- * @brief Heading sensor measurement output with heading bias corrections
- * applied (@ref MessageType::HEADING_OUTPUT, version 1.0).
+ * @brief Multi-antenna GNSS attitude sensor measurement output with offset
+ * corrections applied (@ref MessageType::GNSS_ATTITUDE_OUTPUT, version
+ * 1.0).
* @ingroup measurement_messages
*
- * This message is an output from the device contaning heading sensor
- * measurements after applying user-specified horizontal and vertical bias
- * corrections to account for the orientation of the primary and secondary GNSS
- * antennas.
+ * This message is an output from the device contaning orientation measurements
+ * generated using multiple GNSS antennas/receivers. On supported devices, the
+ * device will measure vehicle yaw (heading) and pitch based on the relative
+ * positions of two GNSS antennas. When more than two antennas are present, the
+ * device may additionally measure roll angle.
*
- * See also @ref RawHeadingOutput.
+ * @note
+ * This message contains vehicle body angle measurements generated from GNSS
+ * measurements. These measurements inputs to the navigation engine, not the
+ * filtered output from engine. They may be less accurate than the vehicle body
+ * orientation estimate in @ref PoseMessage.
+ *
+ * The measurements in this message have user-specified corrections applied for
+ * the horizontal and vertical offsets between the two GNSS antennas. See also
+ * @ref RawGNSSAttitudeOutput.
*/
-struct P1_ALIGNAS(4) HeadingOutput : public MessagePayload {
- static constexpr MessageType MESSAGE_TYPE = MessageType::HEADING_OUTPUT;
+struct P1_ALIGNAS(4) GNSSAttitudeOutput : public MessagePayload {
+ static constexpr MessageType MESSAGE_TYPE = MessageType::GNSS_ATTITUDE_OUTPUT;
static constexpr uint8_t MESSAGE_VERSION = 0;
/**
@@ -1171,44 +1181,54 @@ struct P1_ALIGNAS(4) HeadingOutput : public MessagePayload {
uint32_t flags = 0;
/**
- * The measured YPR vector (in degrees), resolved in the ENU frame.
+ * The measured vehicle body orientation (in degrees).
*
* YPR is defined as an intrinsic Euler-321 rotation, i.e., yaw, pitch, then
- * roll.
+ * roll with respect to the local ENU tangent plane. See @ref
+ * PoseMessage::ypr_deg for a complete rotation definition.
+ *
+ * If any angles are not available, they will be set to `NAN`. For
+ * dual-antenna systems, the device will measure yaw and pitch, but not roll.
*
- * @note
- * This field contains the measured attitude information (@ref
- * RawHeadingOutput) from a secondary heading device after applying @ref
- * ConfigType::HEADING_BIAS configuration settings for yaw (horizontal) and
- * pitch (vertical) offsets between the primary and secondary GNSS antennas.
- * If either bias value is not specified, the corresponding measurement values
- * will be set to `NAN`.
+ * Note that yaw is measured from east in a counter-clockwise direction. For
+ * example, north is +90 degrees. Heading with respect to true north can be
+ * computed as `heading = 90.0 - ypr_deg[0]`.
*/
float ypr_deg[3] = {NAN, NAN, NAN};
/**
- * The heading angle (in degrees) with respect to true north, pointing from
- * the primary antenna to the secondary antenna, after applying bias
- * corrections.
- *
- * @note
- * Reported in the range [0, 360).
+ * The standard deviation of the orientation measurement (in degrees).
+ */
+ float ypr_std_deg[3] = {NAN, NAN, NAN};
+
+ /**
+ * The estimated distance between primary and secondary antennas (in meters).
+ */
+ float baseline_distance_m = NAN;
+
+ /**
+ * The standard deviation of the baseline distance estimate (in meters).
*/
- float heading_true_north_deg = NAN;
+ float baseline_distance_std_m = NAN;
};
/**
- * @brief Raw (uncorrected) heading sensor measurement output (@ref
- * MessageType::RAW_HEADING_OUTPUT, version 1.0).
+ * @brief Raw (uncorrected) GNSS attitude sensor measurement output (@ref
+ * MessageType::RAW_GNSS_ATTITUDE_OUTPUT, version 1.0).
* @ingroup measurement_messages
*
- * This message is an output from the device contaning raw heading sensor
- * measurements that have not been corrected for mounting angle biases.
+ * This message is an output from the device contaning raw orientation
+ * measurements generated using multiple GNSS antennas/receivers that have not
+ * been corrected for horizontal/vertical offsets between the antennas. Here,
+ * orientation is represented as the vector from a primary GNSS antenna to a
+ * secondary GNSS antenna.
*
- * See also @ref HeadingOutput.
+ * For vehicle body angle measurements, and for measurements corrected for
+ * horizontal/vertical offsets, see @ref GNSSAttitudeOutput.
*/
-struct P1_ALIGNAS(4) RawHeadingOutput : public MessagePayload {
- static constexpr MessageType MESSAGE_TYPE = MessageType::RAW_HEADING_OUTPUT;
+struct P1_ALIGNAS(4) RawGNSSAttitudeOutput : public MessagePayload {
+ static constexpr MessageType MESSAGE_TYPE =
+ MessageType::RAW_GNSS_ATTITUDE_OUTPUT;
static constexpr uint8_t MESSAGE_VERSION = 0;
/**
@@ -1241,26 +1261,16 @@ struct P1_ALIGNAS(4) RawHeadingOutput : public MessagePayload {
float relative_position_enu_m[3] = {NAN, NAN, NAN};
/**
- * The position standard deviation (in meters), resolved with respect to the
- * local ENU tangent plane: east, north, up.
+ * The standard deviation of the relative position vector (in meters),
+ * resolved with respect to the local ENU tangent plane: east, north, up.
*/
float position_std_enu_m[3] = {NAN, NAN, NAN};
-
- /**
- * The measured heading angle (in degrees) with respect to true north,
- * pointing from the primary antenna to the secondary antenna.
- *
- * @note
- * Reported in the range [0, 360).
- */
- float heading_true_north_deg = NAN;
-
- /**
- * The estimated distance between primary and secondary antennas (in meters).
- */
- float baseline_distance_m = NAN;
};
+////////////////////////////////////////////////////////////////////////////////
+// Binary Sensor Data Definitions
+////////////////////////////////////////////////////////////////////////////////
+
/**
* @brief A block of incoming sensor data whose definition depends on the value
* of @ ref data_type. (@ref MessageType::INPUT_DATA_WRAPPER).
diff --git a/src/point_one/fusion_engine/messages/solution.h b/src/point_one/fusion_engine/messages/solution.h
index 02dc1a01..f66422d8 100644
--- a/src/point_one/fusion_engine/messages/solution.h
+++ b/src/point_one/fusion_engine/messages/solution.h
@@ -32,7 +32,7 @@ namespace messages {
* @ingroup solution_messages
*
* @note
- * All data is timestamped using the Point One Time, which is a monotonic
+ * All data is timestamped using Point One (P1) time, which is a monotonic
* timestamp referenced to the start of the device. Corresponding messages (@ref
* GNSSInfoMessage, @ref GNSSSatelliteMessage, etc.) may be associated using
* their @ref p1_time values.
@@ -504,10 +504,10 @@ struct P1_ALIGNAS(4) CalibrationStatusMessage : public MessagePayload {
* @ingroup solution_messages
*
* @note
- * All data is timestamped using the Point One Time, which is a monotonic
- * timestamp referenced to the start of the device. Corresponding messages (@ref
- * PoseMessage, @ref GNSSSatelliteMessage, etc.) may be associated using
- * their @ref p1_time values.
+ * This message represents the relationship between the navigation engine's
+ * position solution and a nearby RTK base station. It is not used to convey
+ * unfiltered vehicle body orientation measurements generated using multiple
+ * GNSS antennas. See @ref GNSSAttitudeOutput instead.
*/
struct P1_ALIGNAS(4) RelativeENUPositionMessage : public MessagePayload {
static constexpr MessageType MESSAGE_TYPE =