Line | Branch | Exec | Source |
---|---|---|---|
1 | // This file is part of INSTINCT, the INS Toolkit for Integrated | ||
2 | // Navigation Concepts and Training by the Institute of Navigation of | ||
3 | // the University of Stuttgart, Germany. | ||
4 | // | ||
5 | // This Source Code Form is subject to the terms of the Mozilla Public | ||
6 | // License, v. 2.0. If a copy of the MPL was not distributed with this | ||
7 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
8 | |||
9 | /// @file Combiner.hpp | ||
10 | /// @brief Calculates differences between signals | ||
11 | /// @author T. Topp (topp@ins.uni-stuttgart.de) | ||
12 | /// @date 2024-01-29 | ||
13 | |||
14 | #pragma once | ||
15 | |||
16 | // <boost/asio.hpp> needs to be included before <winsock.h> (even though not used in this file) | ||
17 | // https://stackoverflow.com/questions/9750344/boostasio-winsock-and-winsock-2-compatibility-issue | ||
18 | #ifdef _WIN32 | ||
19 | // Set the proper SDK version before including boost/Asio | ||
20 | #include <SDKDDKVer.h> | ||
21 | // Note boost/ASIO includes Windows.h. | ||
22 | #include <boost/asio.hpp> | ||
23 | #endif //_WIN32 | ||
24 | |||
25 | #include <limits> | ||
26 | #include <memory> | ||
27 | #include <unordered_set> | ||
28 | #include <map> | ||
29 | |||
30 | #include "Navigation/Time/InsTime.hpp" | ||
31 | #include "NodeData/NodeData.hpp" | ||
32 | #include "internal/Node/Node.hpp" | ||
33 | #include "internal/gui/widgets/DynamicInputPins.hpp" | ||
34 | |||
35 | #include "Navigation/Math/PolynomialRegressor.hpp" | ||
36 | |||
37 | #include "NodeData/GNSS/GnssCombination.hpp" | ||
38 | #include "NodeData/GNSS/GnssObs.hpp" | ||
39 | #include "NodeData/GNSS/RtklibPosObs.hpp" | ||
40 | #include "NodeData/GNSS/SppSolution.hpp" | ||
41 | #include "NodeData/IMU/ImuObs.hpp" | ||
42 | #include "NodeData/IMU/ImuObsSimulated.hpp" | ||
43 | #include "NodeData/IMU/ImuObsWDelta.hpp" | ||
44 | #include "NodeData/IMU/KvhObs.hpp" | ||
45 | #include "NodeData/IMU/VectorNavBinaryOutput.hpp" | ||
46 | #include "NodeData/State/InsGnssLCKFSolution.hpp" | ||
47 | #include "NodeData/State/InsGnssTCKFSolution.hpp" | ||
48 | #include "NodeData/State/PosVelAtt.hpp" | ||
49 | |||
50 | #include "util/Logger/CommonLog.hpp" | ||
51 | #include "util/Container/ScrollingBuffer.hpp" | ||
52 | #include "util/Container/Unordered_map.hpp" | ||
53 | |||
54 | namespace NAV | ||
55 | { | ||
56 | /// @brief Calculates differences between signals | ||
57 | class Combiner : public Node, public CommonLog | ||
58 | { | ||
59 | public: | ||
60 | /// @brief Default constructor | ||
61 | Combiner(); | ||
62 | /// @brief Destructor | ||
63 | ~Combiner() override; | ||
64 | /// @brief Copy constructor | ||
65 | Combiner(const Combiner&) = delete; | ||
66 | /// @brief Move constructor | ||
67 | Combiner(Combiner&&) = delete; | ||
68 | /// @brief Copy assignment operator | ||
69 | Combiner& operator=(const Combiner&) = delete; | ||
70 | /// @brief Move assignment operator | ||
71 | Combiner& operator=(Combiner&&) = delete; | ||
72 | |||
73 | /// @brief String representation of the Class Type | ||
74 | [[nodiscard]] static std::string typeStatic(); | ||
75 | |||
76 | /// @brief String representation of the Class Type | ||
77 | [[nodiscard]] std::string type() const override; | ||
78 | |||
79 | /// @brief String representation of the Class Category | ||
80 | [[nodiscard]] static std::string category(); | ||
81 | |||
82 | /// @brief ImGui config window which is shown on double click | ||
83 | /// @attention Don't forget to set _hasConfig to true in the constructor of the node | ||
84 | void guiConfig() override; | ||
85 | |||
86 | /// @brief Saves the node into a json object | ||
87 | [[nodiscard]] json save() const override; | ||
88 | |||
89 | /// @brief Restores the node from a json object | ||
90 | /// @param[in] j Json object with the node state | ||
91 | void restore(const json& j) override; | ||
92 | |||
93 | private: | ||
94 | constexpr static size_t OUTPUT_PORT_INDEX_DYN_DATA = 0; ///< @brief Flow (DynamicData) | ||
95 | |||
96 | /// Possible data identifiers to connect | ||
97 | static inline std::vector<std::string> _dataIdentifier = { Pos::type(), | ||
98 | PosVel::type(), | ||
99 | PosVelAtt::type(), | ||
100 | InsGnssLCKFSolution::type(), | ||
101 | InsGnssTCKFSolution::type(), | ||
102 | GnssCombination::type(), | ||
103 | GnssObs::type(), | ||
104 | SppSolution::type(), | ||
105 | RtklibPosObs::type(), | ||
106 | ImuObs::type(), | ||
107 | ImuObsWDelta::type(), | ||
108 | ImuObsSimulated::type(), | ||
109 | KvhObs::type(), | ||
110 | VectorNavBinaryOutput::type() }; | ||
111 | |||
112 | /// Combination of data | ||
113 | struct Combination | ||
114 | { | ||
115 | /// Term of a combination equation | ||
116 | struct Term | ||
117 | { | ||
118 | double factor = 1.0; ///< Factor to multiply the term with | ||
119 | size_t pinIndex = 0; ///< Pin Index | ||
120 | std::variant<size_t, std::string> dataSelection = size_t(0); ///< Data Index or Data identifier | ||
121 | |||
122 | PolynomialRegressor<double> polyReg{ 1, 2 }; ///< Polynomial Regressor to interpolate data | ||
123 | ScrollingBuffer<std::shared_ptr<const NodeData>> rawData{ 2 }; ///< Last raw data to add if we send | ||
124 | |||
125 | /// @brief Get a string description of the combination | ||
126 | /// @param node Combiner node pointer | ||
127 | /// @param descriptors Data descriptors | ||
128 | 581328 | [[nodiscard]] std::string description(const Combiner* node, const std::vector<std::string>& descriptors) const | |
129 | { | ||
130 |
3/6✓ Branch 1 taken 581328 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 581328 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 581328 times.
✗ Branch 8 not taken.
|
581328 | if (std::holds_alternative<size_t>(dataSelection) && std::get<size_t>(dataSelection) < descriptors.size()) |
131 | { | ||
132 |
7/14✓ Branch 1 taken 290664 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 290664 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 290664 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 290664 times.
✓ Branch 10 taken 290664 times.
✓ Branch 11 taken 290664 times.
✓ Branch 12 taken 290664 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
|
2034648 | return fmt::format("{} {} ({})", factor == 1.0 ? "+" : (factor == -1.0 ? "-" : fmt::format("{:.2f}", factor)), |
133 |
2/2✓ Branch 3 taken 290664 times.
✓ Branch 4 taken 290664 times.
|
1743984 | descriptors.at(std::get<size_t>(dataSelection)), node->inputPins.at(pinIndex).name); |
134 | } | ||
135 | ✗ | if (std::holds_alternative<std::string>(dataSelection)) | |
136 | { | ||
137 | ✗ | return fmt::format("{} {} ({})", factor == 1.0 ? "+" : (factor == -1.0 ? "-" : fmt::format("{:.2f}", factor)), | |
138 | ✗ | std::get<std::string>(dataSelection), node->inputPins.at(pinIndex).name); | |
139 | } | ||
140 | ✗ | return fmt::format("N/A ({})", node->inputPins.at(pinIndex).name); | |
141 | } | ||
142 | }; | ||
143 | |||
144 | /// List of terms making up the combination | ||
145 | std::vector<Term> terms{ Term{ .factor = +1.0, .pinIndex = 0 }, | ||
146 | Term{ .factor = -1.0, .pinIndex = 1 } }; | ||
147 | |||
148 | /// @brief Get a string description of the combination | ||
149 | /// @param node Combiner node pointer | ||
150 | 145332 | [[nodiscard]] std::string description(const Combiner* node) const | |
151 | { | ||
152 | 145332 | std::string desc; | |
153 |
2/2✓ Branch 5 taken 290664 times.
✓ Branch 6 taken 145332 times.
|
435996 | for (const auto& term : terms) |
154 | { | ||
155 |
1/2✓ Branch 1 taken 290664 times.
✗ Branch 2 not taken.
|
290664 | auto descriptors = node->getDataDescriptors(term.pinIndex); |
156 |
1/2✓ Branch 1 taken 290664 times.
✗ Branch 2 not taken.
|
290664 | auto termDescription = term.description(node, descriptors); |
157 | |||
158 |
2/2✓ Branch 1 taken 145332 times.
✓ Branch 2 taken 145332 times.
|
290664 | if (!desc.empty()) |
159 | { | ||
160 |
3/6✓ Branch 1 taken 145332 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 145332 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 145332 times.
✗ Branch 7 not taken.
|
145332 | if (termDescription.starts_with("+ ") || termDescription.starts_with("- ")) |
161 | { | ||
162 |
1/2✓ Branch 1 taken 145332 times.
✗ Branch 2 not taken.
|
145332 | desc += " "; |
163 | } | ||
164 | else | ||
165 | { | ||
166 | ✗ | desc += " + "; | |
167 | } | ||
168 | } | ||
169 |
1/2✓ Branch 1 taken 290664 times.
✗ Branch 2 not taken.
|
290664 | desc += termDescription; |
170 | 290664 | } | |
171 | |||
172 | 145332 | return desc; | |
173 | ✗ | } | |
174 | }; | ||
175 | |||
176 | /// Combinations to calculate | ||
177 | std::vector<Combination> _combinations{ Combination() }; | ||
178 | |||
179 | /// Pin data | ||
180 | struct PinData | ||
181 | { | ||
182 | /// Time of the last observation processed | ||
183 | InsTime lastTime; | ||
184 | /// Min time between messages | ||
185 | double minTimeStep = std::numeric_limits<double>::infinity(); | ||
186 | /// Extra data descriptors for dynamic data | ||
187 | std::vector<std::string> dynDataDescriptors; | ||
188 | }; | ||
189 | |||
190 | /// Data per pin | ||
191 | std::vector<PinData> _pinData; | ||
192 | |||
193 | /// Reference pin | ||
194 | size_t _refPinIdx = 0; | ||
195 | |||
196 | /// Output missing combinations with NaN instead of removing | ||
197 | bool _outputMissingAsNaN = false; | ||
198 | |||
199 | bool _noOutputIfTimeDiffLarge = true; ///< Wether to not output a term if the interpolation time point is too far away | ||
200 | double _maxTimeDiffMultiplierFrequency = 2.1; ///< Multiply frequency with this to get maximum allowed time difference to interpolate to | ||
201 | |||
202 | bool _noOutputIfTimeStepLarge = true; ///< Wether to not output a term if the time step to interpolate in between is large | ||
203 | double _maxTimeStepMultiplierFrequency = 5.0; ///< Multiply frequency with this to get maximum allowed time step of incoming observations | ||
204 | |||
205 | /// Send request information | ||
206 | struct SendRequest | ||
207 | { | ||
208 | size_t combIndex = 0; ///< Combination Index | ||
209 | std::unordered_set<size_t> termIndices; ///< Term indices, which are already calculated | ||
210 | double result = 0.0; ///< Calculation result | ||
211 | std::vector<std::pair<std::string, std::shared_ptr<const NodeData>>> rawData; ///< List of the raw data of all terms contributing to the result | ||
212 | }; | ||
213 | |||
214 | /// Chronological list of send request | ||
215 | std::map<InsTime, std::vector<SendRequest>> _sendRequests; | ||
216 | |||
217 | /// @brief Function to call to add a new pin | ||
218 | /// @param[in, out] node Pointer to this node | ||
219 | static void pinAddCallback(Node* node); | ||
220 | /// @brief Function to call to delete a pin | ||
221 | /// @param[in, out] node Pointer to this node | ||
222 | /// @param[in] pinIdx Input pin index to delete | ||
223 | static void pinDeleteCallback(Node* node, size_t pinIdx); | ||
224 | |||
225 | /// @brief Dynamic input pins | ||
226 | /// @attention This should always be the last variable in the header, because it accesses others through the function callbacks | ||
227 | gui::widgets::DynamicInputPins _dynamicInputPins{ 0, this, pinAddCallback, pinDeleteCallback }; | ||
228 | |||
229 | /// @brief Initialize the node | ||
230 | bool initialize() override; | ||
231 | |||
232 | /// @brief Deinitialize the node | ||
233 | void deinitialize() override; | ||
234 | |||
235 | /// @brief Returns a list of descriptors for the pin | ||
236 | /// @param pinIndex Pin Index to look for the descriptor | ||
237 | [[nodiscard]] std::vector<std::string> getDataDescriptors(size_t pinIndex) const; | ||
238 | |||
239 | /// @brief Receive Data Function | ||
240 | /// @param[in] queue Queue with all the received data messages | ||
241 | /// @param[in] pinIdx Index of the pin the data is received on | ||
242 | void receiveData(InputPin::NodeDataQueue& queue, size_t pinIdx); | ||
243 | |||
244 | /// @brief Write info to a json object | ||
245 | /// @param[out] j Json output | ||
246 | /// @param[in] data Object to read info from | ||
247 | friend void to_json(json& j, const Combination& data); | ||
248 | /// @brief Read info from a json object | ||
249 | /// @param[in] j Json variable to read info from | ||
250 | /// @param[out] data Output object | ||
251 | friend void from_json(const json& j, Combination& data); | ||
252 | /// @brief Write info to a json object | ||
253 | /// @param[out] j Json output | ||
254 | /// @param[in] data Object to read info from | ||
255 | friend void to_json(json& j, const Combination::Term& data); | ||
256 | /// @brief Read info from a json object | ||
257 | /// @param[in] j Json variable to read info from | ||
258 | /// @param[out] data Output object | ||
259 | friend void from_json(const json& j, Combination::Term& data); | ||
260 | }; | ||
261 | |||
262 | } // namespace NAV | ||
263 |