14#include <fmt/ranges.h>
56 return "WiFiPositioning";
66 return "Data Processor";
75 if (ImGui::Button(fmt::format(
"Add Input Pin##{}",
size_t(
id)).c_str()))
83 if (ImGui::Button(fmt::format(
"Delete Input Pin##{}",
size_t(
id)).c_str()))
97 if (
auto frame =
static_cast<int>(
_frame);
98 ImGui::Combo(fmt::format(
"Frame##{}",
size_t(
id)).c_str(), &frame,
"ECEF\0LLA\0\0"))
115 devPos.x() =
rad2deg(devPos.x());
116 devPos.y() =
rad2deg(devPos.y());
123 if (ImGui::BeginTable(
"AccessPointInput", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
126 ImGui::TableSetupColumn(
"MAC address", ImGuiTableColumnFlags_WidthFixed, columnWidth);
129 ImGui::TableSetupColumn(
"X", ImGuiTableColumnFlags_WidthFixed, columnWidth);
130 ImGui::TableSetupColumn(
"Y", ImGuiTableColumnFlags_WidthFixed, columnWidth);
131 ImGui::TableSetupColumn(
"Z", ImGuiTableColumnFlags_WidthFixed, columnWidth);
135 ImGui::TableSetupColumn(
"Latitude", ImGuiTableColumnFlags_WidthFixed, columnWidth);
136 ImGui::TableSetupColumn(
"Longitude", ImGuiTableColumnFlags_WidthFixed, columnWidth);
137 ImGui::TableSetupColumn(
"Altitude", ImGuiTableColumnFlags_WidthFixed, columnWidth);
139 ImGui::TableSetupColumn(
"Bias", ImGuiTableColumnFlags_WidthFixed, columnWidth);
140 ImGui::TableSetupColumn(
"Scale", ImGuiTableColumnFlags_WidthFixed, columnWidth);
143 ImGui::TableHeadersRow();
145 for (
size_t rowIndex = 0; rowIndex <
_numOfDevices; rowIndex++)
147 ImGui::TableNextRow();
148 ImGui::TableNextColumn();
152 std::regex macRegex(
"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$");
154 ImGui::SetNextItemWidth(columnWidth);
155 if (ImGui::InputText(fmt::format(
"##Mac{}", rowIndex).c_str(), &
_deviceMacAddresses.at(rowIndex), ImGuiInputTextFlags_None))
170 ImGui::TableNextColumn();
171 ImGui::SetNextItemWidth(columnWidth);
172 if (ImGui::InputDouble(fmt::format(
"##InputX{}", rowIndex).c_str(), &devPosRow[0], 0.0, 0.0,
"%.4fm"))
176 ImGui::TableNextColumn();
177 ImGui::SetNextItemWidth(columnWidth);
178 if (ImGui::InputDouble(fmt::format(
"##InputY{}", rowIndex).c_str(), &devPosRow[1], 0.0, 0.0,
"%.4fm"))
182 ImGui::TableNextColumn();
183 ImGui::SetNextItemWidth(columnWidth);
184 if (ImGui::InputDouble(fmt::format(
"##InputZ{}", rowIndex).c_str(), &devPosRow[2], 0.0, 0.0,
"%.4fm"))
191 ImGui::TableNextColumn();
192 ImGui::SetNextItemWidth(columnWidth);
193 if (
ImGui::InputDoubleL(fmt::format(
"##InputLat{}", rowIndex).c_str(), &devPosRow[0], -180, 180, 0.0, 0.0,
"%.8f°"))
197 ImGui::TableNextColumn();
198 ImGui::SetNextItemWidth(columnWidth);
199 if (
ImGui::InputDoubleL(fmt::format(
"##InputLon{}", rowIndex).c_str(), &devPosRow[1], -180, 180, 0.0, 0.0,
"%.8f°"))
203 ImGui::TableNextColumn();
204 ImGui::SetNextItemWidth(columnWidth);
205 if (ImGui::InputDouble(fmt::format(
"##InputHeight{}", rowIndex).c_str(), &devPosRow[2], 0.0, 0.0,
"%.4fm"))
210 ImGui::TableNextColumn();
211 ImGui::SetNextItemWidth(columnWidth);
212 if (ImGui::InputDouble(fmt::format(
"##InputBias{}", rowIndex).c_str(), &
_deviceBias.at(rowIndex), 0.0, 0.0,
"%.4fm"))
216 ImGui::TableNextColumn();
217 ImGui::SetNextItemWidth(columnWidth);
218 if (ImGui::InputDouble(fmt::format(
"##InputScale{}", rowIndex).c_str(), &
_deviceScale.at(rowIndex), 0.0, 0.0,
"%.4f"))
225 if (ImGui::Button(fmt::format(
"Add Device##{}",
size_t(
id)).c_str(), ImVec2(columnWidth * 2.1F, 0)))
235 if (ImGui::Button(fmt::format(
"Delete Device##{}",
size_t(
id)).c_str(), ImVec2(columnWidth * 2.1F, 0)))
249 ImGui::Combo(fmt::format(
"Solution##{}",
size_t(
id)).c_str(), &solutionMode,
"Least squares\0Kalman Filter\0\0"))
269 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
270 if (ImGui::TreeNode(fmt::format(
"x0 - Initial State##{}",
size_t(
id)).c_str()))
273 llaPos.block<2, 1>(0, 0) =
rad2deg(llaPos.block<2, 1>(0, 0));
275 ImGui::SetNextItemWidth(configWidth);
279 _initialState.e_position.data(),
"%.4f", ImGuiInputTextFlags_CharsScientific))
285 ImGui::SetNextItemWidth(configWidth);
289 llaPos.data(),
"%.8f", ImGuiInputTextFlags_CharsScientific))
291 llaPos.block<2, 1>(0, 0) =
deg2rad(llaPos.block<2, 1>(0, 0));
299 ImGui::SetNextItemWidth(configWidth);
300 if (ImGui::InputDouble(fmt::format(
"Bias (m)##{}",
"m",
303 &
_initialState.bias, 0, 0,
"%.3e", ImGuiInputTextFlags_CharsScientific))
322 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
323 if (ImGui::TreeNode(fmt::format(
"R - Measurement Noise ##{}",
size_t(
id)).c_str()))
328 :
"Standard deviation σ",
333 0, 0,
"%.3e", ImGuiInputTextFlags_CharsScientific))
345 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
346 if (ImGui::TreeNode(fmt::format(
"Q - Process Noise ##{}",
size_t(
id)).c_str()))
351 :
"Standard deviation σ",
356 0, 0,
"%.3e", ImGuiInputTextFlags_CharsScientific))
368 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
369 if (ImGui::TreeNode(fmt::format(
"x0 - Initial State##{}",
size_t(
id)).c_str()))
372 llaPos.block<2, 1>(0, 0) =
rad2deg(llaPos.block<2, 1>(0, 0));
374 ImGui::SetNextItemWidth(configWidth);
378 _initialState.e_position.data(),
"%.4f", ImGuiInputTextFlags_CharsScientific))
384 ImGui::SetNextItemWidth(configWidth);
388 llaPos.data(),
"%.8f°", ImGuiInputTextFlags_CharsScientific))
390 llaPos.block<2, 1>(0, 0) =
deg2rad(llaPos.block<2, 1>(0, 0));
396 ImGui::SetNextItemWidth(configWidth);
400 _state.e_velocity.data(),
"%.3e", ImGuiInputTextFlags_CharsScientific))
408 ImGui::SetNextItemWidth(configWidth);
409 if (ImGui::InputDouble(fmt::format(
"Bias (m)##{}",
"m",
412 &
_state.bias, 0, 0,
"%.3e", ImGuiInputTextFlags_CharsScientific))
426 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
427 if (ImGui::TreeNode(fmt::format(
"P - Error covariance matrix (init)##{}",
size_t(
id)).c_str()))
432 :
"Standard deviation σ",
437 "%.2e", ImGuiInputTextFlags_CharsScientific))
447 :
"Standard deviation σ",
452 "%.2e", ImGuiInputTextFlags_CharsScientific))
464 :
"Standard deviation σ",
469 0, 0,
"%.2e", ImGuiInputTextFlags_CharsScientific))
484 if (ImGui::Checkbox(fmt::format(
"Estimate Bias##{}",
size_t(
id)).c_str(), &
_estimateBias))
501 if (ImGui::Checkbox(fmt::format(
"Weighted Solution##{}",
size_t(
id)).c_str(), &
_weightedSolution))
519 if (ImGui::Checkbox(fmt::format(
"Use Initial Values##{}",
size_t(
id)).c_str(), &
_useInitialValues))
553 j[
"e_position"] =
_state.e_position;
554 j[
"e_velocity"] =
_state.e_velocity;
577 if (j.contains(
"nWifiInputPins"))
582 if (j.contains(
"numStates"))
586 if (j.contains(
"numMeasurements"))
590 if (j.contains(
"frame"))
592 j.at(
"frame").get_to(
_frame);
594 if (j.contains(
"estimateBias"))
598 if (j.contains(
"weightedSolution"))
602 if (j.contains(
"useInitialValues"))
606 if (j.contains(
"deviceMacAddresses"))
610 if (j.contains(
"devicePositions"))
614 if (j.contains(
"deviceBias"))
618 if (j.contains(
"deviceScale"))
622 if (j.contains(
"numOfDevices"))
626 if (j.contains(
"solutionMode"))
630 if (j.contains(
"e_position"))
632 j.at(
"e_position").get_to(
_state.e_position);
634 if (j.contains(
"e_velocity"))
636 j.at(
"e_velocity").get_to(
_state.e_velocity);
638 if (j.contains(
"bias"))
640 j.at(
"bias").get_to(
_state.bias);
642 if (j.contains(
"intialStatePosition"))
644 j.at(
"intialStatePosition").get_to(
_initialState.e_position);
646 if (j.contains(
"initialStateVelocity"))
648 j.at(
"initialStateVelocity").get_to(
_initialState.e_velocity);
650 if (j.contains(
"initialStateBias"))
654 if (j.contains(
"initCovariancePosition"))
658 if (j.contains(
"initCovariancePositionUnit"))
662 if (j.contains(
"initCovarianceVelocity"))
666 if (j.contains(
"initCovarianceVelocityUnit"))
670 if (j.contains(
"initCovarianceBias"))
674 if (j.contains(
"initCovarianceBiasUnit"))
678 if (j.contains(
"measurementNoise"))
682 if (j.contains(
"measurementNoiseUnit"))
686 if (j.contains(
"processNoise"))
690 if (j.contains(
"processNoiseUnit"))
711 Eigen::Vector3d variance_vel = Eigen::Vector3d::Zero();
722 Eigen::Vector3d variance_pos = Eigen::Vector3d::Zero();
733 double variance_bias = 0.0;
744 _kalmanFilter.P.diagonal() << variance_pos, variance_vel, variance_bias;
767 LOG_DEBUG(
"WiFiPositioning initialized");
791 auto obs = std::static_pointer_cast<const WiFiObs>(queue.
extract_front());
799 bool deviceExists =
false;
808 device.distanceStd = obs->distanceStd *
_deviceScale.at(index);
809 device.time = obs->insTime;
816 ecefPos.block<2, 1>(0, 0) =
deg2rad(ecefPos.block<2, 1>(0, 0));
818 if (device.position == ecefPos)
822 device.distanceStd = obs->distanceStd *
_deviceScale.at(index);
823 device.time = obs->insTime;
835 llaPos.block<2, 1>(0, 0) =
deg2rad(llaPos.block<2, 1>(0, 0));
845 auto wifiPositioningSolution = std::make_shared<NAV::WiFiPositioningSolution>();
846 wifiPositioningSolution->insTime = obs->insTime;
853 wifiPositioningSolution->setPositionAndCov_e(
lsqSolution.solution.block<3, 1>(0, 0),
855 if (wifiPositioningSolution->lla_position().hasNaN())
857 LOG_WARN(
"{}: WiFi LSQ calculation failed, invalid position",
nameId());
862 wifiPositioningSolution->bias =
_state.bias;
863 wifiPositioningSolution->biasStdev =
lsqSolution.variance(3, 3);
872 wifiPositioningSolution->setPosVelAndCov_e(
_kalmanFilter.x.block<3, 1>(0, 0),
875 if (wifiPositioningSolution->lla_position().hasNaN())
877 LOG_WARN(
"{}: WiFi KF calculation failed, invalid position",
nameId());
888 LOG_DATA(
"{}: [{}] Received distance to device {} at position {} with distance {}",
nameId(), obs->insTime.toYMDHMS(
GPST),
907 Eigen::MatrixXd e_H = Eigen::MatrixXd::Zero(
static_cast<int>(
_devices.size()), n);
908 Eigen::MatrixXd W = Eigen::MatrixXd::Identity(
static_cast<int>(
_devices.size()),
static_cast<int>(
_devices.size()));
909 Eigen::VectorXd ddist = Eigen::VectorXd::Zero(
static_cast<int>(
_devices.size()));
910 size_t numMeasurements =
_devices.size();
923 for (
size_t o = 0; o < 15; o++)
926 for (
size_t i = 0; i < numMeasurements; i++)
929 double distEst = (
_devices.at(i).position -
_state.e_position).norm();
936 ddist(
static_cast<int>(i)) =
_devices.at(i).distance - distEst;
938 Eigen::Vector3d e_lineOfSightUnitVector = Eigen::Vector3d::Zero();
945 e_H.block<1, 3>(
static_cast<int>(i), 0) = -e_lineOfSightUnitVector;
948 e_H(
static_cast<int>(i), 3) = 1;
954 W(
static_cast<int>(i),
static_cast<int>(i)) = 1 / std::pow(
_devices.at(i).distanceStd, 2);
980 bool solInaccurate = pow(lsq.
solution.norm(), 2) > 1e-3;
989 lsq.
solution.setConstant(std::numeric_limits<double>::quiet_NaN());
990 lsq.
variance.setConstant(std::numeric_limits<double>::quiet_NaN());
993 _state.bias = std::numeric_limits<double>::quiet_NaN();
1030 _kalmanFilter.Q.block(0, 0, 3, 3) = std::pow(tau_i, 3) / 3.0 * Eigen::Matrix3d::Identity();
1031 _kalmanFilter.Q.block(3, 0, 3, 3) = std::pow(tau_i, 2) / 2.0 * Eigen::Matrix3d::Identity();
1032 _kalmanFilter.Q.block(0, 3, 3, 3) = std::pow(tau_i, 2) / 2.0 * Eigen::Matrix3d::Identity();
1033 _kalmanFilter.Q.block(3, 3, 3, 3) = tau_i * Eigen::Matrix3d::Identity();
1067 Eigen::MatrixXd H = Eigen::MatrixXd::Zero(1,
_numStates);
Transformation collection.
nlohmann::json json
json namespace
Text Help Marker (?) with Tooltip.
Utility class for logging to console and file.
#define LOG_DEBUG
Debug information. Should not be called on functions which receive observations (spamming)
#define LOG_DATA
All output which occurs repeatedly every time observations are received.
#define LOG_WARN
Error occurred, but a fallback option exists and program continues to work normally.
#define LOG_TRACE
Detailled info to trace the execution of the program. Should not be called on functions which receive...
Vector Utility functions.
Espressif Observation Class.
WiFi Positioning Algorithm output.
Generalized Kalman Filter class.
ImVec2 _guiConfigDefaultWindowSize
Node(std::string name)
Constructor.
std::vector< InputPin > inputPins
List of input pins.
OutputPin * CreateOutputPin(const char *name, Pin::Type pinType, const std::vector< std::string > &dataIdentifier, OutputPin::PinData data=static_cast< void * >(nullptr), int idx=-1)
Create an Output Pin object.
std::string nameId() const
Node name and id.
std::string name
Name of the Node.
InputPin * CreateInputPin(const char *name, Pin::Type pinType, const std::vector< std::string > &dataIdentifier={}, InputPin::Callback callback=static_cast< InputPin::FlowFirableCallbackFunc >(nullptr), InputPin::FlowFirableCheckFunc firable=nullptr, int priority=0, int idx=-1)
Create an Input Pin object.
void invokeCallbacks(size_t portIndex, const std::shared_ptr< const NodeData > &data)
Calls all registered callbacks on the specified output port.
bool DeleteInputPin(size_t pinIndex)
Deletes the input pin. Invalidates the pin reference given.
bool _hasConfig
Flag if the config window should be shown.
auto extract_front()
Returns a copy of the first element in the container and removes it from the container.
static std::string type()
Returns the type of the data class.
static std::string type()
Returns the type of the data class.
std::vector< double > _deviceBias
Input of biases.
uint8_t _numStates
Number of states.
double _measurementNoise
GUI selection of the process noise (standard deviation σ or Variance σ²)
KalmanFilter _kalmanFilter
Kalman Filter representation - States: 3xVel, 3xPos, (1xBias) - Measurements: 1xDist.
std::vector< Device > _devices
Devices which are used for the positioning.
Frame _frame
Frame to calculate the position in.
size_t _nWifiInputPins
Amount of wifi input pins.
Eigen::Vector3d _initCovarianceVelocity
GUI selection of the initial covariance diagonal values for velocity (standard deviation σ or Varianc...
std::string type() const override
String representation of the Class Type.
void restore(const json &j) override
Restores the node from a json object.
uint8_t _numMeasurements
Number of measurements.
@ meter2
Variance NED [m^2, m^2, m^2].
@ meter
Standard deviation NED [m, m, m].
json save() const override
Saves the node into a json object.
@ LLA
Latitude-Longitude-Altitude frame.
@ ECEF
Earth-Centered Earth-Fixed frame.
@ m_s
Standard deviation [m/s].
@ m2_s2
Variance [m^2/s^2].
Eigen::Vector3d _initCovariancePosition
GUI selection of the initial covariance diagonal values for position (standard deviation σ or Varianc...
WiFiPositioning()
Default constructor.
void guiConfig() override
ImGui config window which is shown on double click.
@ meter2
Variance NED [m^2, m^2, m^2].
@ meter
Standard deviation NED [m, m, m].
std::vector< double > _deviceScale
Input of scales.
LeastSquaresResult< Eigen::VectorXd, Eigen::MatrixXd > lsqSolution()
Calculate the position using the least squares method.
std::vector< std::string > _deviceMacAddresses
Input of mac addresses.
InitCovarianceVelocityUnit _initCovarianceVelocityUnit
Gui selection for the Unit of the initial covariance for the velocity.
bool _estimateBias
Selection of whether the bias will be additionally estimated.
void deinitialize() override
Deinitialize the node.
double _processNoise
GUI selection of the process noise (standard deviation σ or Variance σ²)
void updateNumberOfInputPins()
Adds/Deletes Input Pins depending on the variable _nNavInfoPins.
bool initialize() override
Initialize the node.
SolutionMode _solutionMode
Solution Mode.
InsTime _lastPredictTime
Time when the last prediction was triggered.
@ meter
Standard deviation [m].
~WiFiPositioning() override
Destructor.
bool _useInitialValues
Selection of whether the initial values should always be used or those of the last position.
double _initCovarianceBias
GUI selection of the initial covariance diagonal values for bias (standard deviation σ or Variance σ²...
InitCovariancePositionUnit _initCovariancePositionUnit
Gui selection for the Unit of the initial covariance for the position.
static std::string category()
String representation of the Class Category.
State _initialState
Initial state.
static std::string typeStatic()
String representation of the Class Type.
bool _weightedSolution
Selection of whether the solution will be weighted.
@ meter2
Variance NED [m^2, m^2, m^2].
@ meter
Standard deviation NED [m, m, m].
void kfSolution()
Calculate the position.
ProcessNoiseUnit _processNoiseUnit
Gui selection for the Unit of the initial covariance for the position.
InitCovarianceBiasUnit _initCovarianceBiasUnit
Gui selection for the Unit of the initial covariance for the bias.
std::vector< Eigen::Vector3d > _devicePositions
Input of positions.
void recvWiFiObs(InputPin::NodeDataQueue &queue, size_t pinIdx)
Receive Function for the WiFi Observations.
State _state
State estimated by the algorithm.
static constexpr size_t OUTPUT_PORT_INDEX_WIFISOL
WiFiPositioningSolution.
MeasurementNoiseUnit _measurementNoiseUnit
Gui selection for the Unit of the initial covariance for the position.
size_t _numOfDevices
Number of devices.
static float windowFontRatio()
Ratio to multiply for GUI window elements.
bool InputDouble3(const char *label, double v[3], const char *format, ImGuiInputTextFlags flags)
Shows an InputText GUI element for an array of 'double[3]'.
bool InputDoubleL(const char *label, double *v, double v_min, double v_max, double step, double step_fast, const char *format, ImGuiInputTextFlags flags)
Shows a value limited InputText GUI element for 'double'.
void ApplyChanges()
Signals that there have been changes to the flow.
Eigen::Vector3< typename Derived::Scalar > ecef2lla_WGS84(const Eigen::MatrixBase< Derived > &e_position)
Converts Earth-centered-Earth-fixed coordinates into latitude, longitude and altitude using WGS84.
Eigen::Vector3< typename Derived::Scalar > lla2ecef_WGS84(const Eigen::MatrixBase< Derived > &lla_position)
Converts latitude, longitude and altitude into Earth-centered-Earth-fixed coordinates using WGS84.
Derived::PlainObject transitionMatrix_Phi_Taylor(const Eigen::MatrixBase< Derived > &F, double tau_s, size_t order)
Calculates the state transition matrix 𝚽 limited to specified order in 𝐅𝜏ₛ
KeyedLeastSquaresResult< Scalar, StateKeyType > solveWeightedLinearLeastSquaresUncertainties(const KeyedMatrixX< Scalar, MeasKeyType, StateKeyType > &H, const KeyedMatrixX< Scalar, MeasKeyType, MeasKeyType > &W, const KeyedVectorX< Scalar, MeasKeyType > &dz)
Finds the "weighted least squares" solution.
constexpr auto deg2rad(const T °)
Convert Degree to Radians.
constexpr auto rad2deg(const T &rad)
Convert Radians to Degree.
Eigen::Vector3< typename DerivedA::Scalar > e_calcLineOfSightUnitVector(const Eigen::MatrixBase< DerivedA > &e_posAnt, const Eigen::MatrixBase< DerivedB > &e_posSat)
Calculates the line-of-sight unit vector from the user antenna to the satellite.
Least Squares Uncertainties return value.
VarianceMatrix variance
Least squares variance.
SolutionVector solution
Least squares solution.