def test_single(self): """ Test single and multi line messages """ single = b"!AIVDM,1,1,,B,91b55wi;hbOS@OdQAC062Ch2089h,0*30" assert NMEAMessage(single).is_single assert not NMEAMessage(single).is_multi
def test_msg_type_17(self): msg = NMEAMessage.assemble_from_iterable(messages=[ NMEAMessage( b"!AIVDM,2,1,5,A,A02VqLPA4I6C07h5Ed1h<OrsuBTTwS?r:C?w`?la<gno1RTRwSP9:BcurA8a,0*3A" ), NMEAMessage(b"!AIVDM,2,2,5,A,:Oko02TSwu8<:Jbb,0*11") ]).decode() n = 0x7c0556c07031febbf52924fe33fa2933ffa0fd2932fdb7062922fe3809292afde9122929fcf7002923ffd20c29aaaa assert msg['type'] == 17 assert msg['repeat'] == 0 assert msg['mmsi'] == "002734450" assert msg['lon'] == 17478 assert msg['lat'] == 35992 assert msg['data'] == n msg = NMEAMessage( b"!AIVDM,1,1,,A,A0476BQ>J8`<h2JpH:4P0?j@2mTEw8`=DP1DEnqvj0,0*79" ).decode() assert msg['type'] == 17 assert msg['repeat'] == 0 assert msg['mmsi'] == "004310602" assert msg['lat'] == 20582 assert msg['lon'] == 80290 assert msg[ 'data'] == 14486955885545814640451754168044205828166539334830080
def test_from_str(self): old = NMEAMessage(b"!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C" ).decode().content new = NMEAMessage.from_string( "!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C").decode().content assert old == new
def _assemble_messages(self): queue = [] for line in self._iter_messages(): # Try to parse the message try: msg = NMEAMessage(line) except Exception as e: raise ValueError(f'Failed to parse line "{line}"') from e # Be gentle and just skip invalid messages if not msg.is_valid: continue if msg.is_single: yield msg # Assemble multiline messages elif msg.is_multi: queue.append(msg) if msg.index == msg.count: yield msg.assemble_from_iterable(queue) queue.clear() else: raise ValueError("Messages are out of order!")
def test_dict(self): msg = b"!AIVDM,1,1,,A,15Mj23P000G?q7fK>g:o7@1:0L3S,0*1B" msg = NMEAMessage(msg) def serializable(o: object): if isinstance(o, bytes): return o.decode('utf-8') elif isinstance(o, bitarray): return o.to01() return o expected = dict([(slot, serializable(getattr(msg, slot))) for slot in NMEAMessage.__slots__]) actual = msg.asdict() pprint(actual) self.assertEqual(expected, actual) self.assertEqual(1, actual["ais_id"]) self.assertEqual("!AIVDM,1,1,,A,15Mj23P000G?q7fK>g:o7@1:0L3S,0*1B", actual["raw"]) self.assertEqual("AI", actual["talker"]) self.assertEqual("VDM", actual["type"]) self.assertEqual(1, actual["message_fragments"]) self.assertEqual(1, actual["fragment_number"]) self.assertEqual(None, actual["message_id"]) self.assertEqual("A", actual["channel"]) self.assertEqual("15Mj23P000G?q7fK>g:o7@1:0L3S", actual["payload"]) self.assertEqual(0, actual["fill_bits"]) self.assertEqual(0x1b, actual["checksum"])
def test_msg_type_1(self): msg = NMEAMessage(b"!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C").decode() assert msg.content == {'type': 1, 'repeat': 0, 'mmsi': "366053209", 'status': NavigationStatus.RestrictedManoeuverability, 'turn': 0, 'speed': 0, 'accuracy': 0, 'lon': -122.34161833333333, 'lat': 37.80211833333333, 'course': 219.3, 'heading': 1, 'second': 59, 'maneuver': ManeuverIndicator.NotAvailable, 'raim': False, 'radio': 2281} msg = NMEAMessage(b"!AIVDM,1,1,,A,15NPOOPP00o?b=bE`UNv4?w428D;,0*24").decode() assert msg['type'] == 1 assert msg['mmsi'] == "367533950" assert msg['repeat'] == 0 assert msg['status'] == NavigationStatus.UnderWayUsingEngine assert msg['turn'] == -128 assert msg['speed'] == 0 assert msg['accuracy'] == 1 assert round(msg['lat'], 4) == 37.8084 assert round(msg['lon'], 4) == -122.4082 assert msg['course'] == 360 assert msg['heading'] == 511 assert msg['second'] == 34 assert msg['maneuver'] == ManeuverIndicator.NotAvailable assert msg['raim']
def test_msg_type_21(self): msg = NMEAMessage.assemble_from_iterable(messages=[ NMEAMessage( b"!AIVDM,2,1,7,B,E4eHJhPR37q0000000000000000KUOSc=rq4h00000a,0*4A" ), NMEAMessage(b"!AIVDM,2,2,7,B,@20,4*54") ]).decode() assert msg['type'] == 21 assert msg['mmsi'] == "316021442" assert msg['aid_type'] == NavAid.REFERENCE_POINT assert msg['name'] == "DFO2" assert msg['accuracy'] == 1 assert round(msg['lat'], 2) == 48.65 assert round(msg['lon'], 2) == -123.43 assert not msg['to_bow'] assert not msg['to_stern'] assert not msg['to_port'] assert not msg['to_starboard'] assert msg['off_position'] assert msg['regional'] == 0 assert msg['raim'] assert msg['virtual_aid'] == 0 assert msg['assigned'] == 0 assert msg['name_extension'] == ""
def test_validity(self): msg = b"!AIVDM,1,1,,A,85Mwp`1Kf3aCnsNvBWLi=wQuNhA5t43N`5nCuI=p<IBfVqnMgPGs,0*47" assert NMEAMessage(msg).is_valid msg = b"!AIVDM,1,1,,A,85Mwp`1Kf3aCnsNvBWLi=wQuNhA5t43N`5nCuI=p<IBfVqnMgPGt,0*47" with self.assertRaises(InvalidChecksumException): NMEAMessage(msg)
def test_talker(self): """ Test talker extraction """ msg = b"!AIVDM,1,1,,B,91b55wi;hbOS@OdQAC062Ch2089h,0*30" assert NMEAMessage(msg).talker == "AI" msg = b"!AIVDM,1,1,,A,8@30oni?1j020@00,0*23" assert NMEAMessage(msg).talker == "AI"
def test_message_assembling(self): multi = NMEAMessage.assemble_from_iterable(messages=[ NMEAMessage(b"!AIVDM,2,1,4,A,55O0W7`00001L@gCWGA2uItLth@DqtL5@F22220j1h742t0Ht0000000,0*08"), NMEAMessage(b"!AIVDM,2,2,4,A,000000000000000,2*20") ]) assert not multi.is_single assert multi.is_multi assert multi.is_valid
def test_type(self): """ Test value type """ msg = b"!AIVDM,1,1,,B,91b55wi;hbOS@OdQAC062Ch2089h,0*30" assert NMEAMessage(msg).msg_type == "VDM" msg = b"!AIVDM,1,1,,A,8@30oni?1j020@00,0*23" assert NMEAMessage(msg).msg_type == "VDM"
def test_fail_silently(self): # this tests combines testing for an UnknownMessageException and the silent param at once msg = b"!AIVDM,1,1,,A,U31<0OOP000CshrMdl600?wP00SL,0*43" nmea = NMEAMessage(msg) with self.assertRaises(UnknownMessageException): nmea.decode(silent=False) # by default errors are ignored and None is returned assert nmea.decode() is None
def test_msg_type_26(self): msg = NMEAMessage(b"!AIVDM,1,1,,A,JB3R0GO7p>vQL8tjw0b5hqpd0706kh9d3lR2vbl0400,2*40").decode() assert msg['type'] == 26 assert msg['addressed'] assert msg['structured'] assert msg['dest_mmsi'] == "838351848" msg = NMEAMessage(b"!AIVDM,1,1,,A,J0@00@370>t0Lh3P0000200H:2rN92,4*14").decode() assert msg['type'] == 26 assert not msg['addressed'] assert not msg['structured']
def test_message_eq_method(self): msg = b"!AIVDM,1,1,,B,F030p:j2N2P5aJR0r;6f3rj10000,0*11" first_obj = NMEAMessage(msg) second_obj = NMEAMessage(msg) # make sure they are not the same object assert not id(first_obj) == id(second_obj) # but make sure they equal assert first_obj == second_obj
def test_msg_type_5(self): msg = NMEAMessage.assemble_from_iterable(messages=[ NMEAMessage(b"!AIVDM,2,1,1,A,55?MbV02;H;s<HtKR20EHE:0@T4@Dn2222222216L961O5Gf0NSQEp6ClRp8,0*1C"), NMEAMessage(b"!AIVDM,2,2,1,A,88888888880,2*25") ]).decode() assert msg['callsign'] == "3FOF8" assert msg['shipname'] == "EVER DIADEM" assert msg['shiptype'] == ShipType.Cargo assert msg['to_bow'] == 225 assert msg['to_stern'] == 70 assert msg['to_port'] == 1 assert msg['to_starboard'] == 31 assert msg['draught'] == 12.2 assert msg['destination'] == "NEW YORK"
def test_values(self): """ Test value count """ a = b"!AIVDM,,A,91b77=h3h00nHt0Q3r@@07000<0b,0*69" b = b"!AIVDM,1,1,,A,91b77=h3h00nHt0Q3r@@07000<0b,0*69,0,3" with self.assertRaises(InvalidNMEAMessageException): NMEAMessage(a) with self.assertRaises(InvalidNMEAMessageException): NMEAMessage(b) c = b"!AIVDM,1,1,,B,91b55wi;hbOS@OdQAC062Ch2089h,0*30" assert NMEAMessage(c).is_valid
def test_msg_type(self): """ Test if msg type is correct """ nmea = NMEAMessage(b"!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C") assert nmea.decode().msg_type == AISType.POS_CLASS_A1 nmea = NMEAMessage(b"!AIVDM,1,1,,B,15NG6V0P01G?cFhE`R2IU?wn28R>,0*05") assert nmea.decode().msg_type == AISType.POS_CLASS_A1 nmea = NMEAMessage.assemble_from_iterable(messages=[ NMEAMessage(b"!AIVDM,2,1,4,A,55O0W7`00001L@gCWGA2uItLth@DqtL5@F22220j1h742t0Ht0000000,0*08"), NMEAMessage(b"!AIVDM,2,2,4,A,000000000000000,2*20") ]) assert nmea.decode().msg_type == AISType.STATIC_AND_VOYAGE
def test_msg_type_25(self): msg = NMEAMessage(b"!AIVDM,1,1,,A,I6SWo?8P00a3PKpEKEVj0?vNP<65,0*73").decode() assert msg['type'] == 25 assert msg['addressed'] assert not msg['structured'] assert msg['dest_mmsi'] == "134218384"
def test_deprecated(self): msg = NMEAMessage(b"!AIVDM,1,1,,A,15Mj23P000G?q7fK>g:o7@1:0L3S,0*1B") self.assertEqual(msg.count, msg.fragment_count) self.assertEqual(msg.index, msg.fragment_number) self.assertEqual(msg.seq_id, msg.message_id) self.assertEqual(msg.data, msg.payload)
def test_to_json(self): json_dump = NMEAMessage(b"!AIVDM,1,1,,A,15NPOOPP00o?b=bE`UNv4?w428D;,0*24").decode().to_json() text = """{ "nmea": { "ais_id": 1, "raw": "!AIVDM,1,1,,A,15NPOOPP00o?b=bE`UNv4?w428D;,0*24", "talker": "AI", "msg_type": "VDM", "count": 1, "index": 1, "seq_id": "", "channel": "A", "data": "15NPOOPP00o?b=bE`UNv4?w428D;", "checksum": 36, "bit_array": "000001000101011110100000011111011111100000100000000000000000110111001111101010001101101010010101101000100101011110111110000100001111111111000100000010001000010100001011" }, "decoded": { "type": 1, "repeat": 0, "mmsi": "367533950", "status": 0, "turn": -128, "speed": 0.0, "accuracy": true, "lon": -122.40823166666667, "lat": 37.808418333333336, "course": 360.0, "heading": 511, "second": 34, "maneuver": 0, "raim": true, "radio": 34059 } }""" assert json_dump == text
def test_msg_getitem(self): """ Test if one can get items """ msg = NMEAMessage( b"!AIVDM,1,1,,B,15NG6V0P01G?cFhE`R2IU?wn28R>,0*05").decode() assert msg['repeat'] == 0
def test_msg_type_15(self): msg = NMEAMessage(b"!AIVDM,1,1,,A,?5OP=l00052HD00,2*5B").decode() assert msg['type'] == 15 assert msg['repeat'] == 0 assert msg['mmsi'] == "368578000" assert msg['offset1_1'] == 0 msg = NMEAMessage(b"!AIVDM,1,1,,B,?h3Ovn1GP<K0<P@59a0,2*04").decode() assert msg['type'] == 15 assert msg['repeat'] == 3 assert msg['mmsi'] == "003669720" assert msg['mmsi1'] == "367014320" assert msg['type1_1'] == 3 assert msg['type1_2'] == 5 assert msg['offset1_2'] == 617 assert msg['offset1_1'] == 516
def test_msg_type_14(self): msg = NMEAMessage( b"!AIVDM,1,1,,A,>5?Per18=HB1U:1@E=B0m<L,2*51").decode() assert msg['type'] == 14 assert msg['repeat'] == 0 assert msg['mmsi'] == "351809000" assert msg['text'] == "RCVD YR TEST MSG"
def test_msg_type_6(self): msg = NMEAMessage( b"!AIVDM,1,1,,B,6B?n;be:cbapalgc;i6?Ow4,2*4A").decode() assert msg['seqno'] == 3 assert msg['dest_mmsi'] == "313240222" assert msg['dac'] == 669 assert msg['fid'] == 11
def test_get_item_raises_type_error(self): msg = NMEAMessage(b"!AIVDM,1,1,,A,15Mj23P000G?q7fK>g:o7@1:0L3S,0*1B") with self.assertRaises(TypeError): _ = msg[1] with self.assertRaises(TypeError): _ = msg[1:3]
def test_msg_type_23(self): msg = NMEAMessage(b"!AIVDM,1,1,,B,G02:Kn01R`sn@291nj600000900,2*12").decode() assert msg['type'] == 23 assert msg['mmsi'] == "002268120" assert msg['ne_lon'] == 157.8 assert round(msg['ne_lat'], 1) == 3064.2 assert round(msg['sw_lon'], 1) == 109.6 assert round(msg['sw_lat'], 1) == 3040.8
def test_fail_silently(self): # this tests combines testing for an UnknownMessageException and the silent param at once msg = b"!AIVDM,1,1,,A,U31<0OOP000CshrMdl600?wP00SL,0*43" nmea = NMEAMessage(msg) with self.assertRaises(UnknownMessageException): nmea.decode(silent=False) # by default errors are ignored and an empty AIS message is returned assert nmea.decode() is not None assert isinstance(nmea.decode(), AISMessage) text = """{ "nmea": { "ais_id": 37, "raw": "!AIVDM,1,1,,A,U31<0OOP000CshrMdl600?wP00SL,0*43", "talker": "AI", "type": "VDM", "message_fragments": 1, "fragment_number": 1, "message_id": null, "channel": "A", "payload": "U31<0OOP000CshrMdl600?wP00SL", "fill_bits": 0, "checksum": 67, "bit_array": "100101000011000001001100000000011111011111100000000000000000000000010011111011110000111010011101101100110100000110000000000000001111111111100000000000000000100011011100" }, "decoded": {} }""" self.assertEqual(nmea.decode().to_json(), text)
def test_msg_type_12(self): msg = NMEAMessage(b"!AIVDM,1,1,,A,<5?SIj1;GbD07??4,0*38").decode() assert msg['type'] == 12 assert msg['repeat'] == 0 assert msg['mmsi'] == "351853000" assert msg['seqno'] == 0 assert msg['dest_mmsi'] == "316123456" assert msg['retransmit'] == 0 assert msg['text'] == "GOOD" msg = NMEAMessage(b"!AIVDM,1,1,,A,<42Lati0W:Ov=C7P6B?=Pjoihhjhqq0,2*2B").decode() assert msg['type'] == 12 assert msg['repeat'] == 0 assert msg['mmsi'] == "271002099" assert msg['seqno'] == 0 assert msg['dest_mmsi'] == "271002111" assert msg['retransmit'] == 1 assert msg['text'] == "MSG FROM 271002099"
def test_decode_pos_1_2_3(self): # weired message of type 0 as part of issue #4 msg: NMEAMessage = NMEAMessage(b"!AIVDM,1,1,,B,0S9edj0P03PecbBN`ja@0?w42cFC,0*7C") assert msg.is_valid content: AISMessage = msg.decode(silent=False) assert msg assert content['repeat'] == 2 assert content['mmsi'] == "211512520" assert content['turn'] == -128 assert content['speed'] == 0.3 assert round(content['lat'], 4) == 53.5427 assert round(content['lon'], 4) == 9.9794 assert round(content['course'], 1) == 0.0 msg: NMEAMessage = NMEAMessage(b"!AIVDM,1,1,,B,0S9edj0P03PecbBN`ja@0?w42cFC,0*7C") assert msg.decode().to_json()
def test_msg_type_22(self): # Broadcast msg = NMEAMessage( b"!AIVDM,1,1,,B,F030p:j2N2P5aJR0r;6f3rj10000,0*11").decode() assert msg['type'] == 22 assert msg['mmsi'] == "003160107" assert msg['channel_a'] == 2087 assert msg['channel_b'] == 2088 assert msg['power'] == 0 assert msg['ne_lon'] == -7710.0 assert msg['ne_lat'] == 3300.0 assert msg['sw_lon'] == -8020.0 assert msg['sw_lat'] == 3210 assert msg['band_a'] == 0 assert msg['band_b'] == 0 assert msg['zonesize'] == 2 assert 'dest1' not in msg.content.keys() assert 'dest2' not in msg.content.keys() # Addressed msg = NMEAMessage( b"!AIVDM,1,1,,A,F@@W>gOP00PH=JrN9l000?wB2HH;,0*44").decode() assert msg['type'] == 22 assert msg['mmsi'] == "017419965" assert msg['channel_a'] == 3584 assert msg['channel_b'] == 8 assert msg['power'] == 1 assert msg['addressed'] == 1 assert msg['dest1'] == "028144881" assert msg['dest2'] == "268435519" assert msg['band_a'] == 0 assert msg['band_b'] == 0 assert msg['zonesize'] == 4 assert 'ne_lon' not in msg.content.keys() assert 'ne_lat' not in msg.content.keys() assert 'sw_lon' not in msg.content.keys() assert 'sw_lat' not in msg.content.keys()