Skip to content

Commit 7177a8a

Browse files
committed
fix: handle type 16 messages shorter than 96 bits
1 parent 4d8c698 commit 7177a8a

File tree

4 files changed

+91
-13
lines changed

4 files changed

+91
-13
lines changed

examples/encode_msg.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
For the following example, let's assume that we want to create a type 1 AIS message.
1818
"""
1919
# Required imports
20+
from pyais.decode import decode
2021
from pyais.encode import encode_msg
21-
from pyais.messages import MessageType1
22+
from pyais.messages import MessageType1, MessageType16
2223

2324
# You do not need to pass every attribute to the class.
2425
# All field other than `mmsi` do have default values.
@@ -36,3 +37,12 @@
3637

3738
# You can also change the NMEA fields like the radio channel:
3839
print(encode_msg(msg, radio_channel="B"))
40+
41+
msg_16_short = encode_msg(MessageType16.create(repeat=1, mmsi=123345, mmsi1=99999, increment1=23))[0]
42+
msg_16_long = encode_msg(MessageType16.create(repeat=1, mmsi=123345, mmsi1=99999, increment1=23, mmsi2=777777, increment2=45))[0]
43+
print(msg_16_short)
44+
print(msg_16_long)
45+
46+
47+
print(decode(msg_16_short))
48+
print(decode(msg_16_long))

pyais/messages.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,9 +1341,26 @@ class MessageType15(Payload):
13411341

13421342

13431343
@attr.s(slots=True)
1344-
class MessageType16(Payload):
1344+
class MessageType16DestinationA(Payload):
1345+
"""
1346+
Assignment Mode Command (short)
1347+
Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_16_assignment_mode_command
1348+
"""
1349+
msg_type = bit_field(6, int, default=16, signed=False)
1350+
repeat = bit_field(2, int, default=0, signed=False)
1351+
mmsi = bit_field(30, int, from_converter=from_mmsi)
1352+
spare_1 = bit_field(2, bytes, default=b'', is_spare=True)
1353+
1354+
mmsi1 = bit_field(30, int, default=0, from_converter=from_mmsi)
1355+
offset1 = bit_field(12, int, default=0, signed=False)
1356+
increment1 = bit_field(10, int, default=0, signed=False)
1357+
spare_2 = bit_field(4, bytes, default=b'', is_spare=True)
1358+
1359+
1360+
@attr.s(slots=True)
1361+
class MessageType16DestinationAB(Payload):
13451362
"""
1346-
Assignment Mode Command
1363+
Assignment Mode Command (long)
13471364
Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_16_assignment_mode_command
13481365
"""
13491366
msg_type = bit_field(6, int, default=16, signed=False)
@@ -1360,6 +1377,21 @@ class MessageType16(Payload):
13601377
increment2 = bit_field(10, int, default=0, signed=False)
13611378

13621379

1380+
@attr.s(slots=True)
1381+
class MessageType16(Payload):
1382+
@classmethod
1383+
def create(cls, **kwargs: typing.Union[str, float, int, bool, bytes]) -> "ANY_MESSAGE":
1384+
if 'mmsi2' in kwargs:
1385+
return MessageType16DestinationAB.create(**kwargs)
1386+
return MessageType16DestinationA.create(**kwargs)
1387+
1388+
@classmethod
1389+
def from_bitarray(cls, bit_arr: bitarray) -> "ANY_MESSAGE":
1390+
if len(bit_arr) > 96:
1391+
return MessageType16DestinationAB.from_bitarray(bit_arr)
1392+
return MessageType16DestinationA.from_bitarray(bit_arr)
1393+
1394+
13631395
@attr.s(slots=True)
13641396
class MessageType17(Payload):
13651397
"""
@@ -1963,7 +1995,8 @@ class MessageType27(Payload):
19631995
MessageType13,
19641996
MessageType14,
19651997
MessageType15,
1966-
MessageType16,
1998+
MessageType16DestinationA,
1999+
MessageType16DestinationAB,
19672000
MessageType17,
19682001
MessageType18,
19692002
MessageType19,

tests/test_decode.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ def test_msg_type_15_b(self):
461461

462462
ensure_type_for_msg_dict(msg)
463463

464-
def test_msg_type_16(self):
464+
def test_msg_type_16_short(self):
465465
msg = decode(b"!AIVDM,1,1,,A,@01uEO@mMk7P<P00,0*18").asdict()
466466

467467
assert msg["msg_type"] == 16
@@ -470,9 +470,24 @@ def test_msg_type_16(self):
470470
assert msg["mmsi1"] == 224251000
471471
assert msg["offset1"] == 200
472472
assert msg["increment1"] == 0
473-
assert msg["mmsi2"] == 0
474-
assert msg["offset2"] is None
475-
assert msg["increment2"] is None
473+
assert 'mmsi2' not in msg
474+
assert 'offset2' not in msg
475+
assert 'increment2' not in msg
476+
477+
ensure_type_for_msg_dict(msg)
478+
479+
def test_msg_type_16_long(self):
480+
msg = decode(b"!AIVDO,1,1,,A,@@07Ql@01Qat005h0gN<@00e,0*46").asdict()
481+
482+
assert msg["msg_type"] == 16
483+
assert msg["repeat"] == 1
484+
assert msg["mmsi"] == 123345
485+
assert msg["mmsi1"] == 99999
486+
assert msg["offset1"] == 0
487+
assert msg["increment1"] == 23
488+
assert msg["mmsi2"] == 777777
489+
assert msg["offset2"] == 0
490+
assert msg["increment2"] == 45
476491

477492
ensure_type_for_msg_dict(msg)
478493

@@ -1813,3 +1828,7 @@ def test_decode_fragment_count_0(self):
18131828
# IterMessages should just skip it
18141829
decoded = list(IterMessages([msg]))
18151830
self.assertEqual(decoded, [])
1831+
1832+
1833+
if __name__ == '__main__':
1834+
unittest.main()

tests/test_encode.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
MessageType26BroadcastStructured, MessageType26AddressedStructured, MessageType25BroadcastUnstructured, \
1212
MessageType25AddressedUnstructured, MessageType25BroadcastStructured, MessageType25AddressedStructured, \
1313
MessageType24PartB, MessageType24PartA, MessageType22Broadcast, MessageType22Addressed, MessageType27, \
14-
MessageType23, MessageType21, MessageType20, MessageType19, MessageType18, MessageType17, MessageType16, \
14+
MessageType23, MessageType21, MessageType20, MessageType19, MessageType18, MessageType17, MessageType16DestinationA, \
1515
MessageType15, MessageType4, MessageType5, MessageType6, MessageType7, MessageType8Default, MessageType2, MessageType3, \
16-
MSG_CLASS
16+
MSG_CLASS, MessageType16DestinationAB
1717
from pyais.util import decode_bin_as_ascii6, decode_into_bit_array, str_to_bin, int_to_bin, to_six_bit, encode_ascii_6, \
1818
int_to_bytes, bits2bytes
1919

@@ -43,7 +43,10 @@ def test_widths():
4343
tot_width = sum(field.metadata['width'] for field in MessageType15.fields())
4444
assert tot_width == 160
4545

46-
tot_width = sum(field.metadata['width'] for field in MessageType16.fields())
46+
tot_width = sum(field.metadata['width'] for field in MessageType16DestinationA.fields())
47+
assert tot_width == 96
48+
49+
tot_width = sum(field.metadata['width'] for field in MessageType16DestinationAB.fields())
4750
assert tot_width == 144
4851

4952
tot_width = sum(field.metadata['width'] for field in MessageType17.fields())
@@ -612,14 +615,27 @@ def test_encode_type_16():
612615
'increment2': 0,
613616
'mmsi': '002053501',
614617
'mmsi1': '224251000',
615-
'mmsi2': '000000000',
618+
'mmsi2': '123456',
616619
'offset1': 200,
617620
'offset2': 0,
618621
'repeat': 0,
619622
'type': 16
620623
}
621624
encoded = encode_dict(data)
622-
assert encoded[0] == "!AIVDO,1,1,,A,@01uEO@mMk7P<P0000000000,0*1A"
625+
assert encoded[0] == "!AIVDO,1,1,,A,@01uEO@mMk7P<P0007R@0000,0*0F"
626+
627+
628+
def test_encode_type_16_short():
629+
data = {
630+
'increment1': 0,
631+
'mmsi': '002053501',
632+
'mmsi1': '224251000',
633+
'offset1': 200,
634+
'repeat': 0,
635+
'type': 16
636+
}
637+
encoded = encode_dict(data)
638+
assert encoded[0] == "!AIVDO,1,1,,A,@01uEO@mMk7P<P00,0*1A"
623639

624640

625641
def test_encode_type_15_a():

0 commit comments

Comments
 (0)