#include "AP_AIS.h"
#if AP_AIS_ENABLED
#include <AP_Vehicle/AP_Vehicle_Type.h>
#define AP_AIS_DUMMY_METHODS_ENABLED ((AP_AIS_ENABLED == 2) && !APM_BUILD_TYPE(APM_BUILD_Rover))
#if !AP_AIS_DUMMY_METHODS_ENABLED
#include <AP_Logger/AP_Logger.h>
#include <AP_SerialManager/AP_SerialManager.h>
#include <GCS_MAVLink/GCS_MAVLink.h>
#include <GCS_MAVLink/GCS.h>
#include <AP_AHRS/AP_AHRS.h>
#include <AP_Common/ExpandingString.h>
#include <AC_Avoidance/AP_OADatabase.h>
const AP_Param::GroupInfo AP_AIS::var_info[] = {
AP_GROUPINFO_FLAGS("TYPE", 1, AP_AIS, _type, 0, AP_PARAM_FLAG_ENABLE),
AP_GROUPINFO("LIST_MAX", 2, AP_AIS, _max_list, 25),
AP_GROUPINFO("TIME_OUT", 3, AP_AIS, _time_out, 600),
AP_GROUPINFO("LOGGING", 4, AP_AIS, _log_options, AIS_OPTIONS_LOG_UNSUPPORTED_RAW | AIS_OPTIONS_LOG_DECODED),
AP_GROUPEND
};
AP_AIS::AP_AIS()
{
if (_singleton != nullptr) {
AP_HAL::panic("AIS must be singleton");
}
_singleton = this;
AP_Param::setup_object_defaults(this, var_info);
}
bool AP_AIS::enabled() const
{
return AISType(_type.get()) != AISType::NONE;
}
void AP_AIS::init()
{
if (!enabled()) {
return;
}
_uart = AP::serialmanager().find_serial(AP_SerialManager::SerialProtocol_AIS, 0);
if (_uart == nullptr) {
return;
}
_uart->begin(AP::serialmanager().find_baudrate(AP_SerialManager::SerialProtocol_AIS, 0));
}
void AP_AIS::update()
{
if (!_uart || !enabled()) {
return;
}
uint32_t nbytes = MIN(_uart->available(),1024U);
while (nbytes-- > 0) {
const int16_t byte = _uart->read();
if (byte == -1) {
break;
}
const char c = byte;
if (decode(c)) {
const bool log_all = (_log_options & AIS_OPTIONS_LOG_ALL_RAW) != 0;
const bool log_unsupported = ((_log_options & AIS_OPTIONS_LOG_UNSUPPORTED_RAW) != 0) && !log_all;
if (_incoming.total > AIVDM_BUFFER_SIZE) {
#if HAL_LOGGING_ENABLED
if (log_all || log_unsupported) {
log_raw(&_incoming);
}
#endif
break;
}
#if HAL_LOGGING_ENABLED
if (log_all) {
log_raw(&_incoming);
}
#endif
if (_incoming.num == 1 && _incoming.total == 1) {
if (!payload_decode(_incoming.payload) && log_unsupported) {
#if HAL_LOGGING_ENABLED
log_raw(&_incoming);
#endif
}
} else if (_incoming.num == _incoming.total) {
uint8_t index = 0;
const uint8_t parts = _incoming.num - 1;
uint8_t msg_parts[parts];
for (uint8_t i = 0; i < AIVDM_BUFFER_SIZE; i++) {
if (_AIVDM_buffer[i].num == (index + 1) && _AIVDM_buffer[i].total == _incoming.total && _AIVDM_buffer[i].ID == _incoming.ID) {
msg_parts[index] = i;
index++;
if (index >= parts) {
break;
}
}
}
if (parts != index) {
#if HAL_LOGGING_ENABLED
if (log_unsupported) {
for (uint8_t i = 0; i < index; i++) {
log_raw(&_AIVDM_buffer[msg_parts[i]]);
}
log_raw(&_incoming);
}
#endif
for (uint8_t i = 0; i < index; i++) {
buffer_shift(msg_parts[i]);
}
break;
}
ExpandingString s;
s.append(_AIVDM_buffer[msg_parts[0]].payload, strlen(_AIVDM_buffer[msg_parts[0]].payload));
for (uint8_t i = 1; i < index; i++) {
s.append(_AIVDM_buffer[msg_parts[i]].payload, strlen(_AIVDM_buffer[msg_parts[i]].payload));
}
s.append(_incoming.payload, strlen(_incoming.payload));
#if HAL_LOGGING_ENABLED
const bool decoded = payload_decode(s.get_string());
#endif
for (uint8_t i = 0; i < index; i++) {
#if HAL_LOGGING_ENABLED
if (!decoded && log_unsupported) {
log_raw(&_AIVDM_buffer[msg_parts[i]]);
}
#endif
buffer_shift(msg_parts[i]);
}
#if HAL_LOGGING_ENABLED
if (!decoded && log_unsupported) {
log_raw(&_incoming);
}
#endif
} else {
bool fits_in = false;
for (uint8_t i = 0; i < AIVDM_BUFFER_SIZE; i++) {
if (_AIVDM_buffer[i].num == 0 && _AIVDM_buffer[i].total == 0 && _AIVDM_buffer[i].ID == 0) {
_AIVDM_buffer[i] = _incoming;
fits_in = true;
break;
}
}
if (!fits_in) {
#if HAL_LOGGING_ENABLED
if (log_unsupported) {
log_raw(&_AIVDM_buffer[0]);
}
#endif
buffer_shift(0);
_AIVDM_buffer[AIVDM_BUFFER_SIZE - 1] = _incoming;
}
}
}
}
const uint32_t now = AP_HAL::millis();
const uint32_t timeout = _time_out * 1000;
if (now < timeout) {
return;
}
const uint32_t deadline = now - timeout;
for (uint16_t i = 0; i < _list.max_items(); i++) {
if (_list[i].last_update_ms < deadline && _list[i].last_update_ms != 0) {
clear_list_item(i);
}
}
}
void AP_AIS::send(mavlink_channel_t chan)
{
if (!enabled()) {
return;
}
const uint16_t list_size = _list.max_items();
const uint32_t now = AP_HAL::millis();
uint16_t search_length = 0;
while (search_length < list_size) {
_send_index++;
search_length++;
if (_send_index == list_size) {
_send_index = 0;
}
if (_list[_send_index].last_update_ms != 0 &&
(_list[_send_index].last_send_ms < _list[_send_index].last_update_ms || now -_list[_send_index].last_send_ms > 30000)) {
_list[_send_index].last_send_ms = now;
_list[_send_index].info.tslc = (now - _list[_send_index].last_update_ms) * 0.001;
mavlink_msg_ais_vessel_send_struct(chan,&_list[_send_index].info);
return;
}
}
}
#if AP_OADATABASE_ENABLED
void AP_AIS::send_to_object_avoidance_database(const struct ais_vehicle_t &vessel)
{
AP_OADatabase *oaDb = AP::oadatabase();
if (oaDb == nullptr || !oaDb->healthy()) {
return;
}
if (!check_location(vessel.info.lat, vessel.info.lon)) {
return;
}
const Location loc { vessel.info.lat, vessel.info.lon, 0, Location::AltFrame::ABOVE_ORIGIN };
Vector3f pos;
if (!loc.get_vector_from_origin_NEU_m(pos)) {
return;
}
Vector2f current_pos;
if (!AP::ahrs().get_relative_position_NE_origin_float(current_pos)) {
return;
}
float distance = (pos.xy() - current_pos).length();
if ((vessel.info.flags & AIS_FLAGS_VALID_DIMENSIONS) == 0) {
oaDb->queue_push(pos, vessel.last_update_ms, distance, AP_OADatabase::OA_DbItem::Source::AIS, vessel.info.MMSI);
return;
}
if ((vessel.info.heading == 0) || (vessel.info.heading > 360 * 100)) {
float radius = vessel.info.dimension_bow;
radius = MAX(radius, vessel.info.dimension_stern);
radius = MAX(radius, vessel.info.dimension_port);
radius = MAX(radius, vessel.info.dimension_starboard);
oaDb->queue_push(pos, vessel.last_update_ms, distance, radius, AP_OADatabase::OA_DbItem::Source::AIS, vessel.info.MMSI);
return;
}
Vector2f offset {
(vessel.info.dimension_bow - vessel.info.dimension_stern) * 0.5,
(vessel.info.dimension_starboard - vessel.info.dimension_port) * 0.5
};
offset.rotate(cd_to_rad(vessel.info.heading));
pos.xy() += offset;
distance = (pos.xy() - current_pos).length();
const float radius = MAX(
(vessel.info.dimension_bow + vessel.info.dimension_stern) * 0.5,
(vessel.info.dimension_starboard + vessel.info.dimension_port) * 0.5
);
oaDb->queue_push(pos, vessel.last_update_ms, distance, radius, AP_OADatabase::OA_DbItem::Source::AIS, vessel.info.MMSI);
}
#endif
bool AP_AIS::check_location(int32_t lat, int32_t lng) const
{
if (lat == 0 && lng == 0) {
return false;
}
return check_latlng(lat, lng);
}
void AP_AIS::buffer_shift(uint8_t i)
{
for (uint8_t n = i; n < (AIVDM_BUFFER_SIZE - 1); n++) {
_AIVDM_buffer[n].ID = _AIVDM_buffer[n+1].ID;
_AIVDM_buffer[n].num = _AIVDM_buffer[n+1].num;
_AIVDM_buffer[n].total = _AIVDM_buffer[n+1].total;
strncpy(_AIVDM_buffer[n].payload,_AIVDM_buffer[n+1].payload,AIVDM_PAYLOAD_SIZE);
}
_AIVDM_buffer[AIVDM_BUFFER_SIZE - 1].ID = 0;
_AIVDM_buffer[AIVDM_BUFFER_SIZE - 1].num = 0;
_AIVDM_buffer[AIVDM_BUFFER_SIZE - 1].total = 0;
_AIVDM_buffer[AIVDM_BUFFER_SIZE - 1].payload[0] = 0;
}
bool AP_AIS::get_vessel_index(uint32_t mmsi, uint16_t &index, int32_t lat, int32_t lon)
{
const uint16_t list_size = _list.max_items();
uint16_t empty = 0;
bool found_empty = false;
for (uint16_t i = 0; i < list_size; i++) {
if (_list[i].info.MMSI == mmsi) {
index = i;
return true;
}
if (_list[i].last_update_ms == 0 && !found_empty) {
found_empty = true;
empty = i;
}
}
if (found_empty) {
index = empty;
_list[index].info.MMSI = mmsi;
return true;
}
if (list_size < _max_list) {
if (_list.expand(1)) {
index = list_size;
_list[index].info.MMSI = mmsi;
return true;
}
}
if (!check_location(lat, lon)) {
return false;
}
Location current_loc;
if (!AP::ahrs().get_location(current_loc)) {
return false;
}
Location loc;
float dist;
float max_dist = 0;
for (uint16_t i = 0; i < list_size; i++) {
if (!check_location(_list[i].info.lat, _list[i].info.lon)) {
index = i;
clear_list_item(index);
_list[index].info.MMSI = mmsi;
return true;
}
loc.lat = _list[i].info.lat;
loc.lng = _list[i].info.lon;
dist = loc.get_distance(current_loc);
if (dist > max_dist) {
max_dist = dist;
index = i;
}
}
loc.lat = lat;
loc.lng = lon;
dist = loc.get_distance(current_loc);
if (dist < max_dist) {
clear_list_item(index);
_list[index].info.MMSI = mmsi;
return true;
}
return false;
}
void AP_AIS::clear_list_item(uint16_t index)
{
if (index < _list.max_items()) {
memset(&_list[index],0,sizeof(ais_vehicle_t));
}
}
bool AP_AIS::payload_decode(const char *payload)
{
const uint8_t type = payload_char_decode(payload[0]);
switch (type) {
case 1:
case 2:
case 3:
return decode_position_report(payload, type);
case 4:
return decode_base_station_report(payload);
case 5:
return decode_static_and_voyage_data(payload);
default:
return false;
}
}
int32_t AP_AIS::scale_lat_lon(const int32_t val) const
{
const double scale_factor = (1.0 / 600000.0) * 1e7;
const double ret = val * scale_factor;
if (ret > INT32_MAX || ret < INT32_MIN) {
return INT32_MAX;
}
return ret;
}
bool AP_AIS::decode_position_report(const char *payload, uint8_t type)
{
if (strlen(payload) != 28) {
return false;
}
uint8_t repeat = get_bits(payload, 6, 7);
uint32_t mmsi = get_bits(payload, 8, 37);
uint8_t nav = get_bits(payload, 38, 41);
int8_t rot = get_bits_signed(payload, 42, 49);
uint16_t sog = get_bits(payload, 50, 59);
bool pos_acc = get_bits(payload, 60, 60);
int32_t lon = scale_lat_lon(get_bits_signed(payload, 61, 88));
int32_t lat = scale_lat_lon(get_bits_signed(payload, 89, 115));
uint16_t cog = get_bits(payload, 116, 127) * 10;
uint16_t head = get_bits(payload, 128, 136) * 100;
uint8_t sec_utc = get_bits(payload, 137, 142);
uint8_t maneuver = get_bits(payload, 143, 144);
bool raim = get_bits(payload, 148, 148);
uint32_t radio = get_bits(payload, 149, 167);
#if HAL_LOGGING_ENABLED
if ((_log_options & AIS_OPTIONS_LOG_DECODED) != 0) {
const struct log_AIS_msg1 pkt{
LOG_PACKET_HEADER_INIT(LOG_AIS_MSG1),
time_us : AP_HAL::micros64(),
type : type,
repeat : repeat,
mmsi : mmsi,
nav : nav,
rot : rot,
sog : sog,
pos_acc : pos_acc,
lon : lon,
lat : lat,
cog : cog,
head : head,
sec_utc : sec_utc,
maneuver : maneuver,
raim : raim,
radio : radio
};
AP::logger().WriteBlock(&pkt, sizeof(pkt));
}
#else
(void)repeat;
(void)sec_utc;
(void)maneuver;
(void)raim;
(void)radio;
#endif
uint16_t index;
if (!get_vessel_index(mmsi, index, lat, lon)) {
return true;
}
const uint16_t mask = ~(AIS_FLAGS_POSITION_ACCURACY | AIS_FLAGS_VALID_COG | AIS_FLAGS_VALID_VELOCITY | AIS_FLAGS_VALID_TURN_RATE | AIS_FLAGS_TURN_RATE_SIGN_ONLY);
uint16_t flags = _list[index].info.flags & mask;
if (pos_acc) {
flags |= AIS_FLAGS_POSITION_ACCURACY;
}
if (cog < 36000) {
flags |= AIS_FLAGS_VALID_COG;
} else {
cog = 0;
}
uint16_t sog_cms = 0;
if (sog < 1023) {
flags |= AIS_FLAGS_VALID_VELOCITY;
sog_cms = sog * 0.1 * KNOTS_TO_M_PER_SEC * 100.0;
if (sog == 1022) {
flags |= AIS_FLAGS_HIGH_VELOCITY;
}
}
if (rot <= -128) {
rot = 0;
} else {
flags |= AIS_FLAGS_VALID_TURN_RATE;
const int8_t sign = rot > 0 ? 1 : -1;
if (rot == 127 || rot == -127) {
flags |= AIS_FLAGS_TURN_RATE_SIGN_ONLY;
rot = sign;
} else {
rot = sign * MIN(sq(rot / 4.733) * (100.0 / 60.0), INT8_MAX - 1);
}
}
_list[index].info.lat = lat;
_list[index].info.lon = lon;
_list[index].info.COG = cog;
_list[index].info.heading = head;
_list[index].info.velocity = sog_cms;
_list[index].info.flags = flags;
_list[index].info.turn_rate = rot;
_list[index].info.navigational_status = nav;
_list[index].last_update_ms = AP_HAL::millis();
#if AP_OADATABASE_ENABLED
send_to_object_avoidance_database(_list[index]);
#endif
return true;
}
bool AP_AIS::decode_base_station_report(const char *payload)
{
if (strlen(payload) != 28) {
return false;
}
uint8_t repeat = get_bits(payload, 6, 7);
uint32_t mmsi = get_bits(payload, 8, 37);
uint16_t year = get_bits(payload, 38, 51);
uint8_t month = get_bits(payload, 52, 55);
uint8_t day = get_bits(payload, 56, 60);
uint8_t hour = get_bits(payload, 61, 65);
uint8_t minute = get_bits(payload, 66, 71);
uint8_t second = get_bits(payload, 72, 77);
bool fix = get_bits(payload, 78, 78);
int32_t lon = scale_lat_lon(get_bits_signed(payload, 79, 106));
int32_t lat = scale_lat_lon(get_bits_signed(payload, 107, 133));
uint8_t epfd = get_bits(payload, 134, 137);
bool raim = get_bits(payload, 148, 148);
uint32_t radio = get_bits(payload, 149, 167);
#if HAL_LOGGING_ENABLED
if ((_log_options & AIS_OPTIONS_LOG_DECODED) != 0) {
struct log_AIS_msg4 pkt {
LOG_PACKET_HEADER_INIT(LOG_AIS_MSG4),
time_us : AP_HAL::micros64(),
repeat : repeat,
mmsi : mmsi,
year : year,
month : month,
day : day,
hour : hour,
minute : minute,
second : second,
fix : fix,
lon : lon,
lat : lat,
epfd : epfd,
raim : raim,
radio : radio
};
AP::logger().WriteBlock(&pkt, sizeof(pkt));
}
#else
(void)repeat;
(void)year;
(void)month;
(void)day;
(void)hour;
(void)minute;
(void)second;
(void)fix;
(void)epfd;
(void)raim;
(void)radio;
#endif
uint16_t index;
if (!get_vessel_index(mmsi, index)) {
return true;
}
_list[index].info.lat = lat;
_list[index].info.lon = lon;
_list[index].last_update_ms = AP_HAL::millis();
#if AP_OADATABASE_ENABLED
send_to_object_avoidance_database(_list[index]);
#endif
return true;
}
bool AP_AIS::decode_static_and_voyage_data(const char *payload)
{
if (strlen(payload) != 71) {
return false;
}
char call_sign[8];
char name[21];
char dest[21];
uint8_t repeat = get_bits(payload, 6, 7);
uint32_t mmsi = get_bits(payload, 8, 37);
uint8_t ver = get_bits(payload, 38, 39);
uint32_t imo = get_bits(payload, 40, 69);
get_char(payload, call_sign, 70, 111);
get_char(payload, name, 112, 231);
uint8_t vessel_type = get_bits(payload, 232, 239);
uint16_t bow_dim = get_bits(payload, 240, 248);
uint16_t stern_dim = get_bits(payload, 249, 257);
uint8_t port_dim = get_bits(payload, 258, 263);
uint8_t star_dim = get_bits(payload, 264, 269);
uint8_t fix = get_bits(payload, 270, 273);
uint8_t draught = get_bits(payload, 294, 301);
get_char(payload, dest, 302, 421);
bool dte = get_bits(payload, 422, 422);
#if HAL_LOGGING_ENABLED
if ((_log_options & AIS_OPTIONS_LOG_DECODED) != 0) {
struct log_AIS_msg5 pkt {
LOG_PACKET_HEADER_INIT(LOG_AIS_MSG5),
time_us : AP_HAL::micros64(),
repeat : repeat,
mmsi : mmsi,
ver : ver,
imo : imo,
call_sign : {},
name : {},
vessel_type : vessel_type,
bow_dim : bow_dim,
stern_dim : stern_dim,
port_dim : port_dim,
star_dim : star_dim,
fix : fix,
draught : draught,
dest : {},
dte : dte
};
strncpy(pkt.call_sign, call_sign, sizeof(pkt.call_sign));
strncpy(pkt.name, name, sizeof(pkt.name));
strncpy(pkt.dest, dest, sizeof(pkt.dest));
AP::logger().WriteBlock(&pkt, sizeof(pkt));
}
#else
(void)repeat;
(void)ver;
(void)imo;
(void)fix;
(void)draught;
(void)dte;
#endif
uint16_t index;
if (!get_vessel_index(mmsi, index)) {
return true;
}
const uint16_t mask = ~(AIS_FLAGS_VALID_DIMENSIONS | AIS_FLAGS_LARGE_BOW_DIMENSION | AIS_FLAGS_LARGE_STERN_DIMENSION | AIS_FLAGS_LARGE_STARBOARD_DIMENSION | AIS_FLAGS_VALID_CALLSIGN | AIS_FLAGS_VALID_NAME);
uint16_t flags = _list[index].info.flags & mask;
if (bow_dim != 0 && stern_dim != 0 && port_dim != 0 && star_dim != 0) {
flags |= AIS_FLAGS_VALID_DIMENSIONS;
if (bow_dim == 511) {
flags |= AIS_FLAGS_LARGE_BOW_DIMENSION;
}
if (stern_dim == 511) {
flags |= AIS_FLAGS_LARGE_STERN_DIMENSION;
}
if (port_dim == 63) {
flags |= AIS_FLAGS_LARGE_PORT_DIMENSION;
}
if (star_dim == 63) {
flags |= AIS_FLAGS_LARGE_STARBOARD_DIMENSION;
}
}
if (strlen(call_sign) != 0) {
flags |= AIS_FLAGS_VALID_CALLSIGN;
}
if (strlen(name) != 0) {
flags |= AIS_FLAGS_VALID_NAME;
}
_list[index].info.dimension_bow = bow_dim;
_list[index].info.dimension_stern = stern_dim;
_list[index].info.flags = flags;
_list[index].info.type = vessel_type;
_list[index].info.dimension_port = port_dim;
_list[index].info.dimension_starboard = star_dim;
memcpy(_list[index].info.callsign,call_sign,sizeof(_list[index].info.callsign));
memcpy(_list[index].info.name,name,sizeof(_list[index].info.name));
return true;
}
void AP_AIS::get_char(const char *payload, char *array, uint16_t low, uint16_t high)
{
bool found_char = false;
uint8_t length = ((high - low) + 1)/6;
for (uint8_t i = length; i > 0; i--) {
uint8_t ascii = get_bits(payload, low + (i-1)*6, (low + (i*6)) - 1);
if (ascii < 32) {
ascii += 64;
}
if (ascii == 64 || (ascii == 32 && !found_char)) {
array[i-1] = 0;
} else {
found_char = true;
array[i-1] = ascii;
}
}
array[length] = 0;
}
uint32_t AP_AIS::get_bits(const char *payload, uint16_t low, uint16_t high)
{
uint8_t char_low = low / 6;
uint8_t bit_low = low % 6;
uint8_t char_high = high / 6;
uint8_t bit_high = (high % 6) + 1;
uint32_t val = 0;
for (uint8_t index = 0; index <= char_high - char_low; index++) {
uint8_t value = payload_char_decode(payload[char_low + index]);
uint8_t mask = 0b111111;
if (index == 0) {
mask = mask >> bit_low;
}
value &= mask;
if (index == char_high - char_low) {
value = value >> (6 - bit_high);
val = val << bit_high;
} else {
val = val << 6;
}
val |= value;
}
return val;
}
int32_t AP_AIS::get_bits_signed(const char *payload, uint16_t low, uint16_t high)
{
uint32_t value = get_bits(payload, low, high);
if (get_bits(payload, low, low)) {
return value | (UINT32_MAX << (high - low));
}
return value;
}
uint8_t AP_AIS::payload_char_decode(const char c)
{
uint8_t value = c;
value -= 48;
if (value > 40) {
value -= 8;
}
return value;
}
#if HAL_LOGGING_ENABLED
void AP_AIS::log_raw(const AIVDM *msg)
{
struct log_AIS_raw pkt{
LOG_PACKET_HEADER_INIT(LOG_AIS_RAW_MSG),
time_us : AP_HAL::micros64(),
num : msg->num,
total : msg->total,
ID : msg->ID,
payload : {}
};
memcpy(pkt.payload, msg->payload, sizeof(pkt.payload));
AP::logger().WriteBlock(&pkt, sizeof(pkt));
}
#endif
bool AP_AIS::decode(char c)
{
switch (c) {
case ',':
_checksum ^= c;
FALLTHROUGH;
case '\r':
case '\n':
case '*':
{
if (_sentence_done) {
return false;
}
_term[_term_offset] = 0;
bool valid_sentence = decode_latest_term();
_term_number++;
_term_offset = 0;
_term_is_checksum = (c == '*');
return valid_sentence;
}
case '!':
_sentence_valid = false;
_term_number = 0;
_term_offset = 0;
_checksum = 0;
_term_is_checksum = false;
_sentence_done = false;
return false;
}
if (_term_offset < sizeof(_term) - 1) {
_term[_term_offset++] = c;
}
if (!_term_is_checksum) {
_checksum ^= c;
}
return false;
}
bool AP_AIS::decode_latest_term()
{
if (_term_is_checksum) {
_sentence_done = true;
uint8_t checksum = 16 * char_to_hex(_term[0]) + char_to_hex(_term[1]);
return ((checksum == _checksum) && _sentence_valid);
}
if (_term_number == 0) {
if (strcmp(_term, "AIVDM") == 0) {
_sentence_valid = true;
}
return false;
}
if (!_sentence_valid) {
return false;
}
switch (_term_number) {
case 1:
_incoming.total = strtol(_term, NULL, 10);
break;
case 2:
_incoming.num = strtol(_term, NULL, 10);
break;
case 3:
_incoming.ID = 0;
if (strlen(_term) > 0) {
_incoming.ID = strtol(_term, NULL, 10);
} else if (_incoming.num != 1 || _incoming.total != 1) {
_sentence_valid = false;
}
break;
case 5:
if (strlen(_term) == 0) {
_sentence_valid = false;
} else {
strcpy(_incoming.payload,_term);
}
break;
}
return false;
}
AP_AIS *AP_AIS::get_singleton() {
return _singleton;
}
#else
const AP_Param::GroupInfo AP_AIS::var_info[] = { AP_GROUPEND };
AP_AIS::AP_AIS() {};
bool AP_AIS::enabled() const { return false; }
void AP_AIS::init() {};
void AP_AIS::update() {};
void AP_AIS::send(mavlink_channel_t chan) {};
AP_AIS *AP_AIS::get_singleton() { return nullptr; }
#endif
AP_AIS *AP_AIS::_singleton;
#endif