LCOV - code coverage report
Current view: top level - SITL - SIM_SensAItion.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 164 165 99.4 %
Date: 2026-09-09 11:02:08 Functions: 13 13 100.0 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             : */
      15             : /*
      16             :   Simulate SensAItion serial IMU device
      17             :   Converts SITL flight dynamics to SensAItion protocol packets
      18             : */
      19             : 
      20             : #include "SIM_config.h"
      21             : 
      22             : #if AP_SIM_EAHRS_SENSATION_ENABLED
      23             : 
      24             : #include "SIM_SensAItion.h"
      25             : #include <SITL/SITL.h>
      26             : #include <AP_HAL/AP_HAL.h>
      27             : #include <AP_Math/AP_Math.h>
      28             : #include <AP_Baro/AP_Baro.h>
      29             : #include <AP_HAL/utility/sparse-endian.h> // Required for put_beXX_ptr
      30             : #include <sys/time.h> // Required for struct timeval
      31             : 
      32             : using namespace SITL;
      33             : 
      34             : // Constants
      35             : // Gyro: SITL provides deg/s. Driver expects uDeg/s.
      36             : // Factor = 1e6. (NOT RAD_TO_UDEG!)
      37             : const float GYRO_NOISE_DEG = 0.02f;
      38             : 
      39             : // Baro: SITL provides mhPa
      40             : // std 100 mhPa
      41             : const float BARO_NOISE_MHPA = 100.0f;
      42             : 
      43             : int sim_log_counter = 0;
      44      249900 : static float rand_float_noise()
      45             : {
      46      249900 :     return ((float)rand() / (float)RAND_MAX) * 2.0f - 1.0f;
      47             : }
      48             : // [CPO FIX] Time Sync Helper
      49             : // Anchors the simulation timestamp to the absolute UTC start time of the SITL run.
      50        1143 : static void simulation_timeval(struct timeval *tv)
      51             : {
      52        1143 :     uint64_t now = AP_HAL::micros64();
      53             :     static uint64_t first_usec;
      54             :     static struct timeval first_tv;
      55        1143 :     if (first_usec == 0) {
      56           3 :         first_usec = now;
      57           3 :         first_tv.tv_sec = AP::sitl()->start_time_UTC;
      58             :     }
      59        1143 :     *tv = first_tv;
      60        1143 :     tv->tv_sec += now / 1000000ULL;
      61        1143 :     uint64_t new_usec = tv->tv_usec + (now % 1000000ULL);
      62        1143 :     tv->tv_sec += new_usec / 1000000ULL;
      63        1143 :     tv->tv_usec = new_usec % 1000000ULL;
      64        1143 : }
      65             : 
      66        1143 : static uint32_t get_gps_tow_ms()
      67             : {
      68             :     struct timeval tv;
      69        1143 :     gettimeofday(&tv, nullptr); // Get host system time (simulated wall clock)
      70             : 
      71             :     // Unix Epoch (1970) vs GPS Epoch (1980) offset is ~315964800 seconds
      72             :     // But simplistic SITL often just needs "seconds since Sunday".
      73             :     // Unix Epoch was a Thursday.
      74             :     // +3 days (259200 sec) aligns the modulo to Sunday.
      75             :     // 18 leap seconds (approx) for current time.
      76             : 
      77             :     // However, the most robust way in ArduPilot SITL is to use the
      78             :     // simulation start time which is aligned to the host clock.
      79             : 
      80        1143 :     uint64_t now_us = AP_HAL::micros64();
      81        1143 :     double sim_time_sec = (double)AP::sitl()->start_time_UTC + (now_us * 1.0e-6);
      82             : 
      83             :     // GPS leap seconds (18 as of 2024)
      84             :     // GPS time is ahead of UTC.
      85        1143 :     sim_time_sec += 18;
      86             : 
      87             :     // Seconds in week
      88        1143 :     uint32_t seconds_in_week = (uint32_t)sim_time_sec % 604800;
      89        1143 :     uint32_t ms_part = (now_us / 1000) % 1000;
      90             : 
      91        1143 :     return (seconds_in_week * 1000) + ms_part;
      92             : }
      93             : 
      94           9 : SensAItion::SensAItion(bool interleaved_mode) : SerialDevice::SerialDevice()
      95             : {
      96           9 :     _interleaved_mode = interleaved_mode;
      97           9 : }
      98             : 
      99      385829 : void SensAItion::update(void)
     100             : {
     101      385829 :     int tick1kHz = (AP_HAL::micros() + 500) / 1000;
     102      385829 :     if (tick1kHz <= _tick) {
     103       73439 :         return;
     104             :     }
     105      321390 :     _tick = tick1kHz;
     106             : 
     107             :     char trash_buf[64];
     108      321390 :     read_from_autopilot(trash_buf, sizeof(trash_buf));
     109             : 
     110             :     // [CPO FIX] The "Gentle Start" Delay
     111             :     // Wait 2 seconds after boot to allow ArduPilot to initialize
     112             :     // serial ports and parameters before we flood it with data.
     113             :     // This prevents race conditions during the reboot test.
     114      321390 :     if (AP_HAL::millis() < 1000) {
     115        9000 :         return;
     116             :     }
     117             : 
     118      312390 :     if (!init_sitl_pointer()) {
     119           0 :         return;
     120             :     }
     121             : 
     122      312390 :     const auto &fdm = _sitl->state;
     123             : 
     124             :     // 1. Packet 0: IMU
     125      312390 :     if ((_tick % _periodMessage0) == _phaseMessage0) {
     126       62475 :         send_packet_0_imu(fdm);
     127             :     }
     128             : 
     129             :     // 2. Interleaved-only Packets
     130      312390 :     if (_interleaved_mode) {
     131             : 
     132             :         // Packet 1: Orientation
     133      114160 :         if ((_tick % _periodMessage1) == _phaseMessage1) {
     134        1140 :             send_packet_1_orientation(fdm);
     135             :         }
     136             : 
     137             :         // Packet 2: INS
     138      114160 :         if ((_tick % _periodMessage2) == _phaseMessage2) {
     139        1143 :             send_packet_2_ins(fdm);
     140             :         }
     141             :     }
     142      312390 :     flush_packets();
     143             : }
     144             : 
     145       62475 : void SensAItion::send_packet_0_imu(const struct sitl_fdm &fdm)
     146             : {
     147             : 
     148             :     // Convert from SI units to SensAItion units
     149             :     // Acceleration: m/s² to µg (1e-6 g)
     150       62475 :     const float gravity = 9.80665f;
     151       62475 :     int32_t accel_x = (int32_t)(fdm.xAccel / gravity * 1e6);
     152       62475 :     int32_t accel_y = (int32_t)(fdm.yAccel / gravity * 1e6);
     153       62475 :     int32_t accel_z = (int32_t)(fdm.zAccel / gravity * 1e6);
     154             : 
     155             :     // Angular rates: deg/s to µdeg/s (1e-6 deg/s)
     156       62475 :     int32_t gyro_x = (int32_t)((fdm.rollRate + (rand_float_noise() * GYRO_NOISE_DEG)) * 1e6);
     157       62475 :     int32_t gyro_y = (int32_t)((fdm.pitchRate + (rand_float_noise() * GYRO_NOISE_DEG)) * 1e6);
     158       62475 :     int32_t gyro_z = (int32_t)((fdm.yawRate + (rand_float_noise() * GYRO_NOISE_DEG)) * 1e6);
     159             : 
     160             :     // Temperature: special conversion formula
     161       62475 :     float temp_c = 25.0f;
     162       62475 :     int16_t temperature = (int16_t)((temp_c - 20.0f) / 0.008f);
     163             : 
     164             :     // Magnetometer: Gauss to mGauss
     165       62475 :     int16_t mag_x = (int16_t)(fdm.bodyMagField.x * 1000);
     166       62475 :     int16_t mag_y = (int16_t)(fdm.bodyMagField.y * 1000);
     167       62475 :     int16_t mag_z = (int16_t)(fdm.bodyMagField.z * 1000);
     168             : 
     169             :     // Barometer: Calculate pressure from altitude (Pa to 0.1 Pa units)
     170       62475 :     const float pressure_pa = AP_Baro::get_pressure_for_alt_amsl(fdm.altitude);
     171       62475 :     int32_t baro = (int32_t)(pressure_pa * 10.0f + rand_float_noise() * BARO_NOISE_MHPA);
     172             : 
     173             :     // Periodic status output to verify simulator operation
     174             :     static uint32_t imu_count = 0;
     175       62475 :     if (++imu_count % 1000 == 0) {
     176          57 :         ::printf("SensAItion: IMU packet #%" PRIu32 " sent - accel_x=%" PRId32 " µg, gyro_x=%" PRId32 " µdeg/s\n",
     177             :                  imu_count, accel_x, gyro_x);
     178             :     }
     179             : 
     180             :     // Pack data in big-endian format (36 bytes data)
     181             :     uint8_t pkt[36];
     182       62475 :     put_be32_ptr(&pkt[0], accel_x);
     183       62475 :     put_be32_ptr(&pkt[4], accel_y);
     184       62475 :     put_be32_ptr(&pkt[8], accel_z);
     185       62475 :     put_be32_ptr(&pkt[12], gyro_x);
     186       62475 :     put_be32_ptr(&pkt[16], gyro_y);
     187       62475 :     put_be32_ptr(&pkt[20], gyro_z);
     188       62475 :     put_be16_ptr(&pkt[24], temperature);
     189       62475 :     put_be16_ptr(&pkt[26], mag_x);
     190       62475 :     put_be16_ptr(&pkt[28], mag_y);
     191       62475 :     put_be16_ptr(&pkt[30], mag_z);
     192       62475 :     put_be32_ptr(&pkt[32], baro);
     193             : 
     194       62475 :     if (_interleaved_mode) {
     195       22831 :         write_packet(0x00, pkt, sizeof(pkt));
     196             :         // --- DETAILED LOGGING (SIM SIDE) ---
     197             :         // if (sim_log_counter++ % 400 == 0) {
     198             :         //     fprintf(stderr, "[SIM-OUT] IMU Packet (Interleaved Mode: %d)\n", _interleaved_mode);
     199             :         //     fprintf(stderr, "   Acc(ug): X=%d Y=%d Z=%d\n", (int)accel_x, (int)accel_y, (int)accel_z);
     200             :         //     fprintf(stderr, "   Gyr(ud): X=%d Y=%d Z=%d\n", (int)gyro_x, (int)gyro_y, (int)gyro_z);
     201             :         //     fprintf(stderr, "   Mag(mG): X=%d Y=%d Z=%d\n", (int)mag_x, (int)mag_y, (int)mag_z);
     202             :         //     fprintf(stderr, "   Bar(0.1Pa): %d | Temp(raw): %d\n", (int)baro, (int)temperature);
     203             :         // }
     204             :     } else {
     205       39644 :         write_legacy_packet(pkt, sizeof(pkt));
     206             :         //--- DETAILED LOGGING (SIM SIDE) ---
     207             :         //if (sim_log_counter++ % 400 == 0) {
     208             :         //    fprintf(stderr, "[SIM-OUT] IMU Packet (Legacy Mode: %d)\n", _interleaved_mode);
     209             :         //    fprintf(stderr, "   Acc(ug): X=%d Y=%d Z=%d\n", (int)accel_x, (int)accel_y, (int)accel_z);
     210             :         //    fprintf(stderr, "   Gyr(ud): X=%d Y=%d Z=%d\n", (int)gyro_x, (int)gyro_y, (int)gyro_z);
     211             :         //    fprintf(stderr, "   Mag(mG): X=%d Y=%d Z=%d\n", (int)mag_x, (int)mag_y, (int)mag_z);
     212             :         //    fprintf(stderr, "   Bar(0.1Pa): %d | Temp(raw): %d\n", (int)baro, (int)temperature);
     213             :         //}
     214             :     }
     215             : 
     216       62475 : }
     217             : 
     218        1140 : void SensAItion::send_packet_1_orientation(const struct sitl_fdm &fdm)
     219             : {
     220             :     // Quaternion scaled by 1e6
     221        1140 :     int32_t q0 = (int32_t)(fdm.quaternion.q1 * 1.0e6f); // W
     222        1140 :     int32_t q1 = (int32_t)(fdm.quaternion.q2 * 1.0e6f); // X
     223        1140 :     int32_t q2 = (int32_t)(fdm.quaternion.q3 * 1.0e6f); // Y
     224        1140 :     int32_t q3 = (int32_t)(fdm.quaternion.q4 * 1.0e6f); // Z
     225             : 
     226             :     uint8_t pkt[16];
     227        1140 :     put_be32_ptr(&pkt[0],  (uint32_t)q0);
     228        1140 :     put_be32_ptr(&pkt[4],  (uint32_t)q1);
     229        1140 :     put_be32_ptr(&pkt[8],  (uint32_t)q2);
     230        1140 :     put_be32_ptr(&pkt[12], (uint32_t)q3);
     231             : 
     232             :     // --- LOGGING PROBE (SIM SIDE) ---
     233             :     // if (sim_log_counter % 100 == 0) { // Match Parser 1Hz rate
     234             :     //     fprintf(stderr, "[SIM-OUT] AHRS Packet (ID 0x01)\n");
     235             :     //     fprintf(stderr, "   Quat(1e-6): W=%d X=%d Y=%d Z=%d\n", q0, q1, q2, q3);
     236             :     // }
     237             : 
     238        1140 :     write_packet(0x01, pkt, sizeof(pkt));
     239        1140 : }
     240             : 
     241             : // NOTE: calculate_itow removed in favor of simulation_timeval inline logic
     242             : 
     243        1143 : void SensAItion::send_packet_2_ins(const struct sitl_fdm &fdm)
     244             : {
     245             :     // --- 1. PREPARE DATA ---
     246        1143 :     uint8_t align_status = 1; // 1 = Aligned
     247        1143 :     uint8_t gnss1_fix = 3; // 3D Fix
     248        1143 :     uint8_t gnss2_fix = 3;
     249             : 
     250        1143 :     uint32_t num_sats = 0x0C0C0C0C;
     251             : 
     252             :     // [CPO FIX] Calculate accurate GPS Time of Week using the SITL wall clock
     253             :     // Replaces previous calculate_itow()
     254             :     struct timeval tv;
     255        1143 :     simulation_timeval(&tv);
     256             : 
     257             :     // Convert TV (Seconds+Micros) to GPS Time of Week (ms)
     258             :     // We assume SITL starts somewhat recently.
     259             :     // Calculate seconds since Sunday 00:00:00 UTC
     260             :     //uint32_t seconds_in_week = 604800;
     261             :     //uint32_t tow_ms = (tv.tv_sec % seconds_in_week) * 1000 + (tv.tv_usec / 1000);
     262        1143 :     uint32_t itow = get_gps_tow_ms();
     263             : 
     264             :     // Position (deg -> 1e-7 deg)
     265        1143 :     int32_t lat = (int32_t)(fdm.latitude * 1.0e7);
     266        1143 :     int32_t lon = (int32_t)(fdm.longitude * 1.0e7);
     267             : 
     268             :     // Velocity (m/s -> mm/s)
     269        1143 :     int32_t vel_n = (int32_t)(fdm.speedN * 1000.0f);
     270        1143 :     int32_t vel_e = (int32_t)(fdm.speedE * 1000.0f);
     271        1143 :     int32_t vel_d = (int32_t)(fdm.speedD * 1000.0f);
     272             : 
     273             :     // Altitude (m -> mm)
     274        1143 :     int32_t alt_mm = (int32_t)(fdm.altitude * 1000.0);
     275             : 
     276             :     // Accuracy (mm, mm/s)
     277        1143 :     int32_t acc_lat_mm = 100; // 0.1m
     278        1143 :     int32_t acc_lon_mm = 100; // 0.1m
     279        1143 :     int32_t acc_vn_mm = 20;
     280        1143 :     int32_t acc_ve_mm = 20;
     281        1143 :     int32_t acc_vd_mm = 20;
     282        1143 :     int32_t acc_vd_pos_mm = 100;
     283        1143 :     uint32_t err_flags = 0;
     284        1143 :     uint8_t sensor_valid = 0xFF; // All valid
     285             : 
     286             :     // Date
     287        1143 :     uint16_t year = 2025;
     288        1143 :     uint16_t month = 12;
     289        1143 :     uint8_t day = 7;
     290             : 
     291             :     // --- 2. PACKING (Big Endian - 69 Bytes) ---
     292             :     uint8_t pkt[69];
     293             : 
     294             :     // Bytes 0-3: Num Sats (4B)
     295        1143 :     put_be32_ptr(&pkt[0], num_sats);
     296             : 
     297             :     // Bytes 4-7: Error Flags (4B)
     298        1143 :     put_be32_ptr(&pkt[4], err_flags);
     299             : 
     300             :     // Byte 8: Sensor Valid (1B)
     301        1143 :     pkt[8] = sensor_valid;
     302             : 
     303             :     // Bytes 9-12: Latitude (4B)
     304        1143 :     put_be32_ptr(&pkt[9], (uint32_t)lat);
     305             : 
     306             :     // Bytes 13-16: Longitude (4B)
     307        1143 :     put_be32_ptr(&pkt[13], (uint32_t)lon);
     308             : 
     309             :     // Bytes 17-28: Velocity N, E, D (12B)
     310        1143 :     put_be32_ptr(&pkt[17], (uint32_t)vel_n);
     311        1143 :     put_be32_ptr(&pkt[21], (uint32_t)vel_e);
     312        1143 :     put_be32_ptr(&pkt[25], (uint32_t)vel_d);
     313             : 
     314             :     // Bytes 29-32: Altitude MSL (4B)
     315        1143 :     put_be32_ptr(&pkt[29], (uint32_t)alt_mm);
     316             : 
     317             :     // Byte 33: Alignment Status (1B)
     318        1143 :     pkt[33] = align_status;
     319             : 
     320             :     // Bytes 34-37: Time iTOW (4B)
     321        1143 :     put_be32_ptr(&pkt[34], itow);
     322             : 
     323             :     // 38-39: GNSS Fix
     324        1143 :     pkt[38] = gnss1_fix;
     325        1143 :     pkt[39] = gnss2_fix;
     326             : 
     327             :     // 40-44: UTC Date/Time
     328        1143 :     put_be16_ptr(&pkt[40], year);
     329        1143 :     put_be16_ptr(&pkt[42], month);
     330        1143 :     pkt[44] = day;
     331             : 
     332             :     // 45-68: Accuracy Metrics (mm or mm/s)
     333        1143 :     put_be32_ptr(&pkt[45], acc_lat_mm);
     334        1143 :     put_be32_ptr(&pkt[49], acc_lon_mm);
     335        1143 :     put_be32_ptr(&pkt[53], acc_vn_mm);
     336        1143 :     put_be32_ptr(&pkt[57], acc_ve_mm);
     337        1143 :     put_be32_ptr(&pkt[61], acc_vd_mm);
     338        1143 :     put_be32_ptr(&pkt[65], acc_vd_pos_mm);
     339             : 
     340             :     // --- LOGGING PROBE (SIM SIDE) ---
     341             :     // if (sim_log_counter % 100 == 0) {
     342             :     //     fprintf(stderr, "[SIM-OUT] INS Packet (iTOW: %u)\n", itow);
     343             :     //     fprintf(stderr, "   Pos: Lat=%d Lon=%d Alt=%d mm\n", lat, lon, alt_mm);
     344             :     //     fprintf(stderr, "   Vel: N=%d E=%d D=%d mm/s\n", vel_n, vel_e, vel_d);
     345             :     //     fprintf(stderr, "   Stat: Align=%d Valid=0x%02X\n", align_status, sensor_valid);
     346             :     // }
     347             : 
     348             : 
     349        1143 :     write_packet(0x02, pkt, sizeof(pkt));
     350        1143 : }
     351             : 
     352             : 
     353      312390 : void SensAItion::flush_packets()
     354             : {
     355      312390 :     if (_buffert_cnt > 0) {
     356       62475 :         write_to_autopilot((const char *)&_buffert, _buffert_cnt);
     357       62475 :         _buffert_cnt = 0;
     358             :     }
     359      312390 : }
     360             : 
     361      219388 : void SensAItion::write_to_autopilot_buf(const char *data, int length)
     362             : {
     363      219388 :     memcpy(&_buffert[_buffert_cnt], data, length);
     364      219388 :     _buffert_cnt += length;
     365      219388 : }
     366             : 
     367       25114 : void SensAItion::write_packet(uint8_t msg_id, const uint8_t* payload, uint16_t length)
     368             : {
     369       25114 :     const uint8_t header = 0xFA;
     370       25114 :     write_to_autopilot_buf((const char *)&header, 1);
     371       25114 :     write_to_autopilot_buf((const char *)&msg_id, 1);
     372       25114 :     write_to_autopilot_buf((const char *)payload, length);
     373       25114 :     uint8_t crc = (uint8_t)calculate_crc(msg_id, payload, length, true);
     374       25114 :     write_to_autopilot_buf((const char *)&crc, 1);
     375       25114 : }
     376             : 
     377       39644 : void SensAItion::write_legacy_packet(const uint8_t* payload, uint16_t length)
     378             : {
     379       39644 :     const uint8_t header = 0xFA;
     380       39644 :     write_to_autopilot_buf((const char *)&header, 1);
     381       39644 :     write_to_autopilot_buf((const char *)payload, length);
     382       39644 :     uint8_t crc = (uint8_t)calculate_crc(0, payload, length, false);
     383       39644 :     write_to_autopilot_buf((const char *)&crc, 1);
     384       39644 : }
     385             : 
     386       64758 : uint16_t SensAItion::calculate_crc(uint8_t msg_id, const uint8_t* payload, uint16_t length, bool use_id)
     387             : {
     388       64758 :     uint8_t crc = 0;
     389       64758 :     if (use_id) {
     390       25114 :         crc ^= msg_id;
     391             :     }
     392     2410965 :     for (uint16_t i = 0; i < length; i++) {
     393     2346207 :         crc ^= payload[i];
     394             :     }
     395       64758 :     return crc;
     396             : }
     397             : 
     398             : #endif // AP_SIM_EAHRS_SENSATION_ENABLED

Generated by: LCOV version 1.14