Repository: M0r13n/pyais

Issue:
Messagetype27 lat/long should be signed
https://github.com/M0r13n/pyais/blob/21da419607a512256e3cfe9abc852738aa8c2599/pyais/messages.py#L1362-L1363

Discovered this while testing, seemed my numbers were off by half and never negative. Doing more testing today. I'm no ais expert but it seems like a bug.

Discussion:

Patch:
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index a76ed59..93ccff4 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,6 +1,14 @@
 ====================
  pyais CHANGELOG
 ====================
+-------------------------------------------------------------------------------
+ Version 2.1.1 24 Apr 2022
+-------------------------------------------------------------------------------
+* Closes https://github.com/M0r13n/pyais/issues/65
+  * makes lat and lon signed for type 27 messages
+* drops nosetest as a development dependency
+
+
 -------------------------------------------------------------------------------
  Version 2.1.0 14 Apr 2022
 -------------------------------------------------------------------------------
diff --git a/pyais/__init__.py b/pyais/__init__.py
index 47ae7a0..ee21463 100644
--- a/pyais/__init__.py
+++ b/pyais/__init__.py
@@ -4,7 +4,7 @@ from pyais.encode import encode_dict, encode_msg, ais_to_nmea_0183
 from pyais.decode import decode
 
 __license__ = 'MIT'
-__version__ = '2.1.0'
+__version__ = '2.1.1'
 __author__ = 'Leon Morten Richter'
 
 __all__ = (
diff --git a/pyais/messages.py b/pyais/messages.py
index 03233c5..a370668 100644
--- a/pyais/messages.py
+++ b/pyais/messages.py
@@ -1359,8 +1359,8 @@ class MessageType27(Payload):
     accuracy = bit_field(1, bool, default=0, signed=False)
     raim = bit_field(1, bool, default=0, signed=False)
     status = bit_field(4, int, default=0, from_converter=NavigationStatus, to_converter=NavigationStatus, signed=False)
-    lon = bit_field(18, float, from_converter=from_lat_lon_600, to_converter=to_lat_lon_600, default=0)
-    lat = bit_field(17, float, from_converter=from_lat_lon_600, to_converter=to_lat_lon_600, default=0)
+    lon = bit_field(18, float, from_converter=from_lat_lon_600, to_converter=to_lat_lon_600, default=0, signed=True)
+    lat = bit_field(17, float, from_converter=from_lat_lon_600, to_converter=to_lat_lon_600, default=0, signed=True)
     speed = bit_field(6, float, default=0, signed=False)
     course = bit_field(9, float, default=0, signed=False)
     gnss = bit_field(1, bool, default=0, signed=False)


Test patch:
diff --git a/tests/test_decode.py b/tests/test_decode.py
index 196f458..1f670b2 100644
--- a/tests/test_decode.py
+++ b/tests/test_decode.py
@@ -8,15 +8,25 @@ from pprint import pprint
 
 from pyais import NMEAMessage, encode_dict
 from pyais.ais_types import AISType
-from pyais.constants import ManeuverIndicator, NavigationStatus, ShipType, NavAid, EpfdType, StationType, TransmitMode
+from pyais.constants import (EpfdType, ManeuverIndicator, NavAid,
+                             NavigationStatus, ShipType, StationType,
+                             TransmitMode)
 from pyais.decode import decode
 from pyais.exceptions import UnknownMessageException
-from pyais.messages import MessageType18, MessageType5, MessageType6, MSG_CLASS, MessageType24PartA, MessageType24PartB, \
-    MessageType25AddressedStructured, MessageType25BroadcastStructured, MessageType25AddressedUnstructured, \
-    MessageType25BroadcastUnstructured, MessageType26AddressedStructured, MessageType26BroadcastStructured, \
-    MessageType26BroadcastUnstructured, MessageType22Addressed, MessageType22Broadcast, to_turn, from_turn
+from pyais.messages import (MSG_CLASS, MessageType5, MessageType6,
+                            MessageType18, MessageType22Addressed,
+                            MessageType22Broadcast, MessageType24PartA,
+                            MessageType24PartB,
+                            MessageType25AddressedStructured,
+                            MessageType25AddressedUnstructured,
+                            MessageType25BroadcastStructured,
+                            MessageType25BroadcastUnstructured,
+                            MessageType26AddressedStructured,
+                            MessageType26BroadcastStructured,
+                            MessageType26BroadcastUnstructured, from_turn,
+                            to_turn)
 from pyais.stream import ByteStream
-from pyais.util import bytes2bits, bits2bytes, b64encode_str
+from pyais.util import b64encode_str, bits2bytes, bytes2bits
 
 
 def ensure_type_for_msg_dict(msg_dict: typing.Dict[str, typing.Any]) -> None:
@@ -679,6 +689,12 @@ class TestAIS(unittest.TestCase):
 
         ensure_type_for_msg_dict(msg)
 
+    def test_msg_type_27_signed(self):
+        msg = decode('!AIVDO,1,1,,A,K01;FQh?PbtE3P00,0*75').asdict()
+        assert msg['mmsi'] == 1234567
+        assert msg['lon'] == -13.368333
+        assert msg['lat'] == -50.121667
+
     def test_broken_messages(self):
         # Undefined epfd
         assert decode(b"!AIVDM,1,1,,B,4>O7m7Iu@<9qUfbtm`vSnwvH20S8,0*46").asdict()['epfd'] == EpfdType.Undefined