From 7f46e35e752c4138dfad2f91fe9cde0cf4bb291b Mon Sep 17 00:00:00 2001 From: Leon Morten Richter Date: Sat, 8 May 2021 17:31:32 +0200 Subject: [PATCH] Correctly limit the maximum message size and allow empty channels (#29) --- docs/conversion.rst | 22 ++++++++++++++++++ docs/usage.rst | 5 +++-- pyais/messages.py | 11 +++------ tests/test_ais.py | 48 ++++++++++++++++++++++++++++++++++++++++ tests/test_decode_raw.py | 1 - 5 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 docs/conversion.rst diff --git a/docs/conversion.rst b/docs/conversion.rst new file mode 100644 index 0000000..572dd21 --- /dev/null +++ b/docs/conversion.rst @@ -0,0 +1,22 @@ +############ +Conversion +############ + +The following fields are directly converted to floats without **ANY** conversion: + +1. Turn +2. Speed (speed over ground) +3. Longitude +4. Latitude +5. Course (Course over ground) +6. `to_bow`, `to_stern`, `to_port`, `to_starboard` +7. `ne_lon`, `ne_lat`, `sw_lon`, `sw_lat` + +All of these values are native ``floats``. This means that you need to convert the value into the format of choice. + +A common use case is to convert the values into strings, with fixed sized precision:: + + content = decode_msg("!AIVDO,1,1,,,B>qc:003wk?8mP=18D3Q3wgTiT;T,0*13") + print(content["speed"]) #=> 102.30000000000001 + print(format(content["speed"], ".1f")) #=> 102.3 + print(f"{content['speed'] :.6f}") #=> 102.300000 diff --git a/docs/usage.rst b/docs/usage.rst index 937c302..87c54b3 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -1,6 +1,6 @@ -############ +################ Usage examples -############ +################ .. toctree:: :maxdepth: 1 @@ -8,3 +8,4 @@ Usage examples examples/single examples/file examples/sockets + conversion diff --git a/pyais/messages.py b/pyais/messages.py index 9bc813d..1749f74 100644 --- a/pyais/messages.py +++ b/pyais/messages.py @@ -45,11 +45,6 @@ def validate_message(msg: bytes) -> None: "Sentence number is empty!" ) - if not values[4]: - raise InvalidNMEAMessageException( - "The AIS channel (A or B) is empty." - ) - if not values[5]: raise InvalidNMEAMessageException( "The NMEA message body (payload) is empty." @@ -95,10 +90,10 @@ def validate_message(msg: bytes) -> None: "Invalid sequential message ID. No Number." ) - # It should not have more than 82 chars (including starting $ or ! and ) - if len(msg) > 82: + # It should not have more than 82 chars of payload + if len(values[5]) > 82: raise InvalidNMEAMessageException( - f"{msg.decode('utf-8')} has more than 82 characters." + f"{msg.decode('utf-8')} has more than 82 characters of payload." ) # Only encapsulated messages are currently supported diff --git a/tests/test_ais.py b/tests/test_ais.py index 0639a36..6865cbc 100644 --- a/tests/test_ais.py +++ b/tests/test_ais.py @@ -1,5 +1,6 @@ import unittest +from pyais import decode_msg from pyais.ais_types import AISType from pyais.constants import ManeuverIndicator, NavigationStatus, ShipType, NavAid, EpfdType from pyais.exceptions import UnknownMessageException @@ -583,3 +584,50 @@ def test_fail_silently(self): "decoded": {} }""" self.assertEqual(nmea.decode().to_json(), text) + + def test_empty_channel(self): + msg = b"!AIVDO,1,1,,,B>qc:003wk?8mP=18D3Q3wgTiT;T,0*13" + + self.assertEqual(NMEAMessage(msg).channel, "") + + content = decode_msg(msg) + self.assertEqual(content["type"], 18) + self.assertEqual(content["repeat"], 0) + self.assertEqual(content["mmsi"], "1000000000") + self.assertEqual(format(content["speed"], ".1f"), "102.3") + self.assertEqual(content["accuracy"], 0) + self.assertEqual(str(content["lon"]), "181.0") + self.assertEqual(str(content["lat"]), "91.0") + self.assertEqual(str(content["course"]), "360.0") + self.assertEqual(content["heading"], 511) + self.assertEqual(content["second"], 31) + self.assertEqual(content["regional"], 0) + self.assertEqual(content["cs"], 1) + self.assertEqual(content["display"], 0) + self.assertEqual(content["band"], 1) + self.assertEqual(content["radio"], 410340) + + def test_msg_with_more_that_82_chars_payload(self): + content = decode_msg( + "!AIVDM,1,1,,B,53ktrJ82>ia4=50<0020<5=@Dhv0t8T@u<0000001PV854Si0;mR@CPH13p0hDm1C3h0000,2*35" + ) + + self.assertEqual(content["type"], 5) + self.assertEqual(content["mmsi"], "255801960") + self.assertEqual(content["repeat"], 0) + self.assertEqual(content["ais_version"], 2) + self.assertEqual(content["imo"], 9356945) + self.assertEqual(content["callsign"], "CQPC") + self.assertEqual(content["shipname"], "CASTELO OBIDOS") + self.assertEqual(content["shiptype"], ShipType.NotAvailable) + self.assertEqual(content["to_bow"], 12) + self.assertEqual(content["to_stern"], 38) + self.assertEqual(content["to_port"], 8) + self.assertEqual(content["to_starboard"], 5) + self.assertEqual(content["epfd"], EpfdType.GPS) + self.assertEqual(content["month"], 2) + self.assertEqual(content["day"], 7) + self.assertEqual(content["hour"], 17) + self.assertEqual(content["minute"], 0) + self.assertEqual(content["draught"], 4.7) + self.assertEqual(content["destination"], "VIANA DO CASTELO") diff --git a/tests/test_decode_raw.py b/tests/test_decode_raw.py index 408741d..54770b7 100644 --- a/tests/test_decode_raw.py +++ b/tests/test_decode_raw.py @@ -41,7 +41,6 @@ def should_raise(msg): should_raise(",1,1,,A,403Ovl@000Htt