Commit 6735f056 authored by Dag Brattli's avatar Dag Brattli

Added support for message type 4. Fixed bug with negative latitude and longitudes

parent e372eccd
......@@ -7,12 +7,13 @@ System (AIS) [AIVDM/AIVDO](http://catb.org/gpsd/AIVDM.html) NMEA message decoder
The library is Written in F# using [FParsec](http://www.quanttec.com/fparsec/). The main advantage of
using a parser combinator library such as FParsec, and using an applicative (functor) style is that
the implementation looks very similar to the specification, thus the code is clean, and easy to extend
the implementation looks very similar to the specification, thus the code is clean, easy to extend
and validate against the specification.
Currently parses:
* Types 1, 2 and 3: Position Report Class A
* Type 4: Base Station Report
* Type 5: Static and Voyage Related Data
The parser works in two stages. In the first stage you parse the outer layer of the AIS data packet:
......
......@@ -79,6 +79,37 @@ type public Parser() =
| Failure (error, _, _) ->
raise (System.ArgumentException(error))
/// <summary>
/// Parses an AIS data packet inner layer of message type 4.
/// This is the second stage of parsing a VDM/VDO data packet.
/// </summary>
/// <param name="input">An AisResult to parse the payload of.</param>
/// <param name="result">
/// A MessageType5 that will be set if the method returns true.
/// </param>
/// <returns>
/// True if parsing of the packet completed. False if this is not
/// a message of type 4.
/// </returns>
/// <exception cref="System.ArgumentException">Thrown if the data
/// payload is invalid and parsing of the packet failed.
/// </exception>
member public _this.TryParse(input: AisResult, [<Out>] result : MessageType4 byref) : bool =
let binaryString = Common.intListToBinaryString input.Payload
let res = run Type4.parseMessageType4 binaryString
match res with
| Success (message, _, _) ->
match message with
| Type4 cnb ->
result <- cnb
true
| _ ->
false
| Failure (error, _, _) ->
raise (System.ArgumentException(error))
/// <summary>
/// Parses an AIS data packet inner layer of message type 5.
/// This is the second stage of parsing a VDM/VDO data packet.
......
......@@ -16,7 +16,7 @@ type NavigationStatus =
| AisSartIsActive = 14
| NotDefined = 15
type EpdfFixType =
type EpfdFixType =
| Undefined = 0
| Gps = 1
| Glonass = 2
......@@ -80,13 +80,33 @@ type MessageType123 = {
Status: NavigationStatus;
RateOfTurn: float;
SpeedOverGround: int;
PositionAccuracy: int;
PositionAccuracy: bool;
Longitude: float;
Latitude: float;
CourseOverGround: float;
TrueHeading: int;
TimeStamp: int;
ManeuverIndicator: ManeuverIndicator;
RaimFlag: bool;
RadioStatus: int
}
// Base Station Report
type MessageType4 = {
Repeat: byte;
Mmsi: int;
Year: int;
Month: int;
Day: int;
Hour: int;
Minute: int;
Second: int;
FixQuality: bool;
Longitude: float;
Latitude: float;
Epfd: EpfdFixType;
RaimFlag: bool;
RadioStatus: int
}
// Static And Voyage Related Data
......@@ -102,7 +122,7 @@ type MessageType5 = {
ToStern: int;
ToPort: int;
ToStarBoard: int;
Epfd: EpdfFixType;
Epfd: EpfdFixType;
Month: int;
Day: int;
Hour: int;
......@@ -119,6 +139,7 @@ type BaseStationReport = {
type MessageType =
| Type123 of MessageType123
| Type4 of MessageType4
| Type5 of MessageType5
module Common =
......@@ -152,17 +173,41 @@ module Common =
pstring type'
|>> fun x -> Convert.ToByte(x, 2)
let parseType3 (type1: string) (type2: string) (type3: string)=
pstring type1 <|> pstring type2 <|> pstring type3
let parseType3 (type1: string) (type2: string) (type3: string) =
pstring type1
<|> pstring type2
<|> pstring type3
|>> fun x -> Convert.ToByte(x, 2)
let parseRepeat = Core.parseUint2
let parseMmsi = Core.parseUint30
let parseLongitude =
Core.parseBits 28
|>> fun x -> (String.replicate 5 x.[..0]) + x.[1..]
|>> fun x -> Convert.ToInt32(x, 2)
|>> fun x -> float(x) / 600000.0
let parseLatitude =
Core.parseBits 27
|>> fun x -> (String.replicate 6 x.[..0]) + x.[1..]
|>> fun x -> Convert.ToInt32(x, 2)
|>> fun x -> float(x) / 600000.0
let parseEpfd =
Core.parseBits 4
|>> (fun x ->
let value = Convert.ToInt32(x, 2)
enum<EpdfFixType>(value)
enum<EpfdFixType>(value)
)
let parseSpare =
Core.parseBits 3
let parseRaimFlag =
Core.parseBool
let parseRadioStatus =
Core.parseBits 19
|>> fun x -> Convert.ToInt32(x, 2)
\ No newline at end of file
......@@ -13,6 +13,10 @@ module Core =
let inline parseBits count : Parser<_> =
manyMinMaxSatisfy count count isBit
let parseBool =
parseBits 1
|>> fun x -> Convert.ToByte(x, 2) = 1uy
let parseUint2 =
parseBits 2
|>> fun x -> Convert.ToByte(x, 2)
......
......@@ -12,6 +12,7 @@
<Compile Include="Core.fs" />
<Compile Include="Common.fs" />
<Compile Include="Type123.fs" />
<Compile Include="Type4.fs" />
<Compile Include="Type5.fs" />
<Compile Include="Ais.fs" />
<Compile Include="Api.fs" />
......
......@@ -7,8 +7,8 @@ open NAisParser.Core
module Type123 =
let messageType123 type' repeat mmsi status turn
speed accuracy lon lat course heading second maneuver: MessageType123=
let messageType123 type' repeat mmsi status turn speed accuracy lon
lat course heading second maneuver raim radio: MessageType123=
{
Type = type'
Repeat = repeat;
......@@ -23,6 +23,8 @@ module Type123 =
TrueHeading = heading;
TimeStamp = second;
ManeuverIndicator = maneuver;
RaimFlag = raim;
RadioStatus = radio;
}
let defaultMessageType123: MessageType123= {
......@@ -32,13 +34,15 @@ module Type123 =
Status = NavigationStatus.NotDefined;
RateOfTurn = 128.0;
SpeedOverGround = 0;
PositionAccuracy = 0;
PositionAccuracy = false;
Longitude = 181.0;
Latitude = 91.0;
CourseOverGround = 0.0;
TrueHeading = 511;
TimeStamp = 0;
ManeuverIndicator = ManeuverIndicator.NoSpecialManeuver;
RaimFlag = false;
RadioStatus = 0;
}
let parseRateOfTurn =
......@@ -51,18 +55,7 @@ module Type123 =
|>> fun x -> Convert.ToInt32(x, 2)
let parsePositionAccuracy =
Core.parseBits 1
|>> fun x -> Convert.ToInt32(x, 2)
let parseLongitude =
Core.parseBits 28
|>> fun x -> Convert.ToInt32(x, 2)
|>> fun x -> float(x) / 600000.0
let parseLatitude =
Core.parseBits 27
|>> fun x -> Convert.ToInt32(x, 2)
|>> fun x -> float(x) / 600000.0
Core.parseBool
let parseCourseOverGround =
Core.parseBits 12
......@@ -91,7 +84,8 @@ module Type123 =
enum<ManeuverIndicator>(value)
let parseType123 : Parser<_> =
Common.parseType3 "000001" "000010" "000011"
// A little repetitive, but better to do it here at declaration time
Common.parseType3 (Common.toPaddedBinary 1) (Common.toPaddedBinary 2) (Common.toPaddedBinary 3)
let parseMessageType123: Parser<_> =
preturn messageType123
......@@ -102,10 +96,13 @@ module Type123 =
<*> parseRateOfTurn
<*> parseSpeedOverGround
<*> parsePositionAccuracy
<*> parseLongitude
<*> parseLatitude
<*> Common.parseLongitude
<*> Common.parseLatitude
<*> parseCourseOverGround
<*> parseTrueHeading
<*> parseTimeStamp
<*> parseManeuverIndicator
<* Common.parseSpare
<*> Common.parseRaimFlag
<*> Common.parseRadioStatus
|>> Type123
\ No newline at end of file
namespace NAisParser
open System
open FParsec
open NAisParser.Core
module Type4 =
let messageType4 repeat mmsi year month day hour minute second
accuracy lat lon epfd raim radio
: MessageType4 =
{
Repeat = repeat;
Mmsi = mmsi;
Year = year;
Month = month;
Day = day;
Hour = hour;
Minute = minute;
Second = second;
FixQuality = accuracy;
Longitude = lon;
Latitude = lat;
Epfd = epfd
RaimFlag = raim;
RadioStatus = radio;
}
let defaultMessageType4: MessageType4 = {
Repeat = 0uy;
Mmsi = 0;
Year = 0;
Month = 0;
Day = 0;
Hour = 24;
Minute = 60;
Second = 60;
FixQuality = false;
Longitude = 181.0;
Latitude = 91.0;
Epfd = EpfdFixType.Undefined;
RaimFlag = false;
RadioStatus = 0
}
let parseYear =
Core.parseBits 14
|>> fun x -> Convert.ToInt32(x, 2)
let parseMonth =
Core.parseBits 4
|>> fun x -> Convert.ToInt32(x, 2)
let parseDay =
Core.parseBits 5
|>> fun x -> Convert.ToInt32(x, 2)
let parseHour = parseDay
let parseMinute =
Core.parseBits 6
|>> fun x -> Convert.ToInt32(x, 2)
let parseSecond = parseMinute
let parseFixQuality = Core.parseBool
let parseType4 = Common.parseType <| Common.toPaddedBinary 4
let parseMessageType4: Parser<MessageType> =
preturn messageType4
*> parseType4
<*> Common.parseRepeat
<*> Common.parseMmsi
<*> parseYear
<*> parseMonth
<*> parseDay
<*> parseHour
<*> parseMinute
<*> parseSecond
<*> parseFixQuality
<*> Common.parseLongitude
<*> Common.parseLatitude
<*> Common.parseEpfd
<* Common.parseSpare
<*> Common.parseRaimFlag
<*> Common.parseRadioStatus
|>> Type4
......@@ -43,7 +43,7 @@ module Type5 =
ToStern = 0;
ToPort = 0;
ToStarBoard = 0;
Epfd = EpdfFixType.Undefined;
Epfd = EpfdFixType.Undefined;
Month = 0;
Day = 0;
Hour = 24;
......@@ -83,13 +83,6 @@ module Type5 =
let parseToStarboard = parseToPort
let parseEpdf =
Core.parseBits 4
|>> (fun x ->
let value = Convert.ToInt32(x, 2)
enum<EpdfFixType>(value)
)
let parseMonth =
Core.parseBits 4
|>> fun x -> Convert.ToInt32(x, 2)
......@@ -110,9 +103,9 @@ module Type5 =
let parseDestination = Core.parseAscii 120
let parseDte = Core.parseBits 1 |>> fun x -> Convert.ToByte(x, 2) = 1uy
let parseDte = Core.parseBool
let parseType5 = Common.parseType "000101"
let parseType5 = Common.parseType <| Common.toPaddedBinary 5
let parseMessageType5: Parser<MessageType> =
preturn messageType5
......@@ -128,7 +121,7 @@ module Type5 =
<*> parseToStern
<*> parseToPort
<*> parseToStarboard
<*> parseEpdf
<*> Common.parseEpfd
<*> parseMonth
<*> parseDay
<*> parseHour
......
......@@ -172,3 +172,30 @@ type TestApi () =
// Assert
result1 |> should be True
[<Test>]
member _this.``Test API parse type 4 is success`` () =
// Arrage
let input = "!AIVDM,1,1,,A,400TcdiuiT7VDR>3nIfr6>i00000,0*78";
let mutable aisResult = ref Ais.defaultAisResult
let mutable cnbResult = ref Type4.defaultMessageType4
let parser = Parser()
// Act
let result1 = parser.TryParse(input, aisResult)
let result2 = parser.TryParse(aisResult.Value, cnbResult)
// Assert
result2 |> should be True
cnbResult.Value.Mmsi |> should equal 601011
cnbResult.Value.Year |> should equal 2012
cnbResult.Value.Month |> should equal 6
cnbResult.Value.Day |> should equal 8
cnbResult.Value.Hour |> should equal 7
cnbResult.Value.Minute |> should equal 38
cnbResult.Value.Second |> should equal 20
cnbResult.Value.FixQuality |> should equal true
cnbResult.Value.Latitude |> should equal 31.033513333333332
cnbResult.Value.Longitude |> should equal -29.870835
cnbResult.Value.Epfd |> should equal EpfdFixType.Gps
cnbResult.Value.RaimFlag |> should equal false
\ No newline at end of file
......@@ -25,10 +25,10 @@ type TestClassType4 () =
let result2 =
match result with
| Success (ais, _, _) ->
run Type123.parseMessageType123 (Common.intListToBinaryString ais.Payload)
run Type4.parseMessageType4 (Common.intListToBinaryString ais.Payload)
| Failure (a, b, c) -> Failure(a, b, c)
// Assert
isSuccess(result) |> should be True
isSuccess(result2) |> should be False
isSuccess(result2) |> should be True
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment