Code Monkey home page Code Monkey logo

pyais's Issues

Add a console_scripts entrypoint

It would be nice, if one would be able to install the package and have a program inside your terminal, that is able to decode AIS messages.

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

pyais/pyais/messages.py

Lines 794 to 818 in cc47fb1

class MessageType18(Payload):
"""
Standard Class B CS Position Report
Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_18_standard_class_b_cs_position_report
"""
msg_type = bit_field(6, int, default=18)
repeat = bit_field(2, int, default=0)
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, default=0)
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)
heading = bit_field(9, int, default=0)
second = bit_field(6, int, default=0)
reserved_2 = bit_field(2, int, default=0)
cs = bit_field(1, bool, default=0)
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)

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.

decode.py: decode_msg_23 should have 'shiptype' not 'ship_type'

There are two different keys for ship type. Please only use one.

`
decode_msg_5 ...
'shiptype': ShipType(get_int_from_data(232, 240)),
decode_msg_19 ..
'shiptype': ShipType(get_int_from_data(232, 240)),
...
decode_msg_23 . . .
'ship_type': ShipType(get_int_from_data(114, 122))

`

Exception when trying to decode multipart payloads

When trying to decode messages with the decode_msg function, I run into the exception that the message is not currently supported (in the file decode.py). I've checked out the file but don't understand what the problem is. One AIS payload that was encoded correctly looks as following:

AIS:
 !AIVDM,1,1,,B,34SNn:50000eUKHN`Ai0a41d0DU:,0*40
Decoded:
 {'type': 3, 'repeat': 0, 'mmsi': '305641000', 'status': <NavigationStatus.Moored: 5>, 'turn': 0, 'speed': 0.0, 'accuracy': 0, 'lon': 9.958153333333334, 'lat': 53.52864666666667, 'course': 16.400000000000002, 'heading': 128, 'second': 54, 'maneuver': <ManeuverIndicator.NotAvailable: 0>, 'raim': 0, 'radio': 84298}

and it works right until I run into a message with !AIVDM,2:

!AIVDM,2,1,0,A,539p4OT00000@7W3K@08ThiLE8@E:0000000001S0h9135Pl?0R0C@UDQp00,0*68
{'type': 5, 'repeat': 0, 'mmsi': '211682430', 'ais_version': 1, 'imo': 0, 'callsign': 'DA9064', 'shipname': 'BILLWERDER', 'shiptype': <ShipType.OtherType_NoAdditionalInformation: 99>, 'to_bow': 6, 'to_stern': 9, 'to_port': 1, 'to_starboard': 3, 'epfd': <EpfdType.GPS: 1>, 'month': 6, 'day': 1, 'hour': 20, 'minute': 15, 'draught': 0.2, 'destination': 'HAMBURG', 'dte': 0}
!AIVDM,2,2,0,A,00000000000,2*24
pyais.exceptions.UnknownMessageException: The message b'!AIVDM,2,2,0,A,00000000000,2*24' is not currently supported!

Is multipart payload decoding not supported yet or am I not understanding what exactly to do when I encounter a multipart payload?

Gatehouse wrapper messages feature

Some AIS messages have so-called Gatehouse wrappers. These encapsulating messages contain extra information, such as time and checksums. Some readers also process these. See some more documentation here.

We have created a small mixin that allows for storing this extra information. We also added an extra message type to parse the metadata lines. Could you have a look to see if this is an approach you would be willing to adopt?

As an example, see the following, which is followed by a regular !AIVDM message

$PGHP,1,2020,12,31,23,59,58,239,0,0,0,1,2C*5B

I will submit the code as a PR referring to this issue.

There is no way to gracefully break out of a stream

Hello There
In UDPStream, TCPStream when you enter the loop there is no way to break out of the loop when there is no message incoming from UDPStream,TCPStream so using a UDPStream, TCPStream in a GUI application with the capability of starting and stopping the stream is impossible.

[docs] messages.rst content duplicated.

Hello 😊

Some of the contents of the docs/messages.rst seem to overlap. 🤔
as MessageType1 ('line 105 ~ 174 and 175 ~ 244') is duplicated.

at this part

MessageType1
    AIS Vessel position report using SOTDMA (Self-Organizing Time Division Multiple Access)
    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_types_1_2_and_3_position_report_class_a


    Attributes:
        * `msg_type`

...

Is the checksum check not commonly used?

Hi.
First of all, thank you for making a good library.

While using decode() in this library, I found that checksum check was not performed. (I proceeded with decoding by changing only the 'checksum' field.)
The output was printed by adding print(nmea.is_vailed) to _assamble_messages() belonging to decode.py. It seems that the result of False should be raise, but all decoding proceeds normally.

# Mosaic messages for security reasons
is_vaild result:  True   ais_msg:  b'!AIVDM,1,1,,A,xxxxxxxxxxa3OsLEKwfmKB:L0@8g,0*55\n'
is_vaild result:  False   ais_msg:  b'!AIVDM,1,1,,A,xxxxxxxxxxa3OsLEKwfmKB:L0@8g,0*15\n'
is_vaild result:  False   ais_msg:  b'!AIVDM,1,1,,A,xxxxxxxxxxa3OsLEKwfmKB:L0@8g,0*10\n'
is_vaild result:  False   ais_msg:  b'!AIVDM,1,1,,A,xxxxxxxxxxa3OsLEKwfmKB:L0@8g,0*1A\n'
is_vaild result:  False   ais_msg:  b'!AIVDM,1,1,,A,xxxxxxxxxx3OsLEKwfmKB:L0@8g,0*B5'

Is the checksum check function not commonly used? Or is there another reason?
Thank you. :)

Wrong Value in turn field (AIS1-3) ?

Hi,

Here an output from an ais msg:

{'type': 1, 'repeat': 0, 'mmsi': 'XXXXXX', 'status': <NavigationStatus.Undefined: 15>, 'turn': -128, 'speed': 0.1, 'accuracy': 1, 'lon': 4.303693333333333, 'lat': 51.270986666666666, 'course': 360.0, 'heading': 511, 'second': 55, 'maneuver': <ManeuverIndicator.NotAvailable: 0>, 'raim': 1, 'radio': 82243}

The "turn"-Field with -128 is not correct i think. A value of 128 (positiv) is the value for "no information" and it is not possible to save -128 with 8 bits (singed int -127 - 128).

Ty & Gz!

Could NMEAMessage contain fields for each field in raw?

Having easy access to the results of NMEAMessage(foo).raw.decode().split(',') would make things neater for certain tasks, in case it's necessary to manually process those fields.

For example:
!AIVDM,1,1,,A,15Mj23P000G?q7fK>g:o7@1:0L3S,0*1B
would have fields:
{'message_fragments': 1, 'fragment_number': 1, 'message_id': None, 'channel': 'A', 'payload': '15Mj23P000G?q7fK>g:o7@1:0L3S', 'fill_bits':0, 'checksum': '*1B'}

TypeError: 'dict' object is not callable

Hello,
I'm trying to use the decode function by following through the example code but end up getting the error saying "TypeError: 'dict' object is not callable". This is code:
decoded_s = decode("!AIVDM,1,1,,B,15NG6V0P01G?cFhE`R2IU?wn28R>,0*05")

May be it's something that I did not set up correctly. Can you please give me an advice on how to fix this issue? Thank you very much.

Decoding of Binary Payloads

Thanks for the great module! Am I correct in understanding that the current code does not decode the binary payloads contained in AIS messages 6, 8, 25, and 26?

How to decode invalid messages

Hi,

I am dealing with a stream of AIS messages. Approximately 20 % are invalid. The library raises an exception and the message is not processed. For example, the following two:

!ABVDM,1,1,7,,B3seA8000?mPBpURDA?Q3wmQnDOJ,0*3C,1616655884
!AIVDM,1,1,,B,53ktrJ82>ia4=50<0020<5=@Dhv0t8T@u<0000001PV854Si0;mR@CPH13p0hDm1C3h0000,2*35

The first one has the channel empty and the second has more than 82 characters. I inserted both of them in maritec and the messages were decoded. The "only problem" they had is an erroneous checksum. Is there a way to decode them with pyais? Using NMEAMessage function an error is raised and the message is not decoded. It could be possible to print warnings instead of raising exceptions?

Thank you so much for your work.

EDIT: other option could be to bypass the validate_message routine with an argument. Its default would be False.

Q&A

Single Messages
"Supports single messages": Can someone recommend a library that decodes multi-line messages. Or is there a work around that can leverage pyais?

I have code that can collate the message parts and put them all together in any manner a library might require. All I need is one that then can decode.

Message types
"Currently, this module is able to decode most message types. There are only a few exceptions. "

Which specific messages are not decoded? I have a close to production need to decode everything. Any plan to add the messages types that are currently not decoded?

Only one attribute of decode

How I take just only the attribute of decode ?. For example, I use the code:

from pyais.stream import FileReaderStream

filename = "sample.ais" #archive with NMEA message

for msg in FileReaderStream(filename):
    decoded = msg.decode()
    print(decoded)

, but I want print only lat or long, and not all datas.

Problem connecting to UDP server

Hi all, I am trying to use pyais to read a UDP output from an AIS receiver on the network. I can read it fine using netcat:

jpe@pmpc1770:~>nc -vu 192.171.162.68 12345
Connection to 192.171.162.68 12345 port [udp/*] succeeded!
\s:PML,c:1622632119,t:LIVE*10\!AIVDM,1,1,,A,13P:DSP000wdk1vLm2mRa@C:08GI,0*74
\s:PML,c:1622632119,t:LIVE*10\!AIVDM,1,1,,B,13M@LCP000wdm98LmIidL7u<0L0M,0*6C
\s:PML,c:1622632119,t:LIVE*10\!AIVDM,1,1,,A,13:dR<5Ohkwe;BDLlES`=Fo>0<0Q,0*53
\s:PML,c:1622632119,t:LIVE*10\!AIVDM,1,1,,B,139K?fP00ewe2jLLe7C01P1>00T1,0*59
\s:PML,c:1622632119,t:LIVE*10\!AIVDM,1,1,,B,339K?i001;wdoV:LjCuW9Ec@2000,0*01
\s:PML,c:1622632120,t:LIVE*1A\!AIVDM,1,1,,B,13MAPkPP00OdoipLl@E>4?wB2D08,0*16
\s:PML,c:1622632121,t:LIVE*1B\!AIVDM,1,1,,A,13M@ENh000wdn9RLm76<BUG>08Hb,0*3C
\s:PML,c:1622632121,t:LIVE*1B\!AIVDM,1,1,,B,13M@E50000OdfwlLmKBSC@sD08Hl,0*6F
\s:PML,c:1622632122,t:LIVE*18\$AIHBT,5.0,A,8*28
\s:PML,c:1622632122,t:LIVE*18\!AIVDM,1,1,,B,15QJmN002;OehoJLU5rprW;H0<0t,0*47
\s:PML,c:1622632122,t:LIVE*18\!AIVDM,1,1,,A,13OhOf0P?w<tSF0l4Q@>4?wv0PS7,0*16
\s:PML,c:1622632122,t:LIVE*18\!AIVDM,1,1,,B,13P<SaS000wdiKhLmEFPhlIF0@I`,0*63
\s:PML,c:1622632123,t:LIVE*19\!AIVDM,1,1,,B,13P;r00000wdmFlLmLCf42CF2@Ij,0*40
\s:PML,c:1622632123,t:LIVE*19\!AIVDM,1,1,,A,339K?fPP@ewe2jPLe7OP801F0000,0*4C
\s:PML,c:1622632123,t:LIVE*19\!AIVDM,1,1,,B,B3`p=Ph00GsAhuW<urAwWwn5CP06,0*1D
\s:PML,c:1622632123,t:LIVE*19\!AIVDM,1,1,,A,339K?fP00ewe2jPLe7OP801F0000,0*5C
\s:PML,c:1622632123,t:LIVE*19\!AIVDM,1,1,,A,339K?i001:wdoV0LjCf7:5cF20s0,0*4C
\s:PML,c:1622632124,t:LIVE*1E\!AIVDM,1,1,,B,B3MAat00?7s@LdW<pkQG7wn1h8JK,0*76
\s:PML,c:1622632124,t:LIVE*1E\!AIVDM,1,1,,A,13M@DFh000wdm9HLm5pTLEPn0@JO,0*0E
\s:PML,c:1622632124,t:LIVE*1E\!AIVDM,1,1,,A,B3P:skh00?sAK6W=6VgQ3wnQnDRb,0*60
\s:PML,c:1622632124,t:LIVE*1E\!AIVDM,1,1,,B,B3P=uwh00?sBPfW=43wQ3wnUoP06,0*66
\s:PML,c:1622632126,t:LIVE*1C\!AIVDM,1,1,,B,339K?fP00fwe2jVLe7a01h1L00w@,0*78
\s:PML,c:1622632126,t:LIVE*1C\!AIVDM,1,1,,B,339K?i001:wdoUlLjCIW8mcN20w1,0*08
\s:PML,c:1622632126,t:LIVE*1C\!AIVDM,1,1,,A,13M@BR001JOdmO>LlUlMm;7L00S6,0*69
\s:PML,c:1622632126,t:LIVE*1C\!AIVDM,1,1,,A,B3P7m>h0<Os02>7<cRR@gwoQl<0N,0*2F
\s:PML,c:1622632126,t:LIVE*1C\!AIVDM,1,1,,A,B3P9Nr00<wsDHh7;bovWcwoQl8Kv,0*3D

However if I try the following Python code:

#!/usr/bin/env python3
import socket
from pyais.stream import UDPStream

host = "192.171.162.68"
port = 12345

for msg in UDPStream(host, port):
    # msg.decode()
    print(msg)
    # do something with it

I get the error:

Traceback (most recent call last):
  File "/users/rsg/jpe/projects/pml-ais/log-roof-station.py", line 8, in <module>
    for msg in UDPStream(host, port):
  File "/users/rsg/jpe/.local/lib/python3.8/site-packages/pyais/stream.py", line 279, in __init__
    sock.bind((host, port))
OSError: [Errno 99] Cannot assign requested address

Also I tried the commandline utility and same issue:

jpe@pmpc1770:~>ais-decode socket -t udp 192.171.162.68 12345
Traceback (most recent call last):
  File "/usr/local/bin/ais-decode", line 8, in <module>
    sys.exit(main())
  File "/users/rsg/jpe/.local/lib/python3.8/site-packages/pyais/main.py", line 145, in main
    exit_code: int = namespace.func(namespace)
  File "/users/rsg/jpe/.local/lib/python3.8/site-packages/pyais/main.py", line 99, in decode_from_socket
    with stream_cls(args.destination, args.port) as s:
  File "/users/rsg/jpe/.local/lib/python3.8/site-packages/pyais/stream.py", line 279, in __init__
    sock.bind((host, port))
OSError: [Errno 99] Cannot assign requested address

My OS is Ubuntu 20.04 and Python 3.8.5. Does anyone know what the error is? Thank you

in v2-alpha, MessageType21 payload includes shipname, which I think should just be name

In messages.py:

lass MessageType21(Payload):
    """
    Aid-to-Navigation Report
    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_21_aid_to_navigation_report
    """
....
    shipname = bit_field(120, str, default='')

I believe shipname should simply be name, which is what's listed in that gitlab URL.

I found this when testing a script I use which displays ship names my receiver is picking up. With ~v1.7 I would get only the shipnames, but with v2 in addition to ships I'd get A, B, C, 1, 2, etc., which are aids to navigation markers from type 21 messages.

ValueError: invalid literal for int() with base 16: b''

Maybe this is not as intended?

I am parsing a file of all sorts of bad data and decoding it; I handle the exceptions; however this seems like a bad way to handle a missing checksum?

!AIVDM,1,1,,A,100u3FP04r28t0<WcshcQI<H0H79,0

Traceback (most recent call last): File "pyais_test.py", line 14, in <module> decoded = pyais.decode(i) File "c:\Python38\lib\site-packages\pyais\decode.py", line 34, in decode nmea = _assemble_messages(*parts) File "c:\Python38\lib\site-packages\pyais\decode.py", line 13, in _assemble_messages nmea = NMEAMessage(msg) File "c:\Python38\lib\site-packages\pyais\messages.py", line 201, in __init__ self.checksum = int(checksum[2:], 16) ValueError: invalid literal for int() with base 16: b''

All the best

Make fields accessible by __getitem__

I'm working with pyais and I'm finding the syntax NMEAMessage(blah).asdict()['mmsi']) clumsy. It should be possible to subscript these fields if NMEAMessage defined a getitem.

I will try to enter a pull request at some point, if I'm able to make time.

Non-deterministic results from 'decode_msg'

I am seeing some non-determinism in decode_msg output, e.g.:

>>> raw = b'!AIVDM,1,1,,A,D02=ag0flffp,0*11'
>>> pyais.decode_msg(raw)['offset2']
2
>>> pyais.decode_msg(raw)['offset2']
0

Memory overflow? This AIS message itself seems short but I would expect an error if it was unexpected length?

python: 3.9
pyais: 1.6.2
bitarray: 2.3.4

Proposal for update

I use this code to decode nmea0183 data (!AIVD...) collected from the AIS-reciever in my boat. Sometimes the message get errors. When the pyais try to decode the corrupted AIS-message, the code stop with a message as follows.
Traceback (most recent call last):

File "C:\Users\Asus\OneDrive\Documents__Python\decode_AIS\decode_AIS.py", line 93, in
for msg in FileReaderStream(filename):

File "C:\Users\Asus\anaconda3\lib\site-packages\pyais\stream.py", line 54, in _assemble_messages
msg: NMEAMessage = NMEAMessage(line)

File "C:\Users\Asus\anaconda3\lib\site-packages\pyais\messages.py", line 218, in init
self.bit_array: bitarray = decode_into_bit_array(self.payload, self.fill_bits)

File "C:\Users\Asus\anaconda3\lib\site-packages\pyais\util.py", line 34, in decode_into_bit_array
raise ValueError(f"Invalid character: {chr(c)}")

ValueError: Invalid character: !

The above error come with the message: !AIVDM,1,1,,B,3815;`100!PhmnPPwL=3OmUd0Dg:,0*45

I would prefer if errors like this one doesn't stop the execution, and instead just give a notice that there was a message with an error, and the continue the process of the next AIS message.

Decoding messages with a unexpected bit_arr length silently fails returning None

When decoding messages, if the bit_arr length is less than what is needed for every property expected to be decoded None is returned with no error.

Test Code:

import pyais
print(pyais.NMEAMessage(bytes("!ARVDM,2,1,3,B,E>m1c1>9TV`9WW@97QUP0000000F@lEpmdceP00003b,0*5C", 'utf-8')).decode().content)

As you can see below I have commented out several properties except for assigned which attempts to access index 270 of the bit_arr.
When this is uncommented the returned value is None, however when commented (there are no properties that access outside of the bit_arr length) you can see the message decodes as expected.
image

Ideally properties that do not have data or fail to decode would result in None.
I appear to be getting this for most if not all of my type 21 messages.

Decoding AIS message type 5, class A static data

I receive AIS messages on Icom m605 VHF radio and forward them to OpenCPN. All the targets around show fine and with full metadata there.

I decode the same stream of messages with pyais, log the messages and count the message types for statistics. However, it seems my counter for message type 5 (class A static data) seems to not increase, and my counter for decoding errors does. All the other message counts seem sane.

I have to dig into this deeper and catch few such messages in the wild, but anyone else has success with decoding said message type? It might of course be a problem on how Icom generates the NMEA messages/checksums etc, but it seems odd as OpenCPN seems to not have a problem as otherwise it could not show the ship names etc.

I will update this issue when I have some actual example messages to digest.

edit. here is an example message captured live from Icom that causes decoding error:

!AIVDM,2,1,3,A,53KPT5P00000tP7?KOTh4AV104lDh6222222220U1P5337<B05iRCS0CQ0kh,0*39

edit2. I can see it now, it is a multipart messege with second part missing for some reason. I will have to dig deeper, it might be my reception and not pyais problem at all.

edit3. It seems I found the problem. My NMEA multiplexer is interleaving the messages from AIS and GPS in a way that pyais does not seem to like - there is (almost) always a gnss sentence or two interleaved between multipart type 5 messages. OpenCPN seems to not care, but pyais does.

I think ultimately this is not a pyais issue. So this issue can be closed.

Variable-width text fields processed as constant-width ones

What am I trying to do:
print(*pyais.encode_msg(pyais.messages.MessageType12.create(mmsi=111111111, dest_mmsi=999999999, text='TEST')), sep='\n')
What I expect to get:
!AIVDM,1,1,,A,<1aucikfJjOtD5CD,0*55
What I actually get

!AIVDM,3,1,0,A,<1aucikfJjOtD5CD000000000000000000000000000000000000000000000,0*57
!AIVDM,3,2,0,A,0000000000000000000000000000000000000000000000000000000000000,0*27
!AIVDM,3,3,0,A,0000000000000000000000000000000000000000000000,0*16

As stated in https://gpsd.gitlab.io/gpsd/AIVDM.htm width of "text" field in MessageType12 and MessageType14 may be shorter than given maximum width, but pyais treats these fields as a constat-width ones, and trails given short text with a bunch of @ signs up to maximum length.

Seems it would be better to somehow calculate actual required width of the field while encoding

Support for multiline AIS messages?

Can multiline AIS messages be processed ? For example:

!AIVDM,2,1,5,A,53KQ<L01pv7ptTtN22059@DlU>2222222222220l1@D555V00:R0CRkp8888,0*41
!AIVDM,2,2,5,A,88888888880,2*21

Best regards,
Andrés

Urgent need

Nice project...
Though am not a python dev but i strongly believe if you have reached the level of decoding ais that's shows a better understanding.

Well am a Java dev building an app related to ais... i have some questions i will like to ask can you privately.

edit:
Thanks for idea and time

Add missing encoders

Currently, the following messages are supported:

  • Typ 1
  • Typ 2
  • Typ 3
  • Typ 4
  • Typ 5
  • Typ 6
  • Typ 7
  • Typ 8
  • Typ 9
  • Typ 10
  • Typ 11
  • Typ 12
  • Typ 13
  • Typ 14
  • Typ 15
  • Typ 16
  • Typ 17
  • Typ 18
  • Typ 19
  • Typ 20
  • Typ 21
  • Typ 22
  • Typ 23
  • Typ 24
  • Typ 25
  • Typ 26
  • Typ 27

The scope of this issue is to add support for encoding all messages.

IndexError: bitarray index out of range

Hi,

I have this message:

ais_print = message.decode()
and I get this:

{'type': 5, 'repeat': 0, 'mmsi': 248659000, 'ais_version': 1, 'imo': 9745500, 'callsign': '9HA4748', 'shipname': 'CMA CGM PREGOLIA', 'shiptype': <ShipType.Cargo_HazardousCategory_A: 71>, 'to_bow': 183, 'to_stern': 12, 'to_port': 30, 'to_starboard': 2, 'epfd': <EpfdType.GPS: 1>, 'month': 7, 'day': 20, 'hour': 22, 'minute': 30, 'draught': 10.5, 'destination': 'DEHAM', 'dte': False}

then

ais_content = message.decode().to_json()
and I get this:

{'nmea': {'ais_id': 5, 'raw': '!AIVDM,2,1,2,B,53e8t>42De5kTP7COCP<l60<Ln118DLthT400017Fp<N25rFNJA1B0CH,0*18', 'talker': 'AI', 'msg_type': 'VDM', 'count': 2, 'index': 1, 'seq_id': '2', 'channel': 'B', 'data': '53e8t>42De5kTP7COCP<l60<Ln118DLthT400017Fp<N25rFNJA1B0CH', 'checksum': 24, 'bit_array': '000101000011101101001000111100001110000100000010010100101101000101110011100100100000000111010011011111010011100000001100110100000110000000001100011100110110000001000001001000010100011100111100110000100100000100000000000000000000000001000111010110111000001100011110000010000101111010010110011110011010010001000001010010000000010011011000'}, 'decoded': {'type': 5, 'repeat': 0, 'mmsi': 248659000, 'ais_version': 1, 'imo': 9745500, 'callsign': '9HA4748', 'shipname': 'CMA CGM PREGOLIA', 'shiptype': 71, 'to_bow': 183, 'to_stern': 12, 'to_port': 30, 'to_starboard': 2, 'epfd': 1, 'month': 7, 'day': 20, 'hour': 22, 'minute': 30, 'draught': 10.5, 'destination': 'DEHAM', 'dte': False}, 'date': '2020-07-20 23:17:14.338438'}

The 'date' is added later on to the dict.

And with the next message pyais crushes, not sure if it is a coincident with the messages above:

Traceback (most recent call last):
File "./get2.py", line 31, in
ais_print = message.decode()
File "/usr/local/lib/python3.7/dist-packages/pyais/messages.py", line 139, in decode
return AISMessage(self)
File "/usr/local/lib/python3.7/dist-packages/pyais/messages.py", line 151, in init
self.content = decode(self.nmea)
File "/usr/local/lib/python3.7/dist-packages/pyais/decode.py", line 677, in decode
return DECODE_MSGmsg.ais_id
File "/usr/local/lib/python3.7/dist-packages/pyais/decode.py", line 38, in decode_msg_1
'raim': bit_arr[148],
IndexError: bitarray index out of range

Not sure how to debug this, hope it helps?!

Best
Frank

Incorrect decoding field types

After the changes made in #54 I decided to check the field types for every message type in the decoder against the docs.

I have found a number of fields which have incorrect or inconsistent types, I have detailed them all below to be fixed.

All Types

The naming is inconsistent for spare and regional, MessageType18 has reserved and reserved_2 rather than reserved_1 and reserved_2 and both fields change this up on a per message basis.
_1, _2 etc should be used for all fields which have multiple instances such as reserved and spare.

Single bit fields accuracy, raim, dte, retransmit, assigned, cs, display, dsc, band, msg22, virtual_aid, power, addressed, band_a, band_b, structured and gnss are boolean according to the docs for some message types and int for others. As these are single bit fields they should all be boolean for all message types.

Currently accuracy and gnss are not boolean for all message types in the library.

Types 1, 2 & 3

turn according to the docs is a Signed integer with scale - renders as float, suffix is decimal places but its type is int in the library.
As the docs say renders as float, I believe this is actually meant to be a float.

Type 6 & Type 8 & Type 17 & Type 25

data should be a string representation of the binary and not a int as you cannot have leading 0's in python integers.

Type 19

reserved/regional naming is completely inconsistent here. Naming for the Regional reserved fields previously has followed reserved, reserved_2... but here it is reserved, regional. The naming of the reserved & spare fields should be consistent across all message types and use _1 as previously stated.

Type 24 & Type 25 & Type 26

The dict of types you provided in #54 appears to be missing vendorid, model and serial from from Type 24 and addressed, structured, app_id from Type 25 & Type 26

I don't know how you generated them but I presume these missing fields are due to the increased complexity with these types.

ImportError: cannot import name 'OrderedDict'

I am dealing with the issue described in the title. I am using Google Colab for dealing with the data (Python 3.6.9). This is the notebook with the code. It appears some problem related with the typing library. Also, you can download the ais used in this link.

Thank you so much for your work!

from pyais import FileReaderStream

filename = '20201015T122532.txt'
for msg in FileReaderStream(filename):
    decoded_message = msg.decode()
    ais_content = decoded_message.content
/usr/local/lib/python3.6/dist-packages/pyais/util.py in <module>()
      1 from functools import partial, reduce
      2 from operator import xor
----> 3 from typing import OrderedDict, Sequence, Iterable
      4 
      5 from bitarray import bitarray

ImportError: cannot import name 'OrderedDict'

AttributeError: 'NoneType' object has no attribute 'to_json'

Hi,

I have this:

msg = b'!AIVDM,1,1,,B,0S9edj0P03PecbBN`ja@0?w42cFC,0*7C'
msg = NMEAMessage(msg)

which returns

None

so if I pass this directly to convert it to json

message = msg.decode().to_json()
I get:

Traceback (most recent call last):
File "./get3.py", line 33, in
message = msg.decode().to_json()
AttributeError: 'NoneType' object has no attribute 'to_json'

to_json can't handle these types, might be a good feature for new minor release :-)

Best,
Frank

Invalid NMEA message that halts the execution of a py script

While receiving data from AISHUB, I got the following error message when I ran a script to create a separate json file for every minute. It seems to point to an invalid NMEA message. This error kills the script.

Unfortunately this happens every few minutes. It would be great if it just continues and drops the invalid nmea message.

Error message:

Traceback (most recent call last):
  File "/home/rstudio/AISHUB_stream2json.py", line 23, in <module>
    for msg in TCPStream(host, port):
  File "/home/rstudio/.local/share/r-miniconda/envs/r-reticulate/lib/python3.6/site-packages/pyais/stream.py", line 37, in _assemble_messages
    msg: NMEAMessage = NMEAMessage(line)
  File "/home/rstudio/.local/share/r-miniconda/envs/r-reticulate/lib/python3.6/site-packages/pyais/messages.py", line 43, in __init__
    raise InvalidNMEAMessageException("A NMEA message needs to have exactly 7 comma separated entries.")
pyais.exceptions.InvalidNMEAMessageException: A NMEA message needs to have exactly 7 comma separated entries.

Code I used to run:

from pyais import TCPStream
from datetime import datetime
import json
import os
# import shutil

# AISHUB server details
host = "data.aishub.net"
port = 4214 

start = datetime.now()
# now = datetime.now()

# new_file.close()
dt_string = start.strftime("%Y%m%d_%H%M%S")
filename_parts = ["./1_stream/", "nmea_", dt_string, ".json"]
filename = "".join(filename_parts)
filename_parts_destination = ["./1_stream_completed/", "nmea_", dt_string, ".json"]
filename_destination = "".join(filename_parts_destination)

new_file=open(filename,mode="w",encoding="utf-8")

for msg in TCPStream(host, port):
  print(msg)
  # print("---")
  json_data = msg.decode().to_json()
  print("---")
  json_data = json_data.replace(' ', '') 
  json_data = json_data.replace('\n', '') 
  if (datetime.now() - start).total_seconds() > 60:
    new_file.close()
    os.rename(filename, filename_destination)
    start = datetime.now()
    dt_string = start.strftime("%Y%m%d_%H%M%S")
    filename_parts = ["./1_stream/", "nmea_", dt_string, ".json"]
    filename = "".join(filename_parts)
    filename_parts_destination = ["./1_stream_completed/", "nmea_", dt_string, ".json"]
    filename_destination = "".join(filename_parts_destination)
    new_file=open(filename,mode="w",encoding="utf-8")
  json2file = "".join([json_data,"\n"])
  print("---")
  new_file.write(json2file)
  print("---")

Decoding MMSI as int loses leading zeroes

All of the messages in pyais/decode.py are decoding the MMSI value as an integer. However, if there are leading zeroes (which is possible and valid -- see navcen documentation) they are going to be lost. In order to preserve the full original MMSI value, shouldn't these be parsed into strings?

failed parse some MessageType5 data , it throw 'MissingMultipartMessageException'

face this problem, the decode.py code logic bellow:

# Make sure provided parts assemble a single (multiline message)
  if len(args) > frag_cnt:
      raise TooManyMessagesException(f"Got {len(args)} messages, but fragment count is {frag_cnt}")

while parse string :
!AIVDM,2,1,6,A,55MwgS`00001L@?;OK80b022220l1@:44vT4,0*6B

it throw exception:

 raise MissingMultipartMessageException(f"Missing fragment numbers: {diff}")
pyais.exceptions.MissingMultipartMessageException: Missing fragment numbers: [2]

what is the reason ?

if I comment the check logic , then all things works fine , anyone can give tip for this case ?

Incorect fill bits

There are errors in messages generated by pyais:

!AIVDM,2,1,,B,54Rofa@2;t484<o3340t8=V3400000000000000t2H0147?P06lm1D3R@h@0,2*45
!AIVDM,2,2,,B,00000000000,2*17

It should be:

!AIVDM,2,1,,B,54Rofa@2;t484<o3340t8=V3400000000000000t2H0147?P06jkk3hTVQAh,0*41
!AIVDM,2,2,,B,00000000000,2*17

Number of fill bits on the first line is ...,2*45. Proper value is ...,0*41.
It is probably allways 0 on the first line of multiline messages.

Messagetype27 lat/long should be signed

pyais/pyais/messages.py

Lines 1362 to 1363 in 21da419

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)

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.

int_to_bin assumes signed integer when converting to bytes

Problem was found when encoding a dictionary back into a NMEA message.

In the int_to_bin function in the util.py file, the second to last statement converts the value into bytes and then into a bit array. However, when the value is converted to_bytes it always assumes the value is signed.

I ran into an issue when encoding a message type 5 that had a draught value of 12.8. 12.8 gets multiplied up to 128 in the from_course function. 128 is then ultimately passed to int_to_bin and expected to fit into 1 byte. However, a signed value of 128 doesn't fit into 1 byte. So I think int_to_bin needs to be told the signed-ness of the value from the calling function, it can't always be set to signed=True.

Different payload after decode/encode roundtrip

First of all thanks for maintaining this fantastic project!

I am currently trying to generate some mock NMEA data by modifying fields in an existing data stream. That requires a decode/encode roundtrip (followed by another decode later on).

I noticed that the NMEA sentences change, even when not modifying the AIS data. After decoding another time, the AIS data is also different.

Example

example.py

import sys

from pyais.decode import _assemble_messages
import pyais.encode
from pyais.stream import BinaryIOStream

# Read NMEA sentences from stdin
for nmea in BinaryIOStream(sys.stdin.buffer):
    print(nmea)
    assert nmea.is_valid
    # decode AIS message
    m = nmea.decode()
    print(m)
    # encode and decode AIS -> NMEA -> AIS three more times
    for _ in range(3):
        sentences = pyais.encode.encode_msg(
            m, talker_id='AI'+nmea.type, radio_channel=nmea.channel)
        sentences = [s.encode() for s in sentences]
        nmea = _assemble_messages(*sentences)
        print(nmea)
        assert nmea.is_valid
        m = nmea.decode()
        print(m)
    print('---')

After feeding this script with the file from this repo's test folder, many sentences and messages don't match up e.g. the first set of output:

python example.py < ais_test_messages

b'!AIVDM,1,1,,A,13HOI:0P0000VOHLCnHQKwvL05Ip,0*23\n'
MessageType1(msg_type=1, repeat=0, mmsi=227006760, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=None, speed=0.0, accuracy=False, lon=0.13138, lat=49.475577, course=36.7, heading=511, second=14, maneuver=0, spare_1=b'\x00', raim=False, radio=22136)
b'!AIVDM,1,1,,A,13HOI:00002IuQi?IR5gwqh0EWP,2*0E'
MessageType1(msg_type=1, repeat=0, mmsi=227006760, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=0.0, speed=0.0, accuracy=False, lon=33.633373, lat=-84.936497, course=409.5, heading=312, second=0, maneuver=0, spare_1=b'\xa0', raim=False, radio=1656)
b'!AIVDM,1,1,,A,13HOI:00002IuQg?IR5gwqh0D0Ip,0*0D'
MessageType1(msg_type=1, repeat=0, mmsi=227006760, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=0.0, speed=0.0, accuracy=False, lon=33.633372, lat=-84.936497, course=409.5, heading=312, second=0, maneuver=0, spare_1=b'\xa0', raim=False, radio=1656)
b'!AIVDM,1,1,,A,13HOI:00002IuQg?IR5gwqh0D0Ip,0*0D'
MessageType1(msg_type=1, repeat=0, mmsi=227006760, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=0.0, speed=0.0, accuracy=False, lon=33.633372, lat=-84.936497, course=409.5, heading=312, second=0, maneuver=0, spare_1=b'\xa0', raim=False, radio=1656)

A second roundtrip changes the payload again, although fewer bits seem to change (sometimes all AIS fields stay the same). After the second roundtrip, the data stays the same and further encodes and decodes result in the same NMEA sentence and AIS information.

Is ROT(Turn) decoding properly?

Hello,

I am seeing a lot of sentences where the value for "turn" is being set to None, which is causing them to equal <null> when I store them. Using some of the lines from your decode.py example:

import pyais as pa
from pyais import decode

print('version: ', end='')
print(pa.__version__)

# Decode a single part using decode
decoded = decode(b"!AIVDM,1,1,,B,15NG6V0P01G?cFhE`R2IU?wn28R>,0*05")
print(decoded)

# Added my sentence for example 2
decoded = decode("!AIVDM,1,1,,B,15Nfbk3P00rq:4`Gkga7MwvP2L0L,0*0D")
print(decoded)

For both sentence we gate a value of None for turn:

version: 2.1.2
MessageType1(msg_type=1, repeat=0, mmsi=367380120, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=None, speed=0.1, accuracy=False, lon=-122.404333, lat=37.806948, course=245.2, heading=511, second=59, maneuver=0, spare_1=b'\x00', raim=True, radio=34958)
MessageType1(msg_type=1, repeat=0, mmsi=367766220, status=<NavigationStatus.RestrictedManoeuverability: 3>, turn=None, speed=0.0, accuracy=True, lon=-71.39986, lat=41.60838, course=191.1, heading=511, second=16, maneuver=0, spare_1=b'\x00', raim=True, radio=114716)

From an online decoder (http://ais.tbsalling.dk/) using the same sentence(s) we get a value of no turn information available:

rateOfTurn | -128

When I decode and store these values as a float they are being set to <null>. I am confused as to how the from_converter and to_converter are working. Can you please explain? Is the bit field just not receiving the right data?

Thanks.
-bill

print(sys.version)
3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022, 16:36:42) [MSC v.1929 64 bit (AMD64)]

messages with missing sequential ID number or grater than 9

I used this library to decode coming NMAE data from an AIS receiver, I don't really have that deep understanding of the working principle on NMEA messages but from what I found that the fourth number after the comma which is the sequential ID should be from 1 to 9
However on my case I faced two uncommon types where sometimes the sequential ID is 10, or just empty which will leads to "not supported type of messages" error.
As quick fix, on my code I added a condition to set the sequential ID to any number less than 9 in these cases, just to avoid the rising errors...because I notice that changing the "sequential ID" doesn't have any effect on the other message values (mmsi, type ...)
It seems not the correct way of solving this but it's a temporary solution...
I hope you consider my report, and If go wrong on any part please don't hesitate to contact me.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.