Repository: M0r13n/pyais

Issue:
Speed and other fields returning unexpected values
For some fields I'm getting speed and other field values as a integer rather than a float.

Looking at this segment https://github.com/M0r13n/pyais/blob/cc47fb1bf56eaabceb1657770d986a3172bac19a/pyais/messages.py#L794-L818

I'm somewhat confused as the type given to `bit_field` is `int` for everything while properties like speed/lat/lon etc should all be floats.
Also shouldn't the defaults be `None` for missing values, or am I misinterpreting what the default property represents? 
If it is meant to be 0 and not None the very least it should be 0.0 for floats and not 0

Please can you explain the use of `default` in the `bit_field` function.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/M0r13n/pyais/pull/57?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Leon+Morten+Richter) Report
> Merging [#57](https://codecov.io/gh/M0r13n/pyais/pull/57?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Leon+Morten+Richter) (d337ccd) into [master](https://codecov.io/gh/M0r13n/pyais/commit/e833f46cbaa157aa022113c10a5630c002104459?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Leon+Morten+Richter) (e833f46) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/M0r13n/pyais/pull/57/graphs/tree.svg?width=650&height=150&src=pr&token=daXcYHwjaG&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Leon+Morten+Richter)](https://codecov.io/gh/M0r13n/pyais/pull/57?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Leon+Morten+Richter)

```diff
@@           Coverage Diff           @@
##           master      #57   +/-   ##
=======================================
  Coverage   95.98%   95.99%           
=======================================
  Files          10       10           
  Lines        1420     1423    +3     
=======================================
+ Hits         1363     1366    +3     
  Misses         57       57           
```


| [Impacted Files](https://codecov.io/gh/M0r13n/pyais/pull/57?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Leon+Morten+Richter) | Coverage Δ | |
|---|---|---|
| [pyais/messages.py](https://codecov.io/gh/M0r13n/pyais/pull/57/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Leon+Morten+Richter#diff-cHlhaXMvbWVzc2FnZXMucHk=) | `98.13% <100.00%> (+<0.01%)` | :arrow_up: |


Patch:
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 4272b0e..6fb71fe 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,6 +1,15 @@
 ====================
  pyais CHANGELOG
 ====================
+-------------------------------------------------------------------------------
+ Version 2.0.2  20 Mar 2022
+-------------------------------------------------------------------------------
+* Closes https://github.com/M0r13n/pyais/issues/55
+  * Makes the attribute `raim` always an boolean
+
+* Closes https://github.com/M0r13n/pyais/issues/54
+  * Ensure that the attributes speed, lat, lon and course are always `float`
+
 -------------------------------------------------------------------------------
  Version 2.0.1  6 Feb 2022
 -------------------------------------------------------------------------------
diff --git a/Makefile b/Makefile
index 47a5acf..34ed535 100644
--- a/Makefile
+++ b/Makefile
@@ -21,4 +21,8 @@ clean:
 	rm coverage.xml
 	rm .coverage
 
-test: run_tests flake type-check
\ No newline at end of file
+test: run_tests flake type-check
+
+install:
+	pip install wheel
+	pip install -U .[dev]
diff --git a/mypy.ini b/mypy.ini
index 04ebe32..848c9d0 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -13,6 +13,7 @@ warn_unused_ignores = True
 warn_return_any = True
 no_implicit_reexport = True
 strict_equality = True
+strict_optional=True
 
 [mypy-tests.*]
 disallow_any_generics = False
diff --git a/pyais/__init__.py b/pyais/__init__.py
index 98652c3..ba747d1 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.0.1'
+__version__ = '2.0.2'
 __author__ = 'Leon Morten Richter'
 
 __all__ = (
diff --git a/pyais/messages.py b/pyais/messages.py
index 2deb72e..4c5f68c 100644
--- a/pyais/messages.py
+++ b/pyais/messages.py
@@ -354,6 +354,9 @@ class Payload(abc.ABC):
 
             if d_type == int or d_type == bool:
                 bits = int_to_bin(val, width, signed=signed)
+            elif d_type == float:
+                val = int(val)
+                bits = int_to_bin(val, width, signed=signed)
             elif d_type == str:
                 bits = str_to_bin(val, width)
             else:
@@ -417,7 +420,7 @@ class Payload(abc.ABC):
 
             val: typing.Any
             # Get the correct data type and decoding function
-            if d_type == int or d_type == bool:
+            if d_type == int or d_type == bool or d_type == float:
                 shift = (8 - ((end - cur) % 8)) % 8
                 if field.metadata['signed']:
                     val = from_bytes_signed(bits) >> shift
@@ -465,35 +468,35 @@ class Payload(abc.ABC):
 # Conversion functions
 #
 
-def from_speed(v: int) -> NavigationStatus:
-    return NavigationStatus(int(v * 10.0))
+def from_speed(v: typing.Union[int, float]) -> float:
+    return v * 10.0
 
 
-def to_speed(v: int) -> float:
+def to_speed(v: typing.Union[int, float]) -> float:
     return v / 10.0
 
 
-def from_lat_lon(v: int) -> float:
+def from_lat_lon(v: typing.Union[int, float]) -> float:
     return float(v) * 600000.0
 
 
-def to_lat_lon(v: int) -> float:
+def to_lat_lon(v: typing.Union[int, float]) -> float:
     return round(float(v) / 600000.0, 6)
 
 
-def from_lat_lon_600(v: int) -> float:
+def from_lat_lon_600(v: typing.Union[int, float]) -> float:
     return float(v) * 600.0
 
 
-def to_lat_lon_600(v: int) -> float:
+def to_lat_lon_600(v: typing.Union[int, float]) -> float:
     return round(float(v) / 600.0, 6)
 
 
-def from_course(v: int) -> float:
+def from_10th(v: typing.Union[int, float]) -> float:
     return float(v) * 10.0
 
 
-def to_course(v: int) -> float:
+def to_10th(v: typing.Union[int, float]) -> float:
     return v / 10.0
 
 
@@ -514,17 +517,17 @@ class MessageType1(Payload):
     msg_type = bit_field(6, int, default=1, signed=False)
     repeat = bit_field(2, int, default=0, signed=False)
     mmsi = bit_field(30, int, from_converter=from_mmsi, to_converter=to_mmsi)
-    status = bit_field(4, int, default=0, converter=NavigationStatus.from_value)
+    status = bit_field(4, int, default=0, converter=NavigationStatus.from_value, signed=False)
     turn = bit_field(8, int, default=0, signed=True)
-    speed = bit_field(10, int, from_converter=from_speed, to_converter=to_speed, default=0, signed=False)
-    accuracy = bit_field(1, int, default=0)
-    lon = bit_field(28, int, from_converter=from_lat_lon, to_converter=to_lat_lon, default=0, signed=True)
-    lat = bit_field(27, int, from_converter=from_lat_lon, to_converter=to_lat_lon, default=0, signed=True)
-    course = bit_field(12, int, from_converter=from_course, to_converter=to_course, default=0, signed=False)
+    speed = bit_field(10, float, from_converter=from_speed, to_converter=to_speed, default=0, signed=False)
+    accuracy = bit_field(1, int, default=0, signed=False)
+    lon = bit_field(28, float, from_converter=from_lat_lon, to_converter=to_lat_lon, default=0, signed=True)
+    lat = bit_field(27, float, from_converter=from_lat_lon, to_converter=to_lat_lon, default=0, signed=True)
+    course = bit_field(12, float, from_converter=from_10th, to_converter=to_10th, default=0, signed=False)
     heading = bit_field(9, int, default=0, signed=False)
     second = bit_field(6, int, default=0, signed=False)
     maneuver = bit_field(2, int, default=0, from_converter=ManeuverIndicator.from_value,
-                         to_converter=ManeuverIndicator.from_value)
+                         to_converter=ManeuverIndicator.from_value, signed=False)
     spare = bit_field(3, int, default=0)
     raim = bit_field(1, bool, default=0)
     radio = bit_field(19, int, default=0, signed=False)
@@ -562,9 +565,10 @@ class MessageType4(Payload):
     minute = bit_field(6, int, default=0, signed=False)
     second = bit_field(6, int, default=0, signed=False)
     accuracy = bit_field(1, int, default=0, signed=False)
-    lon = bit_field(28, int, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
-    lat = bit_field(27, int, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
-    epfd = bit_field(4, int, default=0, from_converter=EpfdType.from_value, to_converter=EpfdType.from_value)
+    lon = bit_field(28, float, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
+    lat = bit_field(27, float, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
+    epfd = bit_field(4, int, default=0, from_converter=EpfdType.from_value, to_converter=EpfdType.from_value,
+                     signed=False)
     spare = bit_field(10, int, default=0)
     raim = bit_field(1, bool, default=0)
     radio = bit_field(19, int, default=0, signed=False)
@@ -593,10 +597,10 @@ class MessageType5(Payload):
     day = bit_field(5, int, default=0, signed=False)
     hour = bit_field(5, int, default=0, signed=False)
     minute = bit_field(6, int, default=0, signed=False)
-    draught = bit_field(8, int, from_converter=from_course, to_converter=to_course, default=0, signed=False)
+    draught = bit_field(8, float, from_converter=from_10th, to_converter=to_10th, default=0, signed=False)
     destination = bit_field(120, str, default='')
-    dte = bit_field(1, int, default=0)
-    spare = bit_field(1, int, default=0)
+    dte = bit_field(1, bool, default=0, signed=False)
+    spare = bit_field(1, bool, default=0)
 
 
 @attr.s(slots=True)
@@ -661,17 +665,20 @@ class MessageType9(Payload):
     msg_type = bit_field(6, int, default=9, signed=False)
     repeat = bit_field(2, int, default=0, signed=False)
     mmsi = bit_field(30, int, from_converter=from_mmsi, to_converter=to_mmsi)
+
     alt = bit_field(12, int, default=0, signed=False)
+    # speed over ground is in knots, not deciknots
     speed = bit_field(10, int, default=0, signed=False)
     accuracy = bit_field(1, int, default=0, signed=False)
-    lon = bit_field(28, int, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
-    lat = bit_field(27, int, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
-    course = bit_field(12, int, from_converter=from_course, to_converter=to_course, default=0, signed=False)
+    lon = bit_field(28, float, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
+    lat = bit_field(27, float, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
+    course = bit_field(12, float, from_converter=from_10th, to_converter=to_10th, default=0, signed=False)
     second = bit_field(6, int, default=0, signed=False)
+
     reserved = bit_field(8, int, default=0)
-    dte = bit_field(1, int, default=0)
+    dte = bit_field(1, bool, default=0)
     spare = bit_field(3, int, default=0)
-    assigned = bit_field(1, int, default=0)
+    assigned = bit_field(1, bool, default=0)
     raim = bit_field(1, bool, default=0)
     radio = bit_field(20, int, default=0, signed=False)
 
@@ -709,8 +716,8 @@ class MessageType12(Payload):
     mmsi = bit_field(30, int, from_converter=from_mmsi, to_converter=to_mmsi)
     seqno = bit_field(2, int, default=0, signed=False)
     dest_mmsi = bit_field(30, int, from_converter=from_mmsi, to_converter=to_mmsi)
-    retransmit = bit_field(1, int, default=0, signed=False)
-    spare = bit_field(1, int, default=0)
+    retransmit = bit_field(1, bool, default=False, signed=False)
+    spare = bit_field(1, int, default=0, signed=False)
     text = bit_field(936, str, default='')
 
 
@@ -787,8 +794,9 @@ class MessageType17(Payload):
     repeat = bit_field(2, int, default=0, signed=False)
     mmsi = bit_field(30, int, from_converter=from_mmsi, to_converter=to_mmsi)
     spare_1 = bit_field(2, int, default=0)
-    lon = bit_field(18, int, from_converter=from_course, to_converter=to_course, default=0)
-    lat = bit_field(17, int, from_converter=from_course, to_converter=to_course, default=0)
+    # Note that latitude and longitude are in units of a tenth of a minute
+    lon = bit_field(18, float, from_converter=from_10th, to_converter=to_10th, default=0)
+    lat = bit_field(17, float, from_converter=from_10th, to_converter=to_10th, default=0)
     spare_2 = bit_field(5, int, default=0)
     data = bit_field(736, int, default=0, from_converter=int_to_bytes)
 
@@ -802,23 +810,24 @@ class MessageType18(Payload):
     msg_type = bit_field(6, int, default=18, signed=False)
     repeat = bit_field(2, int, default=0, signed=False)
     mmsi = bit_field(30, int, from_converter=from_mmsi, to_converter=to_mmsi)
+
     reserved = bit_field(8, int, default=0, signed=False)
-    speed = bit_field(10, int, default=0, signed=False)
-    accuracy = bit_field(1, int, default=0)
-    lon = bit_field(28, int, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
-    lat = bit_field(27, int, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
-    course = bit_field(12, int, from_converter=from_course, to_converter=to_course, default=0, signed=False)
+    speed = bit_field(10, float, from_converter=from_speed, to_converter=to_speed, default=0, signed=False)
+    accuracy = bit_field(1, int, default=0, signed=False)
+    lon = bit_field(28, float, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
+    lat = bit_field(27, float, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
+    course = bit_field(12, float, from_converter=from_10th, to_converter=to_10th, default=0, signed=False)
     heading = bit_field(9, int, default=0, signed=False)
     second = bit_field(6, int, default=0, signed=False)
     reserved_2 = bit_field(2, int, default=0, signed=False)
     cs = bit_field(1, bool, default=0, signed=False)
-    display = bit_field(1, bool, default=0, signed=False)
-    dsc = bit_field(1, bool, default=0, signed=False)
-    band = bit_field(1, bool, default=0, signed=False)
-    msg22 = bit_field(1, bool, default=0, signed=False)
-    assigned = bit_field(1, bool, default=0, signed=False)
-    raim = bit_field(1, bool, default=0, signed=False)
-    radio = bit_field(20, int, default=0, signed=False)
+    display = bit_field(1, bool, default=0)
+    dsc = bit_field(1, bool, default=0)
+    band = bit_field(1, bool, default=0)
+    msg22 = bit_field(1, bool, default=0)
+    assigned = bit_field(1, bool, default=0)
+    raim = bit_field(1, bool, default=0)
+    radio = bit_field(20, int, default=0)
 
 
 @attr.s(slots=True)
@@ -831,11 +840,12 @@ class MessageType19(Payload):
     repeat = bit_field(2, int, default=0, signed=False)
     mmsi = bit_field(30, int, from_converter=from_mmsi, to_converter=to_mmsi)
     reserved = bit_field(8, int, default=0)
-    speed = bit_field(10, int, from_converter=from_course, to_converter=to_course, default=0, signed=False)
-    accuracy = bit_field(1, int, default=0)
-    lon = bit_field(28, int, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
-    lat = bit_field(27, int, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
-    course = bit_field(12, int, from_converter=from_course, to_converter=to_course, default=0, signed=False)
+
+    speed = bit_field(10, float, from_converter=from_speed, to_converter=to_speed, default=0, signed=False)
+    accuracy = bit_field(1, int, default=0, signed=False)
+    lon = bit_field(28, float, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
+    lat = bit_field(27, float, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
+    course = bit_field(12, float, from_converter=from_10th, to_converter=to_10th, default=0, signed=False)
     heading = bit_field(9, int, default=0, signed=False)
     second = bit_field(6, int, default=0, signed=False)
     regional = bit_field(4, int, default=0, signed=False)
@@ -849,7 +859,7 @@ class MessageType19(Payload):
     epfd = bit_field(4, int, default=0, from_converter=EpfdType.from_value, to_converter=EpfdType.from_value)
     raim = bit_field(1, bool, default=0)
     dte = bit_field(1, bool, default=0)
-    assigned = bit_field(1, int, default=0, signed=False)
+    assigned = bit_field(1, bool, default=0, signed=False)
     spare = bit_field(4, int, default=0)
 
 
@@ -898,13 +908,15 @@ class MessageType21(Payload):
     aid_type = bit_field(5, int, default=0, from_converter=NavAid.from_value, to_converter=NavAid.from_value,
                          signed=False)
     name = bit_field(120, str, default='')
-    accuracy = bit_field(1, bool, default=0, signed=False)
-    lon = bit_field(28, int, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
-    lat = bit_field(27, int, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
+
+    accuracy = bit_field(1, int, default=0, signed=False)
+    lon = bit_field(28, float, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
+    lat = bit_field(27, float, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)
     to_bow = bit_field(9, int, default=0, signed=False)
     to_stern = bit_field(9, int, default=0, signed=False)
     to_port = bit_field(6, int, default=0, signed=False)
     to_starboard = bit_field(6, int, default=0, signed=False)
+
     epfd = bit_field(4, int, default=0, from_converter=EpfdType.from_value, to_converter=EpfdType.from_value)
     second = bit_field(6, int, default=0, signed=False)
     off_position = bit_field(1, bool, default=0)
@@ -966,10 +978,11 @@ class MessageType22Broadcast(Payload):
     # If the message is broadcast (addressed field is 0),
     # the ne_lon, ne_lat, sw_lon, and sw_lat fields are the
     # corners of a rectangular jurisdiction area over which control parameter
-    ne_lon = bit_field(18, int, from_converter=from_course, to_converter=to_course, default=0, signed=True)
-    ne_lat = bit_field(17, int, from_converter=from_course, to_converter=to_course, default=0, signed=True)
-    sw_lon = bit_field(18, int, from_converter=from_course, to_converter=to_course, default=0, signed=True)
-    sw_lat = bit_field(17, int, from_converter=from_course, to_converter=to_course, default=0, signed=True)
+    # ne_lon, ne_lat, sw_lon, and sw_lat fields are in 0.1 minutes
+    ne_lon = bit_field(18, float, from_converter=from_10th, to_converter=to_10th, default=0, signed=True)
+    ne_lat = bit_field(17, float, from_converter=from_10th, to_converter=to_10th, default=0, signed=True)
+    sw_lon = bit_field(18, float, from_converter=from_10th, to_converter=to_10th, default=0, signed=True)
+    sw_lat = bit_field(17, float, from_converter=from_10th, to_converter=to_10th, default=0, signed=True)
 
     addressed = bit_field(1, bool, default=0)
     band_a = bit_field(1, bool, default=0)
@@ -1015,10 +1028,10 @@ class MessageType23(Payload):
     mmsi = bit_field(30, int, from_converter=from_mmsi, to_converter=to_mmsi)
     spare_1 = bit_field(2, int, default=0)
 
-    ne_lon = bit_field(18, int, from_converter=from_course, to_converter=to_course, default=0, signed=True)
-    ne_lat = bit_field(17, int, from_converter=from_course, to_converter=to_course, default=0, signed=True)
-    sw_lon = bit_field(18, int, from_converter=from_course, to_converter=to_course, default=0, signed=True)
-    sw_lat = bit_field(17, int, from_converter=from_course, to_converter=to_course, default=0, signed=True)
+    ne_lon = bit_field(18, int, from_converter=from_10th, to_converter=to_10th, default=0, signed=True)
+    ne_lat = bit_field(17, int, from_converter=from_10th, to_converter=to_10th, default=0, signed=True)
+    sw_lon = bit_field(18, int, from_converter=from_10th, to_converter=to_10th, default=0, signed=True)
+    sw_lat = bit_field(17, int, from_converter=from_10th, to_converter=to_10th, default=0, signed=True)
 
     station_type = bit_field(4, int, default=0, from_converter=StationType.from_value,
                              to_converter=StationType.from_value)
@@ -1305,8 +1318,8 @@ class MessageType27(Payload):
     accuracy = bit_field(1, int, 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, int, from_converter=from_lat_lon_600, to_converter=to_lat_lon_600, default=0)
-    lat = bit_field(17, int, 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)
+    lat = bit_field(17, float, from_converter=from_lat_lon_600, to_converter=to_lat_lon_600, default=0)
     speed = bit_field(6, int, default=0, signed=False)
     course = bit_field(9, int, default=0, signed=False)
     gnss = bit_field(1, int, default=0, signed=False)
diff --git a/setup.py b/setup.py
index 17825d4..b732e89 100644
--- a/setup.py
+++ b/setup.py
@@ -40,6 +40,9 @@ setuptools.setup(
         "bitarray",
         "attrs"
     ],
+    extras_require={
+        'dev': ['nose', 'mypy', 'flake8', 'coverage', 'twine', 'sphinx']
+    },
     entry_points={
         "console_scripts": [
             'ais-decode=pyais.main:main'


Test patch:
diff --git a/tests/test_decode.py b/tests/test_decode.py
index c1ac917..d2a1b6d 100644
--- a/tests/test_decode.py
+++ b/tests/test_decode.py
@@ -1,5 +1,6 @@
 import textwrap
 import unittest
+from pprint import pprint
 
 from pyais import NMEAMessage, encode_dict
 from pyais.ais_types import AISType
@@ -123,6 +124,12 @@ class TestAIS(unittest.TestCase):
 
         assert decode(b"!AIVDM,1,1,,B,0S9edj0P03PecbBN`ja@0?w42cFC,0*7C").to_json()
 
+    def test_decode_1_speed(self):
+        content = decode(b"!AIVDM,1,1,,A,13@nePh01>PjcO4PGReoJEmL0HJg,0*67").asdict()
+
+        assert content['speed'] == 7.8
+        assert content['msg_type'] == 1
+
     def test_msg_type_3(self):
         msg = decode(b"!AIVDM,1,1,,A,35NSH95001G?wopE`beasVk@0E5:,0*6F").asdict()
         assert msg['msg_type'] == 3
@@ -344,10 +351,9 @@ class TestAIS(unittest.TestCase):
 
     def test_msg_type_18(self):
         msg = decode(b"!AIVDM,1,1,,A,B5NJ;PP005l4ot5Isbl03wsUkP06,0*76").asdict()
-        print(msg)
         assert msg['msg_type'] == 18
         assert msg['mmsi'] == "367430530"
-        assert msg['speed'] == 0
+        assert msg['speed'] == 0.0
         assert msg['accuracy'] == 0
         assert round(msg['lat'], 2) == 37.79
         assert round(msg['lon'], 2) == -122.27
@@ -364,6 +370,17 @@ class TestAIS(unittest.TestCase):
         assert not msg['raim']
         assert isinstance(msg['raim'], bool)
 
+        assert isinstance(msg['lat'], float)
+        assert isinstance(msg['lon'], float)
+        assert isinstance(msg['speed'], float)
+        assert isinstance(msg['course'], float)
+
+    def test_msg_type_18_speed(self):
+        msg = decode(b"!AIVDO,1,1,,A,B5NJ;PP2aUl4ot5Isbl6GwsUkP06,0*35").asdict()
+
+        assert msg['speed'] == 67.8
+        assert msg['course'] == 10.1
+
     def test_msg_type_19(self):
         msg = decode(b"!AIVDM,1,1,,B,C5N3SRgPEnJGEBT>NhWAwwo862PaLELTBJ:V00000000S0D:R220,0*0B").asdict()
         assert msg['msg_type'] == 19
@@ -437,7 +454,7 @@ class TestAIS(unittest.TestCase):
         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['sw_lat'] == 3210.0
 
         assert msg['band_a'] == 0
         assert msg['band_b'] == 0
@@ -446,6 +463,11 @@ class TestAIS(unittest.TestCase):
         assert 'dest1' not in msg.keys()
         assert 'dest2' not in msg.keys()
 
+        assert isinstance(msg['ne_lon'], float)
+        assert isinstance(msg['ne_lat'], float)
+        assert isinstance(msg['sw_lon'], float)
+        assert isinstance(msg['sw_lat'], float)
+
     def test_msg_type_22_addressed(self):
         # Addressed
         msg = decode(b"!AIVDM,1,1,,A,F@@W>gOP00PH=JrN9l000?wB2HH;,0*44").asdict()
@@ -597,7 +619,7 @@ class TestAIS(unittest.TestCase):
         self.assertEqual(content["msg_type"], 18)
         self.assertEqual(content["repeat"], 0)
         self.assertEqual(content["mmsi"], "1000000000")
-        self.assertEqual(content["speed"], 1023)
+        self.assertEqual(content["speed"], 102.3)
         self.assertEqual(content["accuracy"], 0)
         self.assertEqual(str(content["lon"]), "181.0")
         self.assertEqual(str(content["lat"]), "91.0")
@@ -906,3 +928,25 @@ class TestAIS(unittest.TestCase):
         decoded = decode(short_msg)
 
         self.assertEqual(decoded.mmsi, '000000001')
+
+    def test_types_for_messages(self):
+        """Make sure that the types are consistent for all messages"""
+        types = {}
+        for typ, msg in MSG_CLASS.items():
+            for field in msg.fields():
+                d_type = field.metadata['d_type']
+                f_name = field.name
+                if f_name in types:
+                    if typ == 9 and f_name == 'speed' and d_type == int:
+                        continue
+                    if f_name == 'spare':
+                        continue
+                    if typ == 27 and f_name == 'speed' and d_type == int:
+                        continue
+                    if typ == 27 and f_name == 'course' and d_type == int:
+                        continue
+                    assert d_type == types[f_name], f"{typ}.{f_name}: {d_type} vs. {types[f_name]}"
+                else:
+                    types[f_name] = d_type
+
+        pprint(types)
diff --git a/tests/test_encode.py b/tests/test_encode.py
index 16e4070..64c5105 100644
--- a/tests/test_encode.py
+++ b/tests/test_encode.py
@@ -517,6 +517,33 @@ def test_encode_type_19():
     assert encoded[0] == "!AIVDO,1,1,,A,C5N3SRP0=nJGEBT>NhWAwwo862PaLELTBJ:V0000000000D:R220,0*46"
 
 
+def test_encode_type_18_with_speed_and_course():
+    data = {
+        'accuracy': 0,
+        'assigned': 0,
+        'band': 1,
+        'course': 10.1,
+        'cs': 1,
+        'display': 0,
+        'dsc': 1,
+        'heading': 511,
+        'lat': 37.785035,
+        'lon': -122.26732,
+        'mmsi': '367430530',
+        'msg22': 1,
+        'radio': 917510,
+        'raim': 0,
+        'regional': 0,
+        'repeat': 0,
+        'second': 55,
+        'speed': 67.85,
+        'type': 18
+    }
+
+    encoded = encode_dict(data)
+    assert encoded[0] == "!AIVDO,1,1,,A,B5NJ;PP2aUl4ot5Isbl6GwsUkP06,0*35"
+
+
 def test_encode_type_18():
     data = {
         'accuracy': 0,
@@ -953,17 +980,17 @@ def test_encode_type_1():
         'raim': 0,
         'repeat': 0,
         'second': 59,
-        'speed': 0.0,
+        'speed': 7.8,
         'status': 3,
         'turn': 0,
         'type': 1
     }
 
     encoded = encode_dict(data, radio_channel="B", talker_id="AIVDM")[0]
-    assert encoded == "!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C"
+    assert encoded == "!AIVDM,1,1,,B,15M67FC01>G?ufbE`FepT@3n00Sa,0*53"
 
     encoded = encode_dict(data, radio_channel="B")[0]
-    assert encoded == "!AIVDO,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5E"
+    assert encoded == "!AIVDO,1,1,,B,15M67FC01>G?ufbE`FepT@3n00Sa,0*51"
 
 
 def test_mmsi_too_long():