Skip to content

Commit 4f271e9

Browse files
author
Tobias Haeckel
committed
untestetd
1 parent f29b862 commit 4f271e9

6 files changed

Lines changed: 323 additions & 483 deletions

File tree

lib/APPLineFollowerSensorFusion/src/App.cpp

Lines changed: 108 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
* Includes
3434
*****************************************************************************/
3535
#include "App.h"
36+
#include <cmath>
3637
#include <Logging.h>
3738
#include <LogSinkPrinter.h>
3839
#include <Util.h>
@@ -106,8 +107,8 @@ static const uint32_t JSON_BIRTHMESSAGE_MAX_SIZE = 64U;
106107
/** Buffer size for JSON serialization of combined sensor snapshot. */
107108
static const uint32_t JSON_SENSOR_SNAPSHOT_MAX_SIZE = 256U;
108109

109-
/** Buffer size for JSON serialization of fusion pose. */
110-
static const uint32_t JSON_FUSION_POSE_MAX_SIZE = 128U;
110+
/** Buffer size for JSON serialization of fusion pose including nested NIS fields. */
111+
static const uint32_t JSON_FUSION_POSE_MAX_SIZE = 320U;
111112

112113
/** Status send interval in ms. */
113114
const uint16_t STATUS_SEND_INTERVAL_MS = 1000U;
@@ -116,11 +117,10 @@ const uint16_t STATUS_SEND_INTERVAL_MS = 1000U;
116117
const uint16_t HOST_TIMESYNC_INTERVAL_MS = 10000U;
117118

118119
/* Convenience aliases for EKF types. */
119-
using ImuMeasurementVector = ExtendedKalmanFilter5D::ImuMeasurementVector;
120-
using OdoMeasurementVector = ExtendedKalmanFilter5D::OdoMeasurementVector;
121-
using CamMeasurementVector = ExtendedKalmanFilter5D::CamMeasurementVector;
122-
using StateVector = ExtendedKalmanFilter5D::StateVector;
123-
using StateMatrix = ExtendedKalmanFilter5D::StateMatrix;
120+
using OdoMeasurementVector = ExtendedKalmanFilter4D::OdoMeasurementVector;
121+
using CamMeasurementVector = ExtendedKalmanFilter4D::CamMeasurementVector;
122+
using StateVector = ExtendedKalmanFilter4D::StateVector;
123+
using StateMatrix = ExtendedKalmanFilter4D::StateMatrix;
124124

125125
/******************************************************************************
126126
* Public Methods
@@ -129,6 +129,7 @@ using StateMatrix = ExtendedKalmanFilter5D::StateMatrix;
129129
App::App() :
130130
m_initialDataSent(false),
131131
m_statusTimer(),
132+
m_hostTimeSyncTimer(),
132133
m_serMuxChannelProvider(Board::getInstance().getRobot().getStream()),
133134
m_timeSync(m_serMuxChannelProvider),
134135
m_lineSensors(m_serMuxChannelProvider),
@@ -141,9 +142,6 @@ App::App() :
141142
m_hasVehicleData(false),
142143
m_lastSsrPose{},
143144
m_hasSsrPose(false),
144-
m_odoOriginInitialized(false),
145-
m_odoOriginX_mm(0.0F),
146-
m_odoOriginY_mm(0.0F),
147145
m_ekfInitializedFromSSR(false)
148146
{
149147
/* Inject dependencies into states. */
@@ -311,8 +309,8 @@ bool App::setupMqtt(const String& clientId, const String& brokerAddr, uint16_t b
311309
{
312310
LOG_FATAL("MQTT configuration could not be set.");
313311
}
314-
else if (false ==
315-
m_mqttClient.subscribe(ssrTopic, false, [this](const String& payload) { ssrTopicCallback(payload); }))
312+
else if (false == m_mqttClient.subscribe(ssrTopic, false, [this](const String& payload)
313+
{ ssrTopicCallback(payload); }))
316314
{
317315
LOG_FATAL("Could not subscribe to MQTT topic: %s.", TOPIC_NAME_RADAR_POSE);
318316
}
@@ -358,51 +356,37 @@ void App::ssrTopicCallback(const String& payload)
358356
const int ang_mrad_i = angle_mrad.as<int>();
359357
const uint64_t hostEpochMs = timestamp_ms.as<uint64_t>();
360358

361-
const bool hostSynced = m_timeSync.isHostSynced();
362-
const uint64_t ssrLocalTsMs =
363-
hostSynced ? m_timeSync.hostToEspLocalMs(hostEpochMs)
364-
: m_timeSync.localNowMs(); /* Fallback: use local time if host is not synced */
359+
const bool hostSynced = m_timeSync.isHostSynced();
360+
const uint64_t ssrLocalTsMs = hostSynced ? m_timeSync.hostToEspLocalMs(hostEpochMs)
361+
: m_timeSync.localNowMs();
365362

366363
LOG_INFO("SSR pose: ts_host_ms=%llu (hostSync=%s)", hostEpochMs, hostSynced ? "true" : "false");
367364

368365
SpaceShipRadarPose ssrPose;
369366
ssrPose.x = static_cast<float>(x_mm_i);
370367
ssrPose.y = static_cast<float>(y_mm_i);
371368
ssrPose.theta = static_cast<float>(ang_mrad_i);
372-
ssrPose.v_x = static_cast<float>(vx_mms_i);
373-
ssrPose.v_y = static_cast<float>(vy_mms_i);
374369
ssrPose.timestamp = static_cast<uint32_t>(ssrLocalTsMs);
375370

376-
if (false == m_odoOriginInitialized)
377-
{
378-
m_odoOriginX_mm = ssrPose.x;
379-
m_odoOriginY_mm = ssrPose.y;
380-
m_odoOriginInitialized = true;
381-
382-
LOG_INFO("Odometry origin set from SSR: x=%dmm y=%dmm", x_mm_i, y_mm_i);
383-
}
384-
385371
if (false == m_ekfInitializedFromSSR)
386372
{
373+
const float vxMms = static_cast<float>(vx_mms_i);
374+
const float vyMms = static_cast<float>(vy_mms_i);
387375
StateVector x0;
388376
x0.setZero();
389377

390378
x0(0) = ssrPose.x;
391379
x0(1) = ssrPose.y;
392380
x0(2) = ssrPose.theta;
393-
394-
const float v_mms = std::sqrt(ssrPose.v_x * ssrPose.v_x + ssrPose.v_y * ssrPose.v_y);
395-
x0(3) = v_mms;
396-
x0(4) = 0.0F;
381+
x0(3) = std::sqrt((vxMms * vxMms) + (vyMms * vyMms));
397382

398383
StateMatrix P0 = StateMatrix::Identity();
399384
P0(0, 0) = 50.0F * 50.0F;
400385
P0(1, 1) = 50.0F * 50.0F;
401386
P0(2, 2) = 200.0F * 200.0F;
402387
P0(3, 3) = 200.0F * 200.0F;
403-
P0(4, 4) = 200.0F * 200.0F;
404388

405-
(void)m_ekf.init(x0, P0);
389+
m_ekf.init(x0, P0);
406390
m_lastEkfUpdateMs = ssrPose.timestamp;
407391
m_ekfInitializedFromSSR = true;
408392

@@ -466,116 +450,120 @@ void App::publishVehicleAndSensorSnapshot(const VehicleData& data)
466450

467451
void App::filterLocationData(const VehicleData& vehicleData, const SpaceShipRadarPose& ssrPose)
468452
{
469-
/* Local variables. */
470453
uint32_t zumoTs32 = 0U;
471454
uint32_t zumoLocalMs32 = 0U;
472455
uint32_t ssrLocalMs32 = 0U;
473456
uint32_t newestLocalTs = 0U;
474457
uint32_t dtMs = 0U;
475458
float dt = 0.0F;
476-
float a_x = 0.0F;
477459
Source newestSource = Source::None;
478-
bool ekfReady = false;
479460
bool hasTimestamp = false;
480-
bool doProcessing = false;
481461

482462
/* Do not run fusion until EKF has been initialized from SSR. */
483-
ekfReady = m_ekfInitializedFromSSR;
484-
485-
if (ekfReady)
463+
if (false == m_ekfInitializedFromSSR)
486464
{
487-
doProcessing = true;
465+
return;
488466
}
489467

490-
if (doProcessing)
468+
/* Timestamp conversion. */
469+
zumoTs32 = static_cast<uint32_t>(vehicleData.timestamp);
470+
zumoLocalMs32 = static_cast<uint32_t>(m_timeSync.mapZumoToLocalMs(zumoTs32));
471+
ssrLocalMs32 = static_cast<uint32_t>(ssrPose.timestamp);
472+
473+
LOG_INFO("Filtering location data: Zumo=%u ms, SSR=%u ms", zumoLocalMs32, ssrLocalMs32);
474+
475+
/* Initialize EKF time on first data. */
476+
hasTimestamp = initializeEkfTimestamp(zumoLocalMs32, ssrLocalMs32);
477+
478+
if (false == hasTimestamp)
491479
{
492-
/* Timestamp conversion. */
493-
zumoTs32 = static_cast<uint32_t>(vehicleData.timestamp);
494-
zumoLocalMs32 = static_cast<uint32_t>(m_timeSync.mapZumoToLocalMs(zumoTs32));
495-
ssrLocalMs32 = static_cast<uint32_t>(ssrPose.timestamp);
480+
return;
481+
}
496482

497-
LOG_INFO("Filtering location data: Zumo=%u ms, SSR=%u ms", zumoLocalMs32, ssrLocalMs32);
483+
newestLocalTs = m_lastEkfUpdateMs;
498484

499-
/* Initialize EKF time on first data. */
500-
hasTimestamp = initializeEkfTimestamp(zumoLocalMs32, ssrLocalMs32);
485+
/* Determine which sensor has the newest update. */
486+
newestSource = determineNewestSource(zumoLocalMs32, ssrLocalMs32, m_lastEkfUpdateMs, newestLocalTs);
501487

502-
if (hasTimestamp)
503-
{
504-
newestLocalTs = m_lastEkfUpdateMs;
505-
506-
/* Determine which sensor has the newest update. */
507-
newestSource = determineNewestSource(zumoLocalMs32, ssrLocalMs32, m_lastEkfUpdateMs, newestLocalTs);
508-
509-
if (newestSource != Source::None)
510-
{
511-
/* Time delta for prediction step. */
512-
dtMs = newestLocalTs - m_lastEkfUpdateMs;
513-
dt = static_cast<float>(dtMs) / 1000.0F;
514-
515-
/* Longitudinal acceleration input. */
516-
a_x = static_cast<float>(vehicleData.accelerationX);
517-
518-
/* EKF prediction. */
519-
m_ekf.predict(a_x, dt);
520-
521-
/* EKF correction step based on sensor source. */
522-
if (newestSource == Source::Vehicle)
523-
{
524-
updateFromVehicle(vehicleData);
525-
}
526-
else if (newestSource == Source::SSR)
527-
{
528-
updateFromSsr(ssrPose);
529-
}
530-
else if (newestSource == Source::VehicleAndSSR)
531-
{
532-
updateFromVehicle(vehicleData);
533-
updateFromSsr(ssrPose);
534-
}
535-
536-
/* Update last EKF timestamp. */
537-
m_lastEkfUpdateMs = newestLocalTs;
538-
539-
/* Publish fused pose. */
540-
publishFusionPose(newestLocalTs);
541-
}
542-
}
488+
if (newestSource == Source::None)
489+
{
490+
return;
543491
}
544-
}
545492

546-
void App::transformOdometryToGlobal(const VehicleData& vehicleData, float& xGlob_mm, float& yGlob_mm,
547-
float& thetaGlob_mrad) const
548-
{
549-
/* Y axis and heading sign differ between local odometry frame and SSR frame. */
550-
constexpr float Y_SIGN = -1.0F;
551-
constexpr float THETA_SIGN = 1.0F;
493+
/* Time delta for prediction step. */
494+
dtMs = newestLocalTs - m_lastEkfUpdateMs;
495+
dt = static_cast<float>(dtMs) / 1000.0F;
496+
497+
/* Use the latest available gyro yaw rate as control input for the process model. */
498+
const float omegaMradPerSec =
499+
ExtendedKalmanFilter4D::gyroDigitsToMradPerSec(static_cast<int16_t>(vehicleData.turnRateZ));
552500

553-
const float xLocal_mm = static_cast<float>(vehicleData.xPos);
554-
const float yLocal_mm = static_cast<float>(vehicleData.yPos);
555-
const float thetaLocal_mrad = static_cast<float>(vehicleData.orientation);
501+
/* EKF prediction. */
502+
m_ekf.predict(omegaMradPerSec, dt);
556503

557-
const float originX = (true == m_odoOriginInitialized) ? m_odoOriginX_mm : 0.0F;
558-
const float originY = (true == m_odoOriginInitialized) ? m_odoOriginY_mm : 0.0F;
504+
/* EKF correction step based on sensor source. */
505+
if (newestSource == Source::Vehicle)
506+
{
507+
updateFromVehicle(vehicleData);
508+
}
509+
else if (newestSource == Source::SSR)
510+
{
511+
updateFromSsr(ssrPose);
512+
}
513+
else if (newestSource == Source::VehicleAndSSR)
514+
{
515+
updateFromVehicle(vehicleData);
516+
updateFromSsr(ssrPose);
517+
}
518+
519+
/* Update last EKF timestamp. */
520+
m_lastEkfUpdateMs = newestLocalTs;
559521

560-
xGlob_mm = originX + xLocal_mm;
561-
yGlob_mm = originY + (Y_SIGN * yLocal_mm);
562-
thetaGlob_mrad = THETA_SIGN * thetaLocal_mrad;
522+
/* Publish fused pose. */
523+
publishFusionPose(newestLocalTs);
563524
}
564525

565526
void App::publishFusionPose(uint32_t tsMs)
566527
{
567-
/* EKF state: [p_x, p_y, theta, v, omega]. */
568-
const StateVector& state = m_ekf.getState();
528+
const StateVector& state = m_ekf.getState();
529+
const ExtendedKalmanFilter4D::NisData& odometryNis = m_ekf.getLastOdometryNis();
530+
const ExtendedKalmanFilter4D::NisData& cameraNis = m_ekf.getLastCameraNis();
531+
const float omegaMradPerSec =
532+
(true == m_hasVehicleData)
533+
? ExtendedKalmanFilter4D::gyroDigitsToMradPerSec(static_cast<int16_t>(m_lastVehicleData.turnRateZ))
534+
: 0.0F;
569535

570536
JsonDocument payloadJson;
571537
char payloadArray[JSON_FUSION_POSE_MAX_SIZE];
572538

573-
payloadJson["ts_ms"] = static_cast<int64_t>(tsMs);
574-
payloadJson["x_mm"] = static_cast<int32_t>(state(0));
575-
payloadJson["y_mm"] = static_cast<int32_t>(state(1));
576-
payloadJson["theta_mrad"] = static_cast<int32_t>(state(2));
577-
payloadJson["v_mms"] = static_cast<int32_t>(state(3));
578-
payloadJson["omega_mradps"] = static_cast<int32_t>(state(4));
539+
payloadJson["ts_ms"] = static_cast<int64_t>(tsMs);
540+
payloadJson["x_mm"] = static_cast<int32_t>(state(0));
541+
payloadJson["y_mm"] = static_cast<int32_t>(state(1));
542+
payloadJson["theta_mrad"] = static_cast<int32_t>(state(2));
543+
payloadJson["v_mms"] = static_cast<int32_t>(state(3));
544+
545+
/* Keep the latest gyro control input in the payload for existing consumers. */
546+
payloadJson["omega_mradps"] = static_cast<int32_t>(omegaMradPerSec);
547+
548+
JsonObject nisObj = payloadJson["nis"].to<JsonObject>();
549+
550+
JsonObject camObj = nisObj["cam"].to<JsonObject>();
551+
camObj["sensor"] = "cam";
552+
camObj["valid"] = cameraNis.isValid;
553+
if (true == cameraNis.isValid)
554+
{
555+
camObj["value"] = cameraNis.value;
556+
camObj["ts_ms"] = static_cast<int64_t>(cameraNis.timestampMs);
557+
}
558+
559+
JsonObject odoObj = nisObj["odo"].to<JsonObject>();
560+
odoObj["sensor"] = "odo";
561+
odoObj["valid"] = odometryNis.isValid;
562+
if (true == odometryNis.isValid)
563+
{
564+
odoObj["value"] = odometryNis.value;
565+
odoObj["ts_ms"] = static_cast<int64_t>(odometryNis.timestampMs);
566+
}
579567

580568
(void)serializeJson(payloadJson, payloadArray);
581569
String payloadStr(payloadArray);
@@ -683,28 +671,16 @@ Source App::determineNewestSource(uint32_t zumoLocalMs32, uint32_t ssrLocalMs32,
683671

684672
void App::updateFromVehicle(const VehicleData& vehicleData)
685673
{
686-
const int16_t rawGyroZ = static_cast<int16_t>(vehicleData.turnRateZ);
687-
float xGlob_mm = 0.0F;
688-
float yGlob_mm = 0.0F;
689-
float thetaGlob_mrad = 0.0F;
690-
OdoMeasurementVector z_odo;
674+
const uint32_t zumoTs32 = static_cast<uint32_t>(vehicleData.timestamp);
675+
const uint32_t zumoLocalMs32 = static_cast<uint32_t>(m_timeSync.mapZumoToLocalMs(zumoTs32));
676+
OdoMeasurementVector z_odo;
691677

692678
LOG_INFO("EKF update from Vehicle.");
693679

694-
/* IMU update using yaw-rate only. */
695-
m_ekf.updateImuFromDigits(rawGyroZ);
696-
697-
/* Convert odometry into global coordinates. */
698-
transformOdometryToGlobal(vehicleData, xGlob_mm, yGlob_mm, thetaGlob_mrad);
699-
700-
/* Update odometry measurement:
701-
* z_odo(0) = v
702-
* z_odo(1) = theta
703-
*/
680+
/* Odometry measurement: z_odo(0) = v */
704681
z_odo(0) = static_cast<float>(vehicleData.center);
705-
z_odo(1) = thetaGlob_mrad;
706682

707-
m_ekf.updateOdometry(z_odo);
683+
m_ekf.updateOdometry(z_odo, zumoLocalMs32);
708684
}
709685

710686
void App::updateFromSsr(const SpaceShipRadarPose& ssrPose)
@@ -713,16 +689,12 @@ void App::updateFromSsr(const SpaceShipRadarPose& ssrPose)
713689

714690
LOG_INFO("EKF update from SSR.");
715691

716-
/* Camera measurement:
717-
* z_cam(0..4) = [x, y, theta, v_x, v_y]
718-
*/
692+
/* Camera measurement: z_cam(0..2) = [x, y, theta] */
719693
z_cam(0) = ssrPose.x;
720694
z_cam(1) = ssrPose.y;
721695
z_cam(2) = ssrPose.theta;
722-
z_cam(3) = ssrPose.v_x;
723-
z_cam(4) = ssrPose.v_y;
724696

725-
m_ekf.updateCamera(z_cam);
697+
m_ekf.updateCamera(z_cam, ssrPose.timestamp);
726698
}
727699

728700
/******************************************************************************

0 commit comments

Comments
 (0)