LCOV - code coverage report
Current view: top level - SITL - SIM_AIS.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 226 256 88.3 %
Date: 2026-09-09 11:02:08 Functions: 11 13 84.6 %

          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 virtual vessels
      17             :     ./Tools/autotest/sim_vehicle.py -v Rover -A --serial5=sim:AIS --map
      18             : 
      19             :     param set SERIAL5_PROTOCOL 40
      20             :     param set AIS_TYPE 1
      21             :     reboot
      22             :     param set SIM_AIS_COUNT 10
      23             :     module load ais
      24             : 
      25             :     Dump logged AIS data to the serial port
      26             :     ./Tools/autotest/sim_vehicle.py -v Rover -A --serial5=sim:AISReplay --custom-location 51.58689798356386,-3.9044570193067965,0,0 --map
      27             : 
      28             :     param set SERIAL5_PROTOCOL 40
      29             :     param set AIS_TYPE 1
      30             :     reboot
      31             :     module load ais
      32             : */
      33             : 
      34             : #include "SIM_config.h"
      35             : 
      36             : #if AP_SIM_AIS_ENABLED
      37             : 
      38             : #include "SIM_AIS.h"
      39             : 
      40             : #include <SITL/SITL.h>
      41             : 
      42             : #define FORCE_VERSION_H_INCLUDE
      43             : #include "ap_version.h"
      44             : 
      45             : #include "SIM_Aircraft.h"
      46             : #include <AP_Common/NMEA.h>
      47             : 
      48             : extern const AP_HAL::HAL& hal;
      49             : 
      50             : using namespace SITL;
      51             : 
      52             : const AP_Param::GroupInfo AIS::var_info[] = {
      53             : 
      54             :     // @Param: COUNT
      55             :     // @DisplayName: Number of AIS vessels
      56             :     // @Description: Total number of AIS simulated vessels
      57             :     AP_GROUPINFO("COUNT", 1, AIS, vessel_count, -1),
      58             : 
      59             :     // @Param: RADIUS
      60             :     // @DisplayName: AIS radius stddev of vessels
      61             :     // @Description: Simulated standard deviation of radius in AIS of a vessel
      62             :     // @Units: m
      63             :     AP_GROUPINFO("RADIUS",  2, AIS, radius_m, 10000),
      64             : 
      65             :     AP_GROUPEND
      66             : };
      67             : 
      68           0 : AIS_Replay::AIS_Replay() : SerialDevice::SerialDevice()
      69             : {
      70             :     char* file_path;
      71           0 :     IGNORE_RETURN(asprintf(&file_path, AP_BUILD_ROOT "/libraries/SITL/SIM_AIS_data.txt"));
      72             : 
      73           0 :     file = fopen(file_path,"r");
      74             : 
      75           0 :     if (file == nullptr) {
      76           0 :         AP_HAL::panic("AIS could not open data file");
      77             :     }
      78             : 
      79             :     // seek past the header line
      80             :     char line[100];
      81           0 :     IGNORE_RETURN(fgets(line, sizeof(line), file));
      82           0 : }
      83             : 
      84           0 : void AIS_Replay::update()
      85             : {
      86           0 :     if (file == nullptr) {
      87           0 :         AP_HAL::panic("AIS lost data file");
      88             :     }
      89             : 
      90             :     // just send a line of data at 1Hz:
      91           0 :     const uint32_t now = AP_HAL::millis();
      92           0 :     if (now - last_sent_ms < 1000) {
      93           0 :         return;
      94             :     }
      95           0 :     last_sent_ms = now;
      96             : 
      97             :     char line[100];
      98             : 
      99           0 :     if (!fgets(line, sizeof(line), file)) {
     100             :         // got to the end of the file, circle back
     101           0 :         fseek(file,0,SEEK_SET);
     102           0 :         if (!fgets(line, sizeof(line), file)) {
     103           0 :             AP_HAL::panic("AIS lost data file");
     104             :         }
     105           0 :         return;
     106             :     }
     107             : 
     108             :     //hal.console->printf("%s",line);
     109           0 :     write_to_autopilot(line, strlen(line));
     110             : 
     111             : }
     112             : 
     113           3 : AIS::AIS()
     114             : {
     115           3 :     AP::sitl()->models.ais_ptr = this;
     116           3 :     AP_Param::setup_object_defaults(this, var_info);
     117           3 : }
     118             : 
     119       59718 : void AIS::update(const class Aircraft &aircraft)
     120             : {
     121       59718 :     const SIM *sitl = AP::sitl();
     122       59718 :     if (sitl == nullptr) {
     123           0 :         return;
     124             :     }
     125             : 
     126             :     // Disabled
     127       59718 :     if (vessel_count <= 0) {
     128       18469 :         return;
     129             :     }
     130             : 
     131             :     // Calculate the time step to take
     132       41249 :     const uint32_t now_ms = AP_HAL::millis();
     133       41249 :     const float dt = (now_ms - last_sim_update_ms) * 0.001;
     134       41249 :     last_sim_update_ms = now_ms;
     135             : 
     136             :     // Get current location to remove any vessel that is too far away
     137       41249 :     const Location &aircraft_loc = aircraft.get_location();
     138             : 
     139             :     // Update all vessels, marking any that move too far from the aircraft as in-active
     140     2103699 :     for (uint8_t i=0; i<ARRAY_SIZE(vessels); i++) {
     141     2062450 :         update_simulated_vessel(vessels[i], dt, aircraft_loc, radius_m, now_ms);
     142             :     }
     143             : 
     144             :     // Add new vessels until we have the correct number
     145     2103699 :     for (uint8_t i=0; i<ARRAY_SIZE(vessels); i++) {
     146             :         // Disable any extra vessels over the set count
     147     2062450 :         if (i >= vessel_count) {
     148     1875749 :             vessels[i].active = false;
     149     1875749 :             continue;
     150             :         }
     151             : 
     152             :         // Vessel is already active
     153      186701 :         if (vessels[i].active) {
     154      186665 :             continue;
     155             :         }
     156             : 
     157             :         // Generate new vessel
     158          36 :         init_vessel(vessels[i], aircraft_loc, radius_m);
     159             :     }
     160             : 
     161             : }
     162             : 
     163             : // Update a active vessel position and heading
     164     2062450 : void AIS::update_simulated_vessel(ais_vessel &vessel, const float dt, const Location &vehicle_loc, const float radius, const uint32_t now_ms)
     165             : {
     166             :     // Check if this vessel is active
     167     2062450 :     if (!vessel.active) {
     168     1875785 :         return;
     169             :     }
     170             : 
     171             :     // Update heading from turn rate, both turn rate and heading are in cdeg
     172      186688 :     vessel.info.heading += vessel.info.turn_rate * dt;
     173             : 
     174             :     // Extract location
     175      186688 :     Location loc { vessel.info.lat, vessel.info.lon, 0, Location::AltFrame::ABOVE_ORIGIN };
     176             : 
     177             :     // Update location from heading and velocity
     178      186688 :     loc.offset_bearing(vessel.info.heading * 0.01, vessel.info.velocity * 0.01 * dt);
     179             : 
     180             :     // Repopulate location
     181      186688 :     vessel.info.lat = loc.lat;
     182      186688 :     vessel.info.lon = loc.lng;
     183             : 
     184             :     // Check if location is still valid
     185      186688 :     if (vehicle_loc.get_distance(loc) > radius) {
     186          23 :         vessel.active = false;
     187          23 :         return;
     188             :     }
     189             : 
     190             :     // Report, update rate depends on speed
     191             :     uint32_t position_interval;
     192             : 
     193             :     // Convert speed over ground from 0.1 knots to cm/s
     194      186665 :     if (vessel.info.velocity > 23.0 * 0.1 * KNOTS_TO_M_PER_SEC * 100.0) {
     195       80073 :         position_interval = 2000; // every 2 seconds if faster than 23 knots
     196             : 
     197      106592 :     } else if (vessel.info.velocity > 14.0 * 0.1 * KNOTS_TO_M_PER_SEC * 100.0) {
     198       37665 :         position_interval = 6000; // every 6 seconds if faster than 14 knots
     199             : 
     200       68927 :     } else if (vessel.info.velocity > 0.0) {
     201        1678 :         position_interval = 10000; // every 10 seconds if moving
     202             : 
     203             :     } else {
     204       67249 :         position_interval = 3 * 60 * 1000; // every 3 mins if anchored or moored and moving at less than 3 knots
     205             : 
     206             :     }
     207             : 
     208             :     // Send position report at internal
     209      186665 :     if ((vessel.last_position_report_ms == 0) || (now_ms - vessel.last_position_report_ms > position_interval)) {
     210          45 :         send_position_report(vessel.info);
     211          45 :         vessel.last_position_report_ms = now_ms;
     212             :     }
     213             : 
     214             :     // Send static and voyage at internal
     215      186665 :     const uint32_t static_and_voyage_interval = 6 * 60 * 1000; // every 6 mins
     216      186665 :     if ((vessel.last_static_and_voyage_ms == 0) || (now_ms - vessel.last_static_and_voyage_ms > static_and_voyage_interval)) {
     217          13 :         send_static_and_voyage(vessel.info);
     218          13 :         vessel.last_static_and_voyage_ms = now_ms;
     219             :     }
     220             : 
     221             : }
     222             : 
     223             : // Generate a new vessel
     224          36 : void AIS::init_vessel(ais_vessel &vessel, const Location &vehicle_loc, const float radius)
     225             : {
     226             :     // Clear any existing data
     227          36 :     memset(&vessel, 0, sizeof(vessel));
     228             : 
     229             :     // Set flags for valid data
     230          36 :     vessel.info.flags |= AIS_FLAGS_VALID_VELOCITY | AIS_FLAGS_VALID_TURN_RATE;
     231             : 
     232             :     // Generate random data for static info, mask to 30 bit
     233          36 :     vessel.info.MMSI = rand() & 0x3FFFFFFF;
     234             : 
     235             :     // Set nav status and type
     236          36 :     vessel.info.navigational_status = AIS_NAV_STATUS_UNDER_WAY;
     237          36 :     vessel.info.type = AIS_TYPE_CARGO;
     238             : 
     239             :     // 90% chance of having valid dimensions
     240          36 :     if (rand() > RAND_MAX * 0.1) {
     241          32 :         vessel.info.flags |= AIS_FLAGS_VALID_DIMENSIONS;
     242             : 
     243             :         // Generate each dimension
     244          32 :         vessel.info.dimension_bow = abs(Aircraft::rand_normal(0, 300.0));
     245          32 :         if (vessel.info.dimension_bow > 511) {
     246           2 :             vessel.info.dimension_bow = 511;
     247           2 :             vessel.info.flags |= AIS_FLAGS_LARGE_BOW_DIMENSION;
     248             :         }
     249             : 
     250          32 :         vessel.info.dimension_stern = abs(Aircraft::rand_normal(0, 300.0));
     251          32 :         if (vessel.info.dimension_stern > 511) {
     252           3 :             vessel.info.dimension_stern = 511;
     253           3 :             vessel.info.flags |= AIS_FLAGS_LARGE_STERN_DIMENSION;
     254             :         }
     255             : 
     256          32 :         vessel.info.dimension_port = abs(Aircraft::rand_normal(0, 30.0));
     257          32 :         if (vessel.info.dimension_port > 63) {
     258           1 :             vessel.info.dimension_port = 63;
     259           1 :             vessel.info.flags |= AIS_FLAGS_LARGE_PORT_DIMENSION;
     260             :         }
     261             : 
     262          32 :         vessel.info.dimension_starboard = abs(Aircraft::rand_normal(0, 30.0));
     263          32 :         if (vessel.info.dimension_starboard > 63) {
     264           4 :             vessel.info.dimension_starboard = 63;
     265           4 :             vessel.info.flags |= AIS_FLAGS_LARGE_STARBOARD_DIMENSION;
     266             :         }
     267             :     }
     268             : 
     269             :     // 25% chance of turning
     270          36 :     if (rand() > RAND_MAX * 0.75) {
     271          14 :         vessel.info.turn_rate = Aircraft::rand_normal(0, 100.0);
     272             :     }
     273             : 
     274             :     // 95% chance of moving
     275          36 :     if (rand() > RAND_MAX * 0.05) {
     276          34 :         vessel.info.velocity = abs(Aircraft::rand_normal(0, 500.0));
     277             : 
     278             :         // Speed threshold for high velocity flag
     279             :         // Convert speed over ground from 0.1 knots to cm/s
     280          34 :         const uint16_t high_velocity = 1022 * 0.1 * KNOTS_TO_M_PER_SEC * 100.0;
     281             : 
     282          34 :         if (vessel.info.velocity >= high_velocity) {
     283           0 :             vessel.info.velocity = high_velocity;
     284           0 :             vessel.info.flags |= AIS_FLAGS_HIGH_VELOCITY;
     285             :         }
     286             :     }
     287             : 
     288             :     // Generate a heading
     289          36 :     vessel.info.heading = wrap_360_cd(rand());
     290             : 
     291             :     // Generate a random location
     292          36 :     Location loc = vehicle_loc;
     293          36 :     loc.offset( Vector3p {
     294             :         Aircraft::rand_normal(0, radius),
     295             :         Aircraft::rand_normal(0, radius),
     296             :         0
     297             :     });
     298          36 :     vessel.info.lat = loc.lat;
     299          36 :     vessel.info.lon = loc.lng;
     300             : 
     301             :     // mark active
     302          36 :     vessel.active = true;
     303          36 : }
     304             : 
     305             : /*
     306             :   formatted print of NMEA message, with checksum appended
     307             :  */
     308          71 : void AIS::nmea_printf(const char *fmt, ...)
     309             : {
     310             :     va_list ap;
     311             : 
     312          71 :     va_start(ap, fmt);
     313          71 :     char *s = nmea_vaprintf(fmt, ap);
     314          71 :     va_end(ap);
     315          71 :     if (s != nullptr) {
     316          71 :         write_to_autopilot((const char*)s, strlen(s));
     317          71 :         free(s);
     318             :     }
     319          71 : }
     320             : 
     321             : 
     322             : // Convert to NMEA char
     323        2183 : uint8_t AIS::encode_char(uint8_t payload) const
     324             : {
     325        2183 :     if (payload + 8 > 40) {
     326         524 :         return payload + 8 + 48;
     327             :     }
     328        1659 :     return payload + 48;
     329             : }
     330             : 
     331        2834 : uint8_t AIS::get_bits(uint32_t value, const uint8_t start, const uint8_t len) const
     332             : {
     333        2834 :     value = value >> start;
     334        2834 :     const uint8_t mask = 0b111111 >> (6 - len);
     335        2834 :     return value & mask;
     336             : }
     337             : 
     338             : // set the specified bits in the payload with 6 bit chars
     339         935 : void AIS::set_bits(uint8_t *payload, const uint16_t low, const uint16_t high, uint32_t value)
     340             : {
     341             :     // Bits are inclusive, so add one
     342         935 :     const uint8_t bit_len = high - low + 1;
     343         935 :     const uint32_t value_mask = 0xFFFFFFFF >> (32 - bit_len);
     344             : 
     345         935 :     if ((value & ~value_mask) != 0) {
     346           0 :         AP_HAL::panic("AIS value passed too big to fit (%u > %u bits)", value, bit_len);
     347             :     }
     348             : 
     349         935 :     const uint8_t char_low = low / 6;
     350         935 :     const uint8_t bit_low = low % 6;
     351             : 
     352         935 :     const uint8_t char_high = high / 6;
     353         935 :     const uint8_t bit_high = (high % 6) + 1;
     354             : 
     355         935 :     const uint8_t char_range = char_high - char_low;
     356         935 :     uint8_t bits = bit_len;
     357        3769 :     for (uint8_t index = 0; index <= char_range; index++) {
     358        2834 :         if (bits == 0) {
     359           0 :             AP_HAL::panic("AIS packing fail A");
     360             :         }
     361        2834 :         if (index == char_range) {
     362             :             // last char uses high bit
     363         935 :             const uint8_t bits_needed = MIN(bit_high, bit_len);
     364         935 :             bits -= bits_needed;
     365         935 :             payload[char_low + index] |= get_bits(value, bits, bits_needed) << (6 - bit_high);
     366             : 
     367        1899 :         } else if (index == 0) {
     368             :             // First char uses low bit
     369         593 :             const uint8_t bits_needed = MIN(6 - bit_low, bit_len);
     370         593 :             bits -= bits_needed;
     371         593 :             payload[char_low + index] |= get_bits(value, bits, bits_needed);
     372             : 
     373             :         } else {
     374             :             // Others need full length
     375        1306 :             bits -= 6;
     376        1306 :             payload[char_low + index] |= get_bits(value, bits, 6);
     377             : 
     378             :         }
     379             :     }
     380             : 
     381         935 :     if (bits != 0) {
     382           0 :         AP_HAL::panic("AIS packing fail B");
     383             :     }
     384             : 
     385         935 : }
     386             : 
     387         135 : void AIS::set_bits_signed(uint8_t *payload, const uint16_t low, const uint16_t high, int32_t value)
     388             : {
     389         135 :     if (value >= 0) {
     390             :         // Positive number is the same as unsigned
     391          81 :         set_bits(payload, low, high, value);
     392          81 :         return;
     393             :     }
     394             : 
     395             :     // Convert to unsigned
     396          54 :     uint32_t val = value;
     397             : 
     398             :     // Mask to correct length
     399          54 :     const uint8_t bit_len = high - low + 1;
     400          54 :     val &= 0xFFFFFFFF >> (32 - bit_len);
     401             : 
     402             :     // Set sign bit
     403          54 :     val |= 1 << (bit_len - 1);
     404             : 
     405          54 :     set_bits(payload, low, high, val);
     406             : }
     407             : 
     408          45 : void AIS::send_position_report(const mavlink_ais_vessel_t &info) {
     409             : 
     410             :     // Turn rate, default to not available
     411          45 :     int8_t rot = -128;
     412          45 :     if ((info.flags & AIS_FLAGS_VALID_TURN_RATE) != 0) {
     413          45 :         if ((info.flags & AIS_FLAGS_TURN_RATE_SIGN_ONLY) != 0) {
     414             :             // Only sign available
     415           0 :             rot = info.turn_rate >= 0 ? 127 : -127;
     416             : 
     417             :         } else {
     418             :             // cdeg/s to deg/min, take abs to allow sqrt
     419          45 :             const float turn_rate_deg_per_min = abs(info.turn_rate) * 60.0f * 0.01f;
     420             : 
     421             :             // Apply scaling, recovering sign
     422          45 :             rot = 4.733 * sqrtf(turn_rate_deg_per_min) * info.turn_rate >= 0 ? 1.0 : -1.0;
     423             :         }
     424             :     }
     425             : 
     426             :     // Corse over ground, default to not available
     427          45 :     uint16_t cog = 3600;
     428          45 :     if ((info.flags & AIS_FLAGS_VALID_COG) != 0) {
     429             :         // cdeg to 0.1 deg
     430           0 :         cog = info.COG * 0.1;
     431             :     }
     432             : 
     433             :     // Accuracy bit from flag
     434          45 :     const bool accuracy = (info.flags & AIS_FLAGS_POSITION_ACCURACY) != 0;
     435             : 
     436             :     // Speed over ground, default to not available
     437          45 :     uint16_t sog = 1023;
     438          45 :     if ((info.flags & AIS_FLAGS_VALID_VELOCITY) != 0) {
     439          45 :         if ((info.flags & AIS_FLAGS_HIGH_VELOCITY) != 0) {
     440           0 :             sog = 1022;
     441             :         } else {
     442          45 :             sog = MIN((float(info.velocity) * 0.01 * M_PER_SEC_TO_KNOTS * 10), 1022);
     443             :         }
     444             :     }
     445             : 
     446             :     // storage for payload
     447          45 :     uint8_t payload[28] {};
     448          45 :     set_bits(payload, 0,  5,  1); // type: Position Report Class A
     449          45 :     set_bits(payload, 6,  7,  3); // repeat: Do not repeat
     450          45 :     set_bits(payload, 8,  37, info.MMSI); // mmsi
     451          45 :     set_bits(payload, 38, 41, info.navigational_status); // status
     452          45 :     set_bits_signed(payload, 42, 49, rot); // rate of turn
     453          45 :     set_bits(payload, 50, 59, sog); // speed
     454          45 :     set_bits(payload, 60, 60, accuracy); // accuracy
     455          45 :     set_bits_signed(payload, 61, 88, int32_t(double(info.lon) * 0.06)); // lon
     456          45 :     set_bits_signed(payload, 89, 115, int32_t(double(info.lat) * 0.06)); // lat
     457          45 :     set_bits(payload, 116, 127, cog); // course over ground
     458          45 :     set_bits(payload, 128, 136, uint32_t(info.heading * 0.01)); // heading convert from deg to centi-deg
     459          45 :     set_bits(payload, 137, 142, 60); // second: not available
     460          45 :     set_bits(payload, 143, 144, 0); // maneuver: not available
     461             :     // 145 - 147: spare
     462          45 :     set_bits(payload, 148, 148, 0); // raim not used
     463          45 :     set_bits(payload, 149, 167, 0); // Radio status
     464             : 
     465             :     // Convert to chars
     466          45 :     uint8_t encoded[sizeof(payload) + 1] {};
     467        1305 :     for (uint8_t i = 0; i < sizeof(payload); i++) {
     468        1260 :         encoded[i] = encode_char(payload[i]);
     469             :     }
     470             : 
     471          45 :     const uint8_t total_fragments = 1;
     472          45 :     const uint8_t fragment = 1;
     473             :     // sequence ID is not used for a single fragment message
     474          45 :     const char channel_code = 'A';
     475          45 :     const uint8_t fill_bits = 0;
     476             : 
     477             :     // Send
     478          45 :     nmea_printf("!AIVDM,%u,%u,,%c,%s,%u", total_fragments, fragment, channel_code, encoded, fill_bits);
     479             : 
     480          45 : }
     481             : 
     482          13 : void AIS::send_static_and_voyage(const mavlink_ais_vessel_t &info) {
     483             : 
     484             :     // Fill in dimensions if valid
     485          13 :     uint16_t bow_dim = 0;
     486          13 :     uint16_t stern_dim = 0;
     487          13 :     uint8_t port_dim = 0;
     488          13 :     uint8_t star_dim = 0;
     489             : 
     490          13 :     if ((info.flags & AIS_FLAGS_VALID_DIMENSIONS) != 0) {
     491          12 :         if ((info.flags & AIS_FLAGS_LARGE_BOW_DIMENSION) != 0) {
     492           1 :             bow_dim = 511;
     493             :         } else {
     494          11 :             bow_dim = MIN(info.dimension_bow, 511);
     495             :         }
     496          12 :         if ((info.flags & AIS_FLAGS_LARGE_STERN_DIMENSION) != 0) {
     497           2 :             stern_dim = 511;
     498             :         } else {
     499          10 :             stern_dim = MIN(info.dimension_stern, 511);
     500             :         }
     501          12 :         if ((info.flags & AIS_FLAGS_LARGE_PORT_DIMENSION) != 0) {
     502           0 :             port_dim = 63;
     503             :         } else {
     504          12 :             port_dim = MIN(info.dimension_port, 63);
     505             :         }
     506          12 :         if ((info.flags & AIS_FLAGS_LARGE_STARBOARD_DIMENSION) != 0) {
     507           2 :             star_dim = 63;
     508             :         } else {
     509          10 :             star_dim = MIN(info.dimension_starboard, 63);
     510             :         }
     511             :     }
     512             : 
     513             :     // storage for payload
     514          13 :     uint8_t payload[71] {};
     515          13 :     set_bits(payload, 0,  5,  5); // type: Static and Voyage Related Data
     516          13 :     set_bits(payload, 6,  7,  3); // repeat: Do not repeat
     517          13 :     set_bits(payload, 8,  37, info.MMSI); // mmsi
     518          13 :     set_bits(payload, 38, 39, 1); // AIS version
     519          13 :     set_bits(payload, 40, 69, 0); // imo number
     520          13 :     set_bits(payload, 70, 111, '@'); // call_sign: empty
     521          13 :     set_bits(payload, 112, 231, '@'); // name: empty
     522          13 :     set_bits(payload, 232, 239, info.type);
     523          13 :     set_bits(payload, 240, 248, bow_dim); // bow_dim
     524          13 :     set_bits(payload, 249, 257, stern_dim); // stern_dim
     525          13 :     set_bits(payload, 258, 263, port_dim); // port_dim
     526          13 :     set_bits(payload, 264, 269, star_dim); // star_dim
     527          13 :     set_bits(payload, 270, 273, 1); // fix: GPS
     528          13 :     set_bits(payload, 274, 277, 0); // ETA month: not available
     529          13 :     set_bits(payload, 278, 282, 0); // ETA day: not available
     530          13 :     set_bits(payload, 283, 287, 0); // ETA hour: not available
     531          13 :     set_bits(payload, 288, 293, 0); // ETA minute: not available
     532          13 :     set_bits(payload, 294, 301, 0); // draught: 0 decimeters
     533          13 :     set_bits(payload, 302, 421, '@'); // destination: empty
     534          13 :     set_bits(payload, 422, 422, 1); // dte: Data terminal not ready
     535             :     // 423: not used
     536             : 
     537             :     // Split and encode
     538          13 :     const uint8_t msg1_len = 50;
     539          13 :     const uint8_t msg2_len = sizeof(payload) - msg1_len;
     540             : 
     541          13 :     uint8_t msg1_encoded[msg1_len + 1] {};
     542         663 :     for (uint8_t i = 0; i < msg1_len; i++) {
     543         650 :         msg1_encoded[i] = encode_char(payload[i]);
     544             :     }
     545             : 
     546          13 :     uint8_t msg2_encoded[msg2_len + 1] {};
     547         286 :     for (uint8_t i = 0; i < msg2_len; i++) {
     548         273 :         msg2_encoded[i] = encode_char(payload[msg1_len + i]);
     549             :     }
     550             : 
     551          13 :     const uint8_t total_fragments = 2;
     552          13 :     uint8_t fragment = 1;
     553          13 :     const char channel_code = 'A';
     554          13 :     const uint8_t fill_bits = 0;
     555             : 
     556             :     // Send
     557          13 :     nmea_printf("!AIVDM,%u,%u,%u,%c,%s,%u", total_fragments, fragment, sequence_ID, channel_code, msg1_encoded, fill_bits);
     558          13 :     fragment++;
     559             : 
     560          13 :     nmea_printf("!AIVDM,%u,%u,%u,%c,%s,%u", total_fragments, fragment, sequence_ID, channel_code, msg2_encoded, fill_bits);
     561             : 
     562          13 :     sequence_ID++;
     563          13 : }
     564             : 
     565             : #endif  // AP_SIM_AIS_ENABLED

Generated by: LCOV version 1.14