Fixposition SDK 0.0.0-heads/main-0-g90a51ff
Collection of c++ libraries and apps for use with Fixposition products
Loading...
Searching...
No Matches
Collecting fusion epoch data from FP_A messages
1/**
2 * \verbatim
3 * ___ ___
4 * \ \ / /
5 * \ \/ / Copyright (c) Fixposition AG (www.fixposition.com) and contributors
6 * / /\ \ License: see the LICENSE file
7 * /__/ \__\
8 * \endverbatim
9 *
10 * @file
11 * @brief Fixposition SDK examples: collecting fusion epoch data from FP_A messages
12 *
13 * To build and run:
14 *
15 * make
16 * ./build/fusion_epoch
17 *
18 * This program builds on the "parser_intro" example and details how to collect the FP_A fusion messages into
19 * fusion epochs for further processing.
20 *
21 * This file is both the source code of this example as well as the documentation on how it works.
22 */
23
24/* LIBC/STL */
25#include <cinttypes>
26#include <cstdint>
27#include <cstdlib>
28
29/* EXTERNAL */
30#include "fpsdk_common/ext/eigen_core.hpp"
31
32/* Fixposition SDK */
33#include <fpsdk_common/app.hpp>
38
39/* PACKAGE */
40
41/* ****************************************************************************************************************** */
42
43using namespace fpsdk::common::time;
44using namespace fpsdk::common::app;
45using namespace fpsdk::common::parser;
46using namespace fpsdk::common::parser::fpa;
47using namespace fpsdk::common::logging;
48using namespace fpsdk::common::trafo;
49
50// ---------------------------------------------------------------------------------------------------------------------
51
52// The example data is a short sequence of output from the Vision-RTK 2 sensor.
53// clang-format off
54const uint8_t SAMPLE_DATA[] = {
55 // (1)
56 "$FP,ODOMSTATUS,1,2349,59920.000000,2,2,1,1,1,1,,0,,,,,,0,1,3,8,8,3,5,5,,0,6,,,,,,,,,,,,*18\r\n"
57 "$FP,EOE,1,2349,59920.000000,FUSION*59\r\n"
58 // (2)
59 "$FP,ODOMETRY,2,2349,59921.000000,4278387.6882,635620.5002,4672339.9313,-0.580560,0.326972,-0.184160,0.722582,"
60 "-0.0008,0.0001,-0.0003,0.00190,-0.00021,-0.00018,0.1875,-0.1978,9.8054,4,0,8,8,-1,0.00139,0.00459,0.00201,0.0024"
61 "4,-0.00294,-0.00160,0.03639,0.00068,0.04631,0.00467,-0.00524,-0.04100,0.00175,0.00055,0.00065,-0.00005,0.0000"
62 "0,0.00045,fp_vrtk2-integ_6912e460-1703*20\r\n"
63 "$FP,ODOMSTATUS,1,2349,59921.000000,1,2,1,1,1,1,,0,,,,,,0,1,3,8,8,3,5,5,,0,6,,,,,,,,,,,,*1A\r\n"
64 "$FP,EOE,1,2349,59921.000000,FUSION*58\r\n"
65 // (3)
66 "$FP,ODOMETRY,2,2349,59922.000000,,,,-0.580477,0.327111,-0.184310,0.722547,"
67 "-0.0012,0.0001,-0.0010,-0.00048,-0.00065,-0.00063,0.1939,-0.2015,9.8043,4,0,8,8,-1,0.00139,0.00458,0.00200,0.002"
68 "44,-0.00293,-0.00160,0.03636,0.00067,0.04631,0.00466,-0.00524,-0.04098,0.00171,0.00055,0.00064,-0.00005,0.000"
69 "00,0.00043,fp_vrtk2-integ_6912e460-1703*29\r\n"
70 "$FP,ODOMSTATUS,1,2349,59922.000000,2,2,1,1,1,1,,0,,,,,,0,1,3,8,8,3,5,5,,0,6,,,,,,,,,,,,*1A\r\n"
71 "$FP,EOE,1,2349,59922.000000,FUSION*5B\r\n"
72 // (4)
73 "$FP,ODOMETRY,2,2349,59923.000000,4278387.6888,635620.5014,4672339.9313,-0.580321,0.327140,-0.184394,0.722638,"
74 "-0.0023,0.0005,-0.0003,0.00038,0.00123,0.00023,0.1910,-0.2060,9.7975,4,0,8,8,-1,0.00139,0.00458,0.00200,0.00244,"
75 "-0.00293,-0.00160,0.03633,0.00068,0.04629,0.00467,-0.00524,-0.04096,0.00171,0.00055,0.00064,-0.00005,0.00000,"
76 "0.00043,fp_vrtk2-integ_6912e460-1703*27\r\n"
77 "$FP,ODOMSTATUS,1,2349,59923.000000,2,2,1,1,1,1,,0,,,,,,0,1,3,8,8,3,5,5,,0,6,,,,,,,,,,,,*1B\r\n"
78 "$FP,EOE,1,2349,59923.000000,FUSION*5A\r\n"
79};
80const std::size_t SAMPLE_SIZE = sizeof(SAMPLE_DATA) - 1; // -1 because "" adds \0
81// clang-format on
82
83// We want to collect the output messages into a "fusion epoch" so that we can further process the data. Specifically,
84// we collect the FP_A-ODOMETRY and FP_A-ODOMSTATUS into matching pairs of messages. We'll use the FP_A-EOE message to
85// detect when an epoch is complete.
86struct CollectedMsgs
87{
88 FpaOdometryPayload fpa_odometry_;
89 FpaOdomstatusPayload fpa_odomstatus_;
90 FpaEoePayload fpa_eoe_;
91};
92
93// The coordinate reference system (CRS) of the position output of the sensor depends on the correction data being used.
94// And for the application we want the to use a particular local coordinate reference system. In this example we assume
95// the raw sensor positions are in ETRS89 and we're in Switzerland, so we Swiss coordinates. See the documentation
96// of the Transformer in fpskd_common.hpp
97static constexpr const char* source_crs = "EPSG:4936"; // ETRS89 x/y/z
98static constexpr const char* target_crs = "EPSG:2056"; // Swiss CH1903+ / LV95 east/north/up
99
100// We'll process the data received from the sensor, for which we'll need a a coordinate transformer
101static void ProcessCollectedMsgs(const CollectedMsgs& collected_msgs, Transformer& trafo);
102
103int main(int /*argc*/, char** /*argv*/)
104{
105#ifndef NDEBUG
107#endif
108 LoggingSetParams({ LoggingLevel::TRACE });
109
110 // Initialise the coordinate transformer
111 Transformer trafo;
112 if (!trafo.Init(source_crs, target_crs)) {
113 ERROR("Failed creating coordinate transformer!");
114 return EXIT_FAILURE;
115 }
116
117 // We parse the input data, decode the messages and process the data as we go. See the parser_intro example for
118 // details on the parsing and decoding the messages.
119 Parser parser;
120 ParserMsg msg;
121 if (!parser.Add(SAMPLE_DATA, SAMPLE_SIZE)) {
122 WARNING("Parser overflow, SAMPLE_DATA is too large!");
123 }
124
125 // The collector of messages
126 CollectedMsgs collected_msgs;
127 while (parser.Process(msg)) {
128 DEBUG("Message %-s (size %" PRIuMAX " bytes)", msg.name_.c_str(), msg.data_.size());
129 bool msg_ok = true;
130
131 // Collect FP_A-ODOMETRY
132 if (msg.name_ == FpaOdometryPayload::MSG_NAME) {
133 msg_ok = collected_msgs.fpa_odometry_.SetFromMsg(msg.data_.data(), msg.data_.size());
134 }
135 // Collect FP_A-ODOMSTATUS
136 else if (msg.name_ == FpaOdomstatusPayload::MSG_NAME) {
137 msg_ok = collected_msgs.fpa_odomstatus_.SetFromMsg(msg.data_.data(), msg.data_.size());
138 }
139 // FP_A-EOE: Check if it the *fusion* end of epoch, otherwise ignore
140 else if (msg.name_ == FpaEoePayload::MSG_NAME) {
141 FpaEoePayload fpa_eoe;
142 msg_ok = fpa_eoe.SetFromMsg(msg.data_.data(), msg.data_.size());
143 if (fpa_eoe.epoch == FpaEpoch::FUSION) {
144 // Add it to the collection and process the data
145 collected_msgs.fpa_eoe_ = fpa_eoe;
146 ProcessCollectedMsgs(collected_msgs, trafo);
147
148 // Clear the collection for the next epoch
149 collected_msgs = CollectedMsgs();
150 }
151 }
152 // else: ignore any other message
153
154 // Carp if decoding a message failed. But otherwise ignore it. We'll handle missing messages below.
155 if (!msg_ok) {
156 msg.MakeInfo();
157 WARNING("Failed decoding %s: %s", msg.name_.c_str(), msg.info_.c_str());
158 // DEBUG_HEXDUMP(msg.data_.data(), msg.data_.size(), NULL, NULL); // Hexdump of the raw message data
159 }
160 }
161
162 // This should output:
163 //
164 // Message FP_A-ODOMSTATUS (size 92 bytes)
165 // Message FP_A-EOE (size 39 bytes)
166 // Warning: Ignoring incomplete epoch at 2349:059920.000 (1)
167 // Message FP_A-ODOMETRY (size 373 bytes)
168 // Message FP_A-ODOMSTATUS (size 92 bytes)
169 // Message FP_A-EOE (size 39 bytes)
170 // Warning: Ignoring uninitialised fusion at 2349:059921.000 (2)
171 // Message OTHER (size 256 bytes)
172 // Message OTHER (size 83 bytes)
173 // Message FP_A-ODOMSTATUS (size 92 bytes)
174 // Message FP_A-EOE (size 39 bytes)
175 // Warning: Ignoring unusable epoch at 2349:059922.000 (3)
176 // Message FP_A-ODOMETRY (size 371 bytes)
177 // Message FP_A-ODOMSTATUS (size 92 bytes)
178 // Message FP_A-EOE (size 39 bytes)
179 // Using position at 2349:059923.000: [ 4278387.7, 635620.5, 4672339.9 ] (4)
180 // Local coordinates: 2676373.0 1250433.7 459.5
181 //
182 // - (1) The first epoch is incomplete as the FP_A-ODOMETRY for that epoch was not present
183 // - (2) The second epoch is complete (all messages present and matching), but FP_A-ODOMSTATUS.init_status is
184 // LOCAL_INIT and we want status GLOBAL_INIT in order to use the data)
185 // - (3) The third epoch is complete, but the FP_A-ODOMETRY.pos_{x,y,z} fields are empty (and we want the position)
186 // - (4) The fourth epoch is complete and passes all checks and we can transform the position to the local CRS
187
188 return EXIT_SUCCESS;
189}
190
191static void ProcessCollectedMsgs(const CollectedMsgs& collected_msgs, Transformer& trafo)
192{
193 auto& fpa_odometry = collected_msgs.fpa_odometry_;
194 auto& fpa_odomstatus = collected_msgs.fpa_odomstatus_;
195 auto& fpa_eoe = collected_msgs.fpa_eoe_;
196
197 // The epoch is complete if all messages are present and their timestamps are valid and match
198 if (!(fpa_odometry.valid_ && fpa_odomstatus.valid_ && fpa_eoe.valid_ && fpa_odometry.gps_time.week.valid &&
199 fpa_odometry.gps_time.tow.valid && (fpa_odometry.gps_time == fpa_odomstatus.gps_time) &&
200 (fpa_odometry.gps_time == fpa_eoe.gps_time))) {
201 WARNING("Ignoring incomplete epoch at %04d:%010.3f", fpa_eoe.gps_time.week.value, fpa_eoe.gps_time.tow.value);
202 return;
203 }
204
205 // Epoch is complete. However, we'll have to check if all the data we need is present. We want the position and the
206 // fusion initialisation status.
207 if (!fpa_odometry.pos.valid || (fpa_odomstatus.init_status == FpaInitStatus::UNSPECIFIED)) {
208 WARNING("Ignoring unusable epoch at %04d:%010.3f", fpa_eoe.gps_time.week.value, fpa_eoe.gps_time.tow.value);
209 return;
210 }
211
212 // Now we should have all the data we want. For this example we only use the position if fusion is fully
213 // initialised.
214 if (fpa_odomstatus.init_status != FpaInitStatus::GLOBAL_INIT) {
215 WARNING(
216 "Ignoring uninitialised fusion at %04d:%010.3f", fpa_eoe.gps_time.week.value, fpa_eoe.gps_time.tow.value);
217 return;
218 }
219
220 // We have a usable position
221 INFO("Using position at %04d:%010.3f: [ %.1f, %.1f, %.1f ]", fpa_eoe.gps_time.week.value,
222 fpa_eoe.gps_time.tow.value, fpa_odometry.pos.values[0], fpa_odometry.pos.values[1], fpa_odometry.pos.values[2]);
223
224 // Transform to local CRS
225 Eigen::Vector3d pos = { fpa_odometry.pos.values[0], fpa_odometry.pos.values[1], fpa_odometry.pos.values[2] };
226 if (trafo.Transform(pos)) {
227 INFO("Local coordinates: %.1f %.1f %.1f", pos(0), pos(1), pos(2));
228 }
229
230 // Notes:
231 // - The same approach can be used to collect NMEA messages into an epoch. For example, using the FP_A-EOE for GNSS,
232 // this lets one reliably combine all NMEA messages for GNSS, including those that do not have a timestamp, into
233 // a consistent set of messages that are part of the same naviation epoch.
234}
235
236/* ****************************************************************************************************************** */
Fixposition SDK: Utilities for apps.
Helper to print a strack trace on SIGSEGV and SIGABRT.
Definition app.hpp:139
Message parser class.
Definition parser.hpp:165
bool Process(ParserMsg &msg)
Process data in parser, return message.
bool Add(const uint8_t *data, const std::size_t size)
Add data to parser.
"Universal" coordinate transformer, backed by PROJ
Definition trafo.hpp:216
bool Transform(Eigen::Vector3d &inout, const bool inv=false)
Transform coordinates.
Fixposition SDK: Parser FP_A routines and types.
Fixposition SDK: Logging.
#define ERROR(fmt,...)
Print a error message.
Definition logging.hpp:56
#define INFO(fmt,...)
Print a info message.
Definition logging.hpp:68
#define DEBUG(fmt,...)
Print a debug message.
Definition logging.hpp:72
#define WARNING(fmt,...)
Print a warning message.
Definition logging.hpp:60
Utilities for apps.
Definition app.hpp:34
Parser FP_A routines and types.
Definition fpa.hpp:96
Time utilities.
Definition time.hpp:39
Transformation utilities.
Definition trafo.hpp:36
Fixposition SDK: Parser.
Message frame output by the Parser.
Definition types.hpp:96
std::string info_
Message (debug) info, default empty, call MakeInfo() to fill.
Definition types.hpp:101
std::string name_
Name of the message.
Definition types.hpp:100
void MakeInfo() const
Make info_ field.
std::vector< uint8_t > data_
Message data.
Definition types.hpp:99
FP_A-EOE (version 1) message payload.
Definition fpa.hpp:538
bool SetFromMsg(const uint8_t *msg, const std::size_t msg_size)
Set data from message.
FpaEpoch epoch
Indicates which epoch ended ("FUSION", "GNSS", "GNSS1", "GNSS2")
Definition fpa.hpp:540
FpaInt week
GPS week number, range 0-9999, or null if time not (yet) available.
Definition fpa.hpp:514
FpaFloat tow
GPS time of week, range 0.000-604799.999999, or null if time not (yet) available.
Definition fpa.hpp:515
FP_A-ODOMETRY (version 2) messages payload (ECEF)
Definition fpa.hpp:772
FP_A-ODOMSTATUS (version 1) messages payload.
Definition fpa.hpp:802
bool valid_
Payload successfully decoded (true), or not (yet) decoded (false)
Definition fpa.hpp:530
Fixposition SDK: Transformation utilities.