From 567d58fa4a623df15617a89db4534c4610c45d21 Mon Sep 17 00:00:00 2001 From: Kurt Schwehr Date: Mon, 17 Dec 2012 09:02:56 -0800 Subject: [PATCH] uscg areanotice 8:367:22 --- Makefile-custom | 1 + ais.h | 100 ++++++++++++++++++++++++++++ ais8_367.cpp | 157 ++++++++++++++++++++++++++++++++++++++++++++ ais_py.cpp | 144 ++++++++++++++++++++++++++++++++++++++++ setup.py | 1 + test/test_data.py | 149 +++++++++++++++++++++++++++++++++++++++++ test/test_decode.py | 1 - test_libais.cpp | 8 ++- 8 files changed, 559 insertions(+), 2 deletions(-) create mode 100644 ais8_367.cpp diff --git a/Makefile-custom b/Makefile-custom index e1ba538e..562dfa72 100644 --- a/Makefile-custom +++ b/Makefile-custom @@ -64,6 +64,7 @@ SRCS += ais8_001_22.cpp # Area notice SRCS += ais8_001_26.cpp # Env Sensors # US Specific Messages SRCS += ais8_366_22.cpp +SRCS += ais8_367.cpp SRCS += ais9.cpp SRCS += ais10.cpp diff --git a/ais.h b/ais.h index 8cc9de8c..5ca86a8e 100644 --- a/ais.h +++ b/ais.h @@ -1235,6 +1235,106 @@ extern const char *ais8_366_22_notice_names[AIS8_366_22_NUM_NAMES]; // 366 34 - Kurt older whale message 2008-2010 // TODO(schwehr): Ais8_366_34 +class Ais8_367_22_SubArea { + public: + virtual Ais8_366_22_AreaShapeEnum getType()=0; + virtual ~Ais8_367_22_SubArea() { } +}; + +Ais8_367_22_SubArea* +ais8_367_22_subarea_factory(const bitset &bs, + const size_t offset); + +class Ais8_367_22_Circle : public Ais8_367_22_SubArea { + public: + float x, y; + int precision; + int radius_m; + unsigned int spare; + + Ais8_367_22_Circle(const bitset &bs, const size_t offset); + ~Ais8_367_22_Circle() {} + Ais8_366_22_AreaShapeEnum getType() {return AIS8_366_22_SHAPE_CIRCLE;} +}; + +class Ais8_367_22_Rect : public Ais8_367_22_SubArea { + public: + float x, y; + int precision; + int e_dim_m; + int n_dim_m; + int orient_deg; + unsigned int spare; + + Ais8_367_22_Rect(const bitset &bs, const size_t offset); + ~Ais8_367_22_Rect() {} + Ais8_366_22_AreaShapeEnum getType() {return AIS8_366_22_SHAPE_RECT;} +}; + +class Ais8_367_22_Sector : public Ais8_367_22_SubArea { + public: + float x, y; + int precision; + int radius_m; + int left_bound_deg; + int right_bound_deg; + int spare; + + Ais8_367_22_Sector(const bitset &bs, const size_t offset); + ~Ais8_367_22_Sector() {} + Ais8_366_22_AreaShapeEnum getType() {return AIS8_366_22_SHAPE_SECTOR;} +}; + +// Polyline or Polygon +class Ais8_367_22_Poly : public Ais8_367_22_SubArea { + public: + Ais8_366_22_AreaShapeEnum shape; + float x, y; + int precision; + + // Up to 4 points + vector angles; + vector dists_m; + unsigned int spare; + + Ais8_367_22_Poly(const bitset &bs, const size_t offset); + ~Ais8_367_22_Poly() {} + Ais8_366_22_AreaShapeEnum getType() {return shape;} +}; + +class Ais8_367_22_Text : public Ais8_367_22_SubArea { + public: + string text; + unsigned int spare; // 3 bits + + Ais8_367_22_Text(const bitset &bs, const size_t offset); + ~Ais8_367_22_Text() {} + Ais8_366_22_AreaShapeEnum getType() {return AIS8_366_22_SHAPE_TEXT;} +}; + +class Ais8_367_22 : public Ais8 { + public: + int version; + int link_id; + int notice_type; + int month; + int day; + int hour; + int minute; + int duration_minutes; + int spare2; + + vector sub_areas; + + Ais8_367_22(const char *nmea_payload, const size_t pad); + ~Ais8_367_22(); +}; +ostream& operator<< (ostream& o, Ais8_367_22 const& msg); + +//const size_t AIS8_366_22_NUM_NAMES = 128; +//extern const char *ais8_366_22_notice_names[AIS8_366_22_NUM_NAMES]; + + class Ais9 : public AisMsg { public: int alt; // m above sea level diff --git a/ais8_367.cpp b/ais8_367.cpp new file mode 100644 index 00000000..2c466617 --- /dev/null +++ b/ais8_367.cpp @@ -0,0 +1,157 @@ +// 8:367:22 Defined by an email from Greg Johnson representing the +// USCG, Fall 2012. Breaks from the RTCM and IMO Circular 289. +// "Area Notice Message Release Version: 1" 13 Aug 2012 + +#include + +#include "ais.h" + +const size_t SUB_AREA_BITS = 96; + +static int scale_multipliers[4] = {1, 10, 100, 1000}; + +Ais8_367_22_Circle::Ais8_367_22_Circle(const bitset &bs, + const size_t offset) { + const int scale_factor = ubits(bs, offset + 3, 2); + x = sbits(bs, offset + 5, 28) / 600000.; + y = sbits(bs, offset + 33, 27) / 600000.; + precision = ubits(bs, offset + 60, 3); + radius_m = ubits(bs, offset + 63, 12) * scale_multipliers[scale_factor]; + spare = ubits(bs, offset + 75, 21); +} + +Ais8_367_22_Rect::Ais8_367_22_Rect(const bitset &bs, + const size_t offset) { + const int scale_factor = ubits(bs, offset + 3, 2); + x = sbits(bs, offset + 5, 28) / 600000.; + y = sbits(bs, offset + 33, 27) / 600000.; + precision = ubits(bs, offset + 60, 3); + e_dim_m = ubits(bs, offset + 63, 8) * scale_multipliers[scale_factor]; + n_dim_m = ubits(bs, offset + 71, 8) * scale_multipliers[scale_factor]; + orient_deg = ubits(bs, offset + 79, 9); + spare = ubits(bs, offset + 88, 8); +} + +Ais8_367_22_Sector::Ais8_367_22_Sector(const bitset &bs, + const size_t offset) { + const int scale_factor = ubits(bs, offset + 3, 2); + x = sbits(bs, offset + 5, 28) / 600000.; + y = sbits(bs, offset + 33, 27) / 600000.; + precision = ubits(bs, offset + 60, 3); + radius_m = ubits(bs, offset + 63, 12) * scale_multipliers[scale_factor]; + left_bound_deg = ubits(bs, offset + 75, 9); + right_bound_deg = ubits(bs, offset + 84, 9); + spare = ubits(bs, offset + 93, 3); +} + +// polyline or polygon +Ais8_367_22_Poly::Ais8_367_22_Poly(const bitset &bs, + const size_t offset) { + shape = static_cast(ubits(bs, offset, 3)); + const int scale_factor = ubits(bs, offset + 3, 2); + for (size_t i = 0; i < 4; i++) { + const int angle = ubits(bs, offset + 5 + (i*21), 10); + const int dist = + ubits(bs, offset + 15 + (i*21), 11) * scale_multipliers[scale_factor]; + if (0 == dist) + break; + angles.push_back(angle); + dists_m.push_back(dist); + } + spare = ubits(bs, offset + 89, 7); +} + +Ais8_367_22_Text::Ais8_367_22_Text(const bitset &bs, + const size_t offset) { + text = string(ais_str(bs, offset + 3, 90)); + spare = ubits(bs, offset + 90, 3); +} + + +Ais8_367_22::Ais8_367_22(const char *nmea_payload, const size_t pad) + : Ais8(nmea_payload, pad) { + if (status != AIS_UNINITIALIZED) + return; + + assert(dac == 367); + assert(fi == 22); + + const int num_bits = (strlen(nmea_payload) * 6) - pad; + if (num_bits <= 216 && num_bits >= 1016) { + status = AIS_ERR_BAD_BIT_COUNT; + return; + } + + bitset bs; + const AIS_STATUS r = aivdm_to_bits(bs, nmea_payload); + if (r != AIS_OK) { + status = r; + return; + } + + version = ubits(bs, 56, 6); + link_id = ubits(bs, 62, 10); + notice_type = ubits(bs, 72, 7); + month = ubits(bs, 79, 4); + day = ubits(bs, 83, 5); + hour = ubits(bs, 88, 5); + minute = ubits(bs, 93, 6); + duration_minutes = ubits(bs, 99, 18); + spare2 = ubits(bs, 117, 3); + + const int num_sub_areas = static_cast(floor((num_bits - 120)/float(SUB_AREA_BITS))); + + // TODO(schwehr): warn if we see extra bits + for (int area_idx = 0; area_idx < num_sub_areas; area_idx++) { + const size_t start = 120 + area_idx*SUB_AREA_BITS; + Ais8_367_22_SubArea *area = + ais8_367_22_subarea_factory(bs, start); + if (area) + sub_areas.push_back(area); + else + status = AIS_ERR_BAD_SUB_SUB_MSG; + } + +} + +Ais8_367_22::~Ais8_367_22() { + for (size_t i = 0; i < sub_areas.size(); i++) { + delete sub_areas[i]; + sub_areas[i] = NULL; + } +} + +Ais8_367_22_SubArea * +ais8_367_22_subarea_factory(const bitset &bs, + const size_t offset) { + const Ais8_366_22_AreaShapeEnum area_shape = + static_cast(ubits(bs, offset, 3)); + Ais8_367_22_SubArea *area = NULL; + switch (area_shape) { + case AIS8_366_22_SHAPE_CIRCLE: + area = new Ais8_367_22_Circle(bs, offset); + break; + case AIS8_366_22_SHAPE_RECT: + area = new Ais8_367_22_Rect(bs, offset); + return area; + case AIS8_366_22_SHAPE_SECTOR: + area = new Ais8_367_22_Sector(bs, offset); + break; + case AIS8_366_22_SHAPE_POLYLINE: // FALLTHROUGH + case AIS8_366_22_SHAPE_POLYGON: + area = new Ais8_367_22_Poly(bs, offset); + break; + case AIS8_366_22_SHAPE_TEXT: + area = new Ais8_367_22_Text(bs, offset); + break; + case AIS8_366_22_SHAPE_RESERVED_6: // FALLTHROUGH + case AIS8_366_22_SHAPE_RESERVED_7: // FALLTHROUGH + // Leave area as 0 to indicate error + break; + case AIS8_366_22_SHAPE_ERROR: + break; + default: + assert(false); + } + return area; +} diff --git a/ais_py.cpp b/ais_py.cpp index 9b449360..55af7744 100644 --- a/ais_py.cpp +++ b/ais_py.cpp @@ -923,6 +923,8 @@ ais8_1_22_append_pydict(const char *nmea_payload, PyObject *dict, DictSafeSetItem(dict, "hour", msg.hour); DictSafeSetItem(dict, "minute", msg.minute); + DictSafeSetItem(dict, "durations_minutes", msg.minute); + PyObject *sub_area_list = PyList_New(msg.sub_areas.size()); // Loop over sub_areas @@ -1539,6 +1541,138 @@ ais8_200_55_append_pydict(const char *nmea_payload, PyObject *dict, DictSafeSetItem(dict, "spare2", spare2_list); } +void +ais8_367_22_append_pydict(const char *nmea_payload, PyObject *dict, + const size_t pad) { + Ais8_367_22 msg(nmea_payload, pad); // TODO(schwehr): check for errors + + DictSafeSetItem(dict, "version", msg.version); + DictSafeSetItem(dict, "link_id", msg.link_id); + DictSafeSetItem(dict, "notice_type", msg.notice_type); + // TODO(schwehr): are 8:1:22 and 8:367:22 tables the same? + DictSafeSetItem(dict, "notice_type_str", + ais8_001_22_notice_names[msg.notice_type]); + + DictSafeSetItem(dict, "month", msg.month); // This is UTC, not local time. + DictSafeSetItem(dict, "day", msg.day); + DictSafeSetItem(dict, "hour", msg.hour); + DictSafeSetItem(dict, "minute", msg.minute); + + DictSafeSetItem(dict, "durations_minutes", msg.duration_minutes); + + PyObject *sub_area_list = PyList_New(msg.sub_areas.size()); + + // Loop over sub_areas + for (size_t i = 0; i < msg.sub_areas.size(); i++) { + switch (msg.sub_areas[i]->getType()) { + case AIS8_366_22_SHAPE_CIRCLE: // or point + { + PyObject *sub_area = PyDict_New(); + Ais8_367_22_Circle *c = + reinterpret_cast(msg.sub_areas[i]); + + DictSafeSetItem(sub_area, "sub_area_type", AIS8_366_22_SHAPE_CIRCLE); + if (c->radius_m == 0) + DictSafeSetItem(sub_area, "sub_area_type_str", "point"); + else + DictSafeSetItem(sub_area, "sub_area_type_str", "circle"); + + DictSafeSetItem(sub_area, "x", c->x); + DictSafeSetItem(sub_area, "y", c->y); + DictSafeSetItem(sub_area, "precision", c->precision); + DictSafeSetItem(sub_area, "radius", c->radius_m); + PyList_SetItem(sub_area_list, i, sub_area); + } + break; + case AIS8_366_22_SHAPE_RECT: + { + PyObject *sub_area = PyDict_New(); + Ais8_367_22_Rect *c = + reinterpret_cast(msg.sub_areas[i]); + + DictSafeSetItem(sub_area, "sub_area_type", AIS8_366_22_SHAPE_RECT); + DictSafeSetItem(sub_area, "sub_area_type_str", "rect"); + + DictSafeSetItem(sub_area, "x", c->x); + DictSafeSetItem(sub_area, "y", c->y); + DictSafeSetItem(sub_area, "precision", c->precision); + DictSafeSetItem(sub_area, "e_dim_m", c->e_dim_m); + DictSafeSetItem(sub_area, "n_dim_m", c->n_dim_m); + DictSafeSetItem(sub_area, "orient_deg", c->orient_deg); + + PyList_SetItem(sub_area_list, i, sub_area); + } + break; + case AIS8_366_22_SHAPE_SECTOR: + { + PyObject *sub_area = PyDict_New(); + Ais8_367_22_Sector *c = + reinterpret_cast(msg.sub_areas[i]); + + DictSafeSetItem(sub_area, "sub_area_type", AIS8_366_22_SHAPE_SECTOR); + DictSafeSetItem(sub_area, "sub_area_type_str", "sector"); + + DictSafeSetItem(sub_area, "x", c->x); + DictSafeSetItem(sub_area, "y", c->y); + DictSafeSetItem(sub_area, "precision", c->precision); + DictSafeSetItem(sub_area, "radius", c->radius_m); + DictSafeSetItem(sub_area, "left_bound_deg", c->left_bound_deg); + DictSafeSetItem(sub_area, "right_bound_deg", c->right_bound_deg); + + PyList_SetItem(sub_area_list, i, sub_area); + } + break; + case AIS8_366_22_SHAPE_POLYLINE: // FALLTHROUGH + case AIS8_366_22_SHAPE_POLYGON: + { + PyObject *sub_area = PyDict_New(); + Ais8_367_22_Poly *poly = + reinterpret_cast(msg.sub_areas[i]); + + DictSafeSetItem(sub_area, "sub_area_type", msg.sub_areas[i]->getType()); + if (msg.sub_areas[i]->getType() == AIS8_366_22_SHAPE_POLYLINE) + DictSafeSetItem(sub_area, "sub_area_type_str", "polyline"); + else + DictSafeSetItem(sub_area, "sub_area_type_str", "polygon"); + assert(polyline->angles.size() == poly->dists_m.size()); + PyObject *angle_list = PyList_New(poly->angles.size()); + PyObject *dist_list = PyList_New(poly->angles.size()); + + for (size_t pt_num = 0; pt_num < poly->angles.size(); pt_num++) { + PyList_SetItem(angle_list, pt_num, + PyFloat_FromDouble(poly->angles[pt_num])); + PyList_SetItem(dist_list, pt_num, + PyFloat_FromDouble(poly->dists_m[pt_num])); + } + + DictSafeSetItem(sub_area, "angles", angle_list); + DictSafeSetItem(sub_area, "dists_m", dist_list); + + PyList_SetItem(sub_area_list, i, sub_area); + } + break; + case AIS8_366_22_SHAPE_TEXT: + { + PyObject *sub_area = PyDict_New(); + + Ais8_367_22_Text *text = + reinterpret_cast(msg.sub_areas[i]); + DictSafeSetItem(sub_area, "sub_area_type", AIS8_366_22_SHAPE_TEXT); + DictSafeSetItem(sub_area, "sub_area_type_str", "text"); + + DictSafeSetItem(sub_area, "text", text->text); + + PyList_SetItem(sub_area_list, i, sub_area); + } + break; + + default: + {} // TODO(schwehr): Mark an unknown subarea or raise an exception. + } + } + DictSafeSetItem(dict, "sub_areas", sub_area_list); +} + // AIS Binary broadcast messages. There will be a huge number of subtypes // If we don't know how to decode it, just return the dac, fi @@ -1675,6 +1809,16 @@ ais8_to_pydict(const char *nmea_payload, const size_t pad) { // case 366: // United states // TODO(schwehr): implement // break; + case 367: // United states + switch (msg.fi) { + case 22: // USCG Area Notice 2012 v1 + ais8_367_22_append_pydict(nmea_payload, dict, pad); + break; + default: + DictSafeSetItem(dict, "parsed", false); + break; + } + break; default: DictSafeSetItem(dict, "parsed", false); // TODO(schwehr): raise exception or return standin? diff --git a/setup.py b/setup.py index 37b7ca58..bfb86260 100755 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ 'ais8.cpp', # Broadcast binary message (BBM) 'ais8_001_22.cpp', # Area notice 'ais8_001_26.cpp', # Environmental Sensor Report + 'ais8_367.cpp', 'ais9.cpp', 'ais10.cpp', # : # 11 See 4 - ; diff --git a/test/test_data.py b/test/test_data.py index 34651392..d1d5221b 100644 --- a/test/test_data.py +++ b/test/test_data.py @@ -200,6 +200,155 @@ 'y': 37.4383659362793} }, + { + 'nmea': ['!AIVDM,1,1,0,A,85M:Ih1KmPAU6jAs85`03cJm;1NHQhPFP000,0*19',], + 'result': {'dac': 367, + 'day': 4, + 'durations_minutes': 2880, + 'fi': 22, + 'hour': 15, + 'id': 8, + 'link_id': 101, + 'minute': 25, + 'mmsi': 366123456, + 'month': 9, + 'notice_type': 13, + 'notice_type_str': 'Caution Area: Survey operations', + 'repeat_indicator': 0, + 'spare': 0, + 'sub_areas': [{ + 'sub_area_type': 0, + 'sub_area_type_str': 'circle', + 'precision': 4, + 'radius': 1800, + 'x': -71.93499755859375, + 'y': 41.23666763305664}], + 'version': 1} + }, + + { + 'nmea': ['!AIVDM,1,1,0,A,85M:Ih1KmPAVhjAs80e0;cKBN1N:W8Q@:2`0,0*0C'], + 'result': {'dac': 367, + 'day': 4, + 'durations_minutes': 360, + 'fi': 22, + 'hour': 15, + 'id': 8, + 'link_id': 102, + 'minute': 25, + 'mmsi': 366123456, + 'month': 9, + 'notice_type': 97, + 'notice_type_str': 'Chart Feature: Submerged object', + 'repeat_indicator': 0, + 'spare': 0, + 'sub_areas': [{'e_dim_m': 400, + 'n_dim_m': 200, + 'orient_deg': 42, + 'precision': 4, + 'sub_area_type': 1, + 'sub_area_type_str': 'rect', + 'x': -71.91000366210938, + 'y': 41.141666412353516}], + 'version': 1} + }, + + { + 'nmea': ['!AIVDM,1,1,0,A,85M:Ih1KmPAW5BAs80e0EcN<11N6th@6BgL8,0*13'], + 'result': {'dac': 367, + 'day': 4, + 'durations_minutes': 360, + 'fi': 22, + 'hour': 15, + 'id': 8, + 'link_id': 103, + 'minute': 25, + 'mmsi': 366123456, + 'month': 9, + 'notice_type': 10, + 'notice_type_str': 'Caution Area: Divers down', + 'repeat_indicator': 0, + 'spare': 0, + 'sub_areas': [{'left_bound_deg': 175, + 'precision': 2, + 'radius': 5000, + 'right_bound_deg': 225, + 'sub_area_type': 2, + 'sub_area_type_str': 'sector', + 'x': -71.75166320800781, + 'y': 41.11666488647461}], + 'version': 1} + }, + + { + 'nmea': ['!AIVDM,2,1,0,A,85M:Ih1KmPA`tBAs85`01cON31N;U`P00000H;Gl1gfp52tjFq20H3r9P000,0*64', + '!AIVDM,2,2,0,A,00000000bPbJT1Q9hd680000,0*03' + ], + 'result': {'dac': 367, + 'day': 4, + 'durations_minutes': 2880, + 'fi': 22, + 'hour': 15, + 'id': 8, + 'link_id': 104, + 'minute': 25, + 'mmsi': 366123456, + 'month': 9, + 'notice_type': 120, + 'notice_type_str': 'Route: Recommended route', + 'repeat_indicator': 0, + 'spare': 0, + 'sub_areas': [{'precision': 4, + 'radius': 0, + 'sub_area_type': 0, + 'sub_area_type_str': 'point', + 'x': -71.6816635131836, + 'y': 41.14833450317383}, + {'angles': [90.0, 111.0, 40.0, 150.0], + 'dists_m': [2000.0, 1500.0, 755.0, 1825.0], + 'sub_area_type': 3, + 'sub_area_type_str': 'polyline'}, + {'angles': [31.0], + 'dists_m': [550.0], + 'sub_area_type': 3, + 'sub_area_type_str': 'polyline'}, + {'sub_area_type': 5, + 'sub_area_type_str': 'text', + 'text': 'TEST LINE 1@@@@'}], + 'version': 1} + +}, + + { + 'nmea': ['!AIVDM,1,1,0,A,85M:Ih1KmPAa8jAs85`01cN:41NI@`P00000P7Td4dUP00000000,0*71'], + 'result': {'dac': 367, + 'day': 4, + 'durations_minutes': 2880, + 'fi': 22, + 'hour': 15, + 'id': 8, + 'link_id': 105, + 'minute': 25, + 'mmsi': 366123456, + 'month': 9, + 'notice_type': 17, + 'notice_type_str': 'Caution Area: Cluster of fishing vessels', + 'repeat_indicator': 0, + 'spare': 0, + 'sub_areas': [{'precision': 4, + 'radius': 0, + 'sub_area_type': 0, + 'sub_area_type_str': 'point', + 'x': -71.75333404541016, + 'y': 41.24166488647461}, + {'angles': [60.0, 300.0], + 'dists_m': [1200.0, 1200.0], + 'sub_area_type': 4, + 'sub_area_type_str': 'polygon'}], + 'version': 1} + + }, + { 'nmea': [ '!AIVDM,1,1,,B,9oVAuAI5;rRRv2OqTi?1uoP?=a@1,0*74,raishub,1342572824' ], 'result': {'alt': 2324, diff --git a/test/test_decode.py b/test/test_decode.py index a16bbf34..4d344e7a 100755 --- a/test/test_decode.py +++ b/test/test_decode.py @@ -20,7 +20,6 @@ def testAll(self): body = ''.join([line.split(',')[5] for line in entry['nmea']]) pad = int(entry['nmea'][-1].split('*')[0][-1]) msg = ais.decode(body, pad) - print msg self.assertDictEqual(msg, entry['result']) diff --git a/test_libais.cpp b/test_libais.cpp index b7726237..fa21ea1d 100644 --- a/test_libais.cpp +++ b/test_libais.cpp @@ -35,7 +35,6 @@ void ASSERT_EQ(const bool a, const bool b, const int line) { int main(UNUSED int argc, UNUSED char* argv[]) { BuildNmeaLookup(); - if (true) { { // !AIVDM,1,1,,B,15Mq4J0P01EREODRv4@74gv00HRq,0*72,b003669970,1272412824 @@ -186,6 +185,13 @@ int main(UNUSED int argc, UNUSED char* argv[]) { #endif } + if (true) { + const string nmea("!AIVDM,1,1,0,A,85M:Ih1KmPAU6jAs85`03cJm;1NHQhPFP000,0*19"); + const string body = GetNthField(nmea, 5, ","); + const int pad = GetPad(nmea); + Ais8_367_22 msg(body.c_str(), pad); + } + // 9 - Search and rescue if (true) { // !AIVDM,1,1,,B,9002=mQq1oIJvt6;2eUn>Sh0040<,0*5D,b003669979,1273709011