VOXL OpenVINS Server 1.0
Visual Inertial Odometry Server for VOXL Platform
Loading...
Searching...
No Matches
ImuMinimal.cpp
Go to the documentation of this file.
1/**
2 * @file ImuMinimal.cpp
3 * @brief IMU interface and data handling implementation for VOXL OpenVINS
4 * @author Zauberflote
5 * @date 2025
6 * @version 1.0
7 *
8 * This file implements the IMU interface and callback functions for handling
9 * inertial measurement unit data in the VOXL OpenVINS system. It provides
10 * the connection to the IMU service and data processing capabilities.
11 *
12 * The implementation provides:
13 * - IMU data reception and validation
14 * - Frame transformation for different IMU orientations
15 * - Batch processing for efficient VIO integration
16 * - Camera-IMU synchronization
17 * - VIO state management and publishing
18 * - Thread-safe operations with mutex protection
19 */
20
21#include "ImuMinimal.h"
22#include <cstdio>
23#include <state/State.h>
24
25namespace
26{
27 // THIS IS NOT CURRENTLY USED -- BUT IT IS HERE FOR FUTURE USE WITH MULTI-IMU SUPPORT
28 ////////////////////////////////////////////////////////////////////////////////////
29 constexpr int kIMUQueueSize = 512;
30 boost::lockfree::spsc_queue<imu_data_t, boost::lockfree::capacity<kIMUQueueSize>> imu_queue;
31 std::atomic<bool> imu_thread_running{false};
32 std::thread imu_thread;
33 ////////////////////////////////////////////////////////////////////////////////////
34
35 // CACHE SHARED PTR TO AVOID REPEATED ALLOCATION AT HIGH FREQUENCY
36 std::shared_ptr<ov_msckf::State> cached_state;
37 std::map<double, std::vector<std::shared_ptr<ov_core::Feature>>> cached_features_map;
38
39 // Rate estimation state
40 // NOT USED FOR NOW -- LIKELY IT WONT BE NECESSARY AT ALL
41 static int64_t last_rate_timestamp_ns = 0;
42 static constexpr double kImuRateAlpha = 0.1; // EMA smoothing factor
43
44 static inline void update_imu_rate_estimate(const imu_data_t *arr, int n_packets)
45 {
46 if (n_packets <= 0 || arr == nullptr)
47 return;
48 // Prefer intra-batch estimate if we have multiple packets
49 if (n_packets >= 2)
50 {
51 int64_t dt_ns = arr[n_packets - 1].timestamp_ns - arr[0].timestamp_ns;
52 int64_t samples = (n_packets - 1);
53 if (dt_ns > 0 && samples > 0)
54 {
55 double avg_dt_ns = static_cast<double>(dt_ns) / static_cast<double>(samples);
56 double inst_rate_hz = 1e9 / avg_dt_ns;
57 double prev = imu_rate_hz.load(std::memory_order_relaxed);
58 double updated = (1.0 - kImuRateAlpha) * prev + kImuRateAlpha * inst_rate_hz;
59 imu_rate_hz.store(updated, std::memory_order_relaxed);
60 last_rate_timestamp_ns = arr[n_packets - 1].timestamp_ns;
61 return;
62 }
63 }
64 // Fallback to inter-batch estimate
65 int64_t t = arr[n_packets - 1].timestamp_ns;
66 if (last_rate_timestamp_ns > 0 && t > last_rate_timestamp_ns)
67 {
68 double dt_ns = static_cast<double>(t - last_rate_timestamp_ns);
69 double inst_rate_hz = 1e9 / dt_ns; // one sample over this interval
70 double prev = imu_rate_hz.load(std::memory_order_relaxed);
71 double updated = (1.0 - kImuRateAlpha) * prev + kImuRateAlpha * inst_rate_hz;
72 imu_rate_hz.store(updated, std::memory_order_relaxed);
73 }
74 printf("imu_rate_hz: %f\n", imu_rate_hz.load(std::memory_order_relaxed));
75 last_rate_timestamp_ns = t;
76 }
77}
78
79/** @brief Mutex for IMU data access synchronization */
80std::mutex imu_lock_mutex;
81
82#define TS_PRINT(msg) \
83 do \
84 { \
85 int64_t __ts = _apps_time_monotonic_ns(); \
86 printf("[TS %ld ns] %s\n", __ts, msg); \
87 } while (0)
88
89/**
90 * @brief Handler for incoming IMU data
91 *
92 * This callback processes incoming IMU data, updates the system state,
93 * and triggers appropriate processing based on motion state. It serves
94 * as the primary entry point for all IMU data processing in the system.
95 *
96 * The function performs the following operations:
97 * - Validates and parses IMU data packets
98 * - Updates frame transformation based on gravity direction
99 * - Converts IMU data to OpenVINS format
100 * - Performs batch processing to reduce mutex operations
101 * - Feeds IMU data to the VIO manager
102 * - Synchronizes camera data with IMU timestamps
103 * - Triggers VIO processing and data publishing
104 *
105 * The function uses batch processing to minimize mutex contention
106 * at high IMU frequencies and ensures proper temporal synchronization
107 * between IMU and camera data.
108 *
109 * @param ch Channel number (unused)
110 * @param data Pointer to IMU data buffer
111 * @param bytes Size of data buffer in bytes
112 * @param context Context pointer (unused)
113 */
114void _imu_data_handler_cb(int ch, char *data, int bytes, void *context)
115{
116 int64_t t_start = _apps_time_monotonic_ns();
117 int64_t t_prev = t_start;
118 is_imu_connected.store(true, std::memory_order_release);
119 // printf("[TS %ld ns] IMU callback start, bytes=%d\n", t_start, bytes);
120
121 // ---- validate data ----
122 int n_packets = 0;
123 imu_data_t *arr = pipe_validate_imu_data_t(data, bytes, &n_packets);
124 int64_t t_validate = _apps_time_monotonic_ns();
125 // printf("[DT %8.3f ms] validate done, n_packets=%d\n",
126 // (t_validate - t_prev)/1e6, n_packets);
127 // t_prev = t_validate;
128
129 active_callbacks.fetch_add(1, std::memory_order_acquire);
130 if (is_resetting.load(std::memory_order_relaxed))
131 {
132 active_callbacks.fetch_sub(1, std::memory_order_release);
133 return;
134 }
135 if (en_debug)
136 {
137 std::cout << "number of IMU packets: " << n_packets << std::endl;
138 }
139 if (!arr || n_packets <= 0)
140 {
141
142 vio_error_codes |= ERROR_CODE_IMU_MISSING;
143 active_callbacks.fetch_sub(1, std::memory_order_release);
144 return;
145 }
146
147 // ---- build batch ----
148 std::vector<ov_core::ImuData> imu_batch;
149 imu_batch.reserve(n_packets);
150
151 // Update IMU rate estimate for jerk window sizing
152 // update_imu_rate_estimate(arr, n_packets);
153
154 for (int i = 0; i < n_packets; ++i)
155 {
156 frame_transform.update(arr[i]);
157
158 ov_core::ImuData sample;
159 sample.timestamp = arr[i].timestamp_ns * 1e-9;
160 sample.wm = Eigen::Vector3d(arr[i].gyro_rad[0], arr[i].gyro_rad[1], arr[i].gyro_rad[2]);
161 sample.am = Eigen::Vector3d(arr[i].accl_ms2[0], arr[i].accl_ms2[1], arr[i].accl_ms2[2]);
162
163 if (last_imu_timestamp_ns != 0 &&
164 (sample.timestamp * 1e9 <= last_imu_timestamp_ns))
165 {
166 int64_t time_diff = last_imu_timestamp_ns - (sample.timestamp * 1e9);
167 if (time_diff > 1000000) // 1 ms
168 {
169 printf("[DEBUG] IMU timestamp regression %ld ns\n", time_diff);
170 vio_error_codes |= ERROR_CODE_BAD_TIMESTAMP;
171 }
172 }
173 imu_batch.push_back(std::move(sample));
174 // DO NOT DOWNSAMPLE FOR NOW
175 // if (has_acc_jerk.load(std::memory_order_acquire) == false && non_static.load(std::memory_order_acquire) == false) {
176 // //CUT RAW IMU SAMPLING FREQUENCY IN ROUGHLY 50%
177 // i = i +1;
178 // }
179 }
180 int64_t t_batch = _apps_time_monotonic_ns();
181 // printf("[DT %8.3f ms] batch conversion done, count=%zu\n",
182 // (t_batch - t_prev)/1e6, imu_batch.size());
183 // t_prev = t_batch;
184
185 if (imu_batch.empty())
186 {
187 vio_error_codes |= ERROR_CODE_DROPPED_IMU;
188 if (active_callbacks.fetch_sub(1, std::memory_order_release) == 1)
189 {
190 std::lock_guard<std::mutex> lk(reset_mtx);
191 reset_cv.notify_one();
192 }
193 return;
194 }
195
196 // ---- feed IMU ----
197 int imu_batch_size = (imu_model == IMU_MODEL_BMI270) ? 800 : 330;
198 if (frame_transform.is_initialized) vio_manager->feed_measurement_batch_imu(imu_batch, imu_batch_size);
199 // int64_t t_feed_imu = _apps_time_monotonic_ns();
200 // printf("[DT %8.3f ms] fed IMU batch into VIO manager\n",
201 // (t_feed_imu - t_prev)/1e6);
202 // t_prev = t_feed_imu;
203
204 // ---- sync camera ----
205 // TODO: CLEAR IMPORTANT CONSIDERARTION: OVINS DOES NOT SUPPORT MULTI-CAM TIME OFFSET CALIBRATION --> That's mainly because of the central image queue design
206 // Independently calibrating camera offset can be supported by either modfiying the feed function in OVINS + state vars
207 // or manually keeping tabs on relative camera offsets and correcting at the CameraQueueFusion level, either solution is kosher but for now we will just use the average offset
208 last_imu_timestamp_ns = imu_batch.back().timestamp * 1e9;
209 double ts_cutoff = (last_imu_timestamp_ns * 1e-9) -
210 vio_manager->get_state()->_calib_dt_CAMtoIMU->value()(0);
211
212 std::vector<ov_core::CameraData> batch;
213 if (CameraQueueFusion::getInstance().getSortedBatch(ts_cutoff, batch))
214 {
215 for (const auto &msg : batch)
216 {
217 if (frame_transform.is_initialized) vio_manager->feed_measurement_camera(msg);
218 cached_state = vio_manager->get_state();
219
220 // release cl_mem objects
221 for (auto &frame : msg.img_frames)
222 {
223 if (frame.img.handle_type == modal_flow::ExternalType::ClMem &&
224 frame.img.external_handle != 0)
225 {
226 cl_mem handle = reinterpret_cast<cl_mem>(
227 static_cast<uintptr_t>(frame.img.external_handle));
228
229 cl_int err = clReleaseMemObject(handle);
230 if (err != CL_SUCCESS)
231 {
232 fprintf(stderr, "Failed to release Frame cl_mem, err=%d\n", err);
233 }
234 // reset so no double free
235 const_cast<modal_flow::ImageView &>(frame.img).external_handle = 0;
236 }
237 }
238
239 if (is_resetting.load(std::memory_order_acquire))
240 {
241 break;
242 }
243 cached_features_map = vio_manager->get_used_features_map();
244 if (is_resetting.load(std::memory_order_relaxed))
245 {
246 break;
247 }
248 voxl::Publisher::getInstance().publish(cached_state, cached_features_map);
249 }
250 }
251 int64_t t_cam = _apps_time_monotonic_ns();
252 // printf("[DT %8.3f ms] processed %zu camera frames\n",
253 // (t_cam - t_prev)/1e6, batch.size());
254 // t_prev = t_cam;
255
256 // ---- finish ----
257 int64_t t_end = _apps_time_monotonic_ns();
258 double dt_ms = (double)(t_end - t_start) / 1e6;
259 if (dt_ms / batch.size() > 32.0 && en_debug)
260 {
261 printf("WARNING: IMU callback took too long: %8.3f ms for %d msgs\n", dt_ms, (int)batch.size());
262 }
263
264 if (active_callbacks.fetch_sub(1, std::memory_order_release) == 1)
265 {
266 std::lock_guard<std::mutex> lk(reset_mtx);
267 reset_cv.notify_one();
268 }
269}
270
271/**
272 * @brief Callback for IMU disconnect events
273 *
274 * This function is called when the IMU service disconnects. It handles
275 * the cleanup and state management required when IMU data becomes unavailable.
276 *
277 * The function uses thread-safe mutex locking to ensure proper
278 * state management during disconnection events.
279 *
280 * @param ch Channel number (unused)
281 * @param context Context pointer (unused)
282 */
283void _imu_disconnect_cb(__attribute__((unused)) int ch,
284 __attribute__((unused)) void *context)
285// THREAD SAFE DISCONNECT CALLBACK
286{
287 std::lock_guard<std::mutex> lg(imu_lock_mutex);
288 if (!is_imu_connected.load())
289 return;
290 pipe_client_flush(IMU_CH);
291 // Small delay to ensure pending operations complete
292 usleep(50000);
293 // pipe_client_close(IMU_CH);
294 is_imu_connected.store(false, std::memory_order_release);
295 return;
296}
297
298/**
299 * @brief Creates IMU pipe client and associated callbacks
300 *
301 * This function sets up the disconnect and data handler callbacks,
302 * and opens the client pipe connection to the IMU service. It initializes
303 * the complete IMU data pipeline for the VIO system.
304 *
305 * The function performs the following operations:
306 * - Sets up disconnect callback for graceful handling of service disconnection
307 * - Sets up data handler callback for processing incoming IMU measurements
308 * - Configures thread priority for high-frequency IMU processing
309 * - Opens the client pipe connection to the IMU service
310 * - Configures the pipe for optimal data flow
311 * - Sets the IMU connection status flag
312 *
313 * @return 0 on success, -1 on failure
314 */
316{
317 // MPA REGULAR CLIENT SETUP
318
319 pipe_client_set_disconnect_cb(IMU_CH, _imu_disconnect_cb, NULL);
320 pipe_client_set_simple_helper_cb(IMU_CH, _imu_data_handler_cb, NULL);
321 pipe_client_set_helper_thread_priority(IMU_CH, THREAD_PRIORITY_RT_HIGH);
322
323 int flags = CLIENT_FLAG_EN_SIMPLE_HELPER;
324
325 if (pipe_client_open(IMU_CH, imu_name, PROCESS_NAME, flags,
326 IMU_RECOMMENDED_READ_BUF_SIZE * 2) != 0)
327 {
328 fprintf(stderr, "failed to open imu client pipe\n");
329 vio_error_codes |= ERROR_CODE_IMU_MISSING;
330 return -1;
331 }
332 is_imu_connected = true;
333
334 return 0;
335}
void _imu_disconnect_cb(__attribute__((unused)) int ch, __attribute__((unused)) void *context)
Callback for IMU disconnect events.
void _imu_data_handler_cb(int ch, char *data, int bytes, void *context)
Handler for incoming IMU data.
int connect_imu_service(void)
Creates IMU pipe client and associated callbacks.
std::mutex imu_lock_mutex
Mutex for IMU data access synchronization.
IMU interface and data handling for VOXL OpenVINS.
volatile int64_t last_imu_timestamp_ns
Timestamp of last IMU data (nanoseconds)
Definition VoxlVars.cpp:177
std::atomic< uint32_t > active_callbacks
Number of callbacks inside the system.
Definition VoxlVars.cpp:56
char imu_name[64]
IMU device name.
Definition VoxlVars.cpp:164
voxl::FrameTransform frame_transform
Global frame transform instance.
Definition VoxlVars.cpp:183
std::mutex reset_mtx
Mutex used by reset thread.
Definition VoxlVars.cpp:59
imu_model_t imu_model
Active IMU model.
Definition VoxlVars.cpp:167
std::unique_ptr< ov_msckf::VioManager > vio_manager
Main VIO manager instance.
Definition VoxlVars.cpp:31
int en_debug
Enable debug output.
Definition VoxlVars.cpp:148
std::condition_variable reset_cv
Reset conditional variable.
Definition VoxlVars.cpp:62
std::atomic< bool > is_resetting
VIO reset state flag.
std::atomic< uint32_t > vio_error_codes
VIO error codes.
std::atomic< bool > is_imu_connected
IMU connection state.
std::atomic< double > imu_rate_hz
Estimated IMU sampling rate in Hz.
#define PROCESS_NAME
Process name for the VOXL OpenVINS server.
Definition VoxlVars.h:288
#define IMU_CH
MPA client channel for IMU data input.
Definition VoxlVars.h:182
static CameraQueueFusion & getInstance()
Get singleton instance.
bool getSortedBatch(double timestamp_cutoff, std::vector< ov_core::CameraData > &out)
Get sorted batch of camera data.
void publish(std::shared_ptr< ov_msckf::State > state, std::map< double, std::vector< std::shared_ptr< ov_core::Feature > > > used_features_map={})
Publish VIO data.
static Publisher & getInstance()
Get singleton instance.
Definition VoxlHK.h:142