AIS Daten werden schon halbwegs okay empfangen
This commit is contained in:
parent
ac84f46ae8
commit
e98d4ae563
@ -29,11 +29,12 @@ namespace bsmd.AIS2Service
|
||||
FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
|
||||
string version = fvi.FileVersion;
|
||||
_log.InfoFormat("Starting AIS2 Service. v.{0} -------------- ", version);
|
||||
|
||||
AISManager.Start();
|
||||
}
|
||||
|
||||
protected override void OnStop()
|
||||
{
|
||||
AISManager.Stop();
|
||||
_log.Info("AIS2 Service stopped.");
|
||||
}
|
||||
}
|
||||
|
||||
263
AIS/bsmd.AIS2Service/AISClass.cs
Normal file
263
AIS/bsmd.AIS2Service/AISClass.cs
Normal file
@ -0,0 +1,263 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bsmd.AIS2Service
|
||||
{
|
||||
internal abstract class AISClass
|
||||
{
|
||||
|
||||
#region constant defs
|
||||
|
||||
protected const double ERAD = 6378.135;
|
||||
protected const double DE2RA = 0.01745329252;
|
||||
protected const double AVG_ERAD = 6371.0;
|
||||
|
||||
#endregion
|
||||
|
||||
#region enumerations
|
||||
|
||||
public enum Status
|
||||
{
|
||||
OK,
|
||||
UNKNOWN_TYPE,
|
||||
UNSUPPORTED,
|
||||
ILLEGAL_ARGUMENT,
|
||||
PARSE_ERROR
|
||||
}
|
||||
|
||||
public enum AISType
|
||||
{
|
||||
AIS_NONE = 0,
|
||||
POSITION_REPORT,
|
||||
POSITION_REPORT_ASSIGNED,
|
||||
POSITION_REPORT_SPECIAL,
|
||||
BASE_STATION_REPORT,
|
||||
STATIC_VOYAGE_DATA,
|
||||
BINARY_ADDR_MSG,
|
||||
BINARY_ACK,
|
||||
BINARY_BCAST,
|
||||
SAR_AIRCRAFT,
|
||||
UTC_INQUIRY,
|
||||
UTC_RESPONSE,
|
||||
SAFETY_RELATED_MSG,
|
||||
SAFETY_RELATED_ACK,
|
||||
SAFETY_RELATED_BCAST,
|
||||
INTERROGATION,
|
||||
ASSIGN_MODE_CMD,
|
||||
DGNSS_BCAST,
|
||||
POSITION_REPORT_B_EQUIP,
|
||||
POSITION_REPORT_B_EQUIP_EXT,
|
||||
DATA_LINK_MAN,
|
||||
AIDS_TO_NAV_REPORT,
|
||||
CHANNEL_MNGNT,
|
||||
GROUP_ASSIGNMENT,
|
||||
CLASS_B_STATIC_DATA
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region fields
|
||||
|
||||
private AISType _type = AISType.AIS_NONE;
|
||||
protected int _mmsi;
|
||||
protected string _data;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public AISType MessageType
|
||||
{
|
||||
get { return this._type; }
|
||||
}
|
||||
|
||||
public int MMSI
|
||||
{
|
||||
get { return _mmsi; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region abstract method signatures
|
||||
|
||||
protected abstract Status Decode();
|
||||
|
||||
#endregion
|
||||
|
||||
#region static methods
|
||||
|
||||
internal static AISClass Decode(string data, ref Status status)
|
||||
{
|
||||
AISClass result = null;
|
||||
|
||||
if (data == null || data.Length == 0)
|
||||
{
|
||||
status = Status.ILLEGAL_ARGUMENT;
|
||||
return null;
|
||||
}
|
||||
|
||||
BitArray bits = AISClass.DecodeChar(data[0]);
|
||||
int type = AISClass.GetInt(bits, 0, 5);
|
||||
|
||||
result = AISClass.CreateMessage(type);
|
||||
if (result != null)
|
||||
{
|
||||
result._data = data;
|
||||
status = result.Decode();
|
||||
}
|
||||
else
|
||||
{
|
||||
status = Status.UNSUPPORTED;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory method to create messages based on the message type
|
||||
/// </summary>
|
||||
protected static AISClass CreateMessage(int type)
|
||||
{
|
||||
AISClass result = null;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case 1:
|
||||
result = new AIS_PosReport();
|
||||
result._type = AISType.POSITION_REPORT;
|
||||
break;
|
||||
case 2:
|
||||
result = new AIS_PosReport();
|
||||
result._type = AISType.POSITION_REPORT_ASSIGNED;
|
||||
break;
|
||||
case 3:
|
||||
result = new AIS_PosReport();
|
||||
result._type = AISType.POSITION_REPORT_SPECIAL;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
result = new AIS_StaticData();
|
||||
result._type = AISType.STATIC_VOYAGE_DATA;
|
||||
break;
|
||||
|
||||
case 18:
|
||||
result = new AIS_ClassB();
|
||||
result._type = AISType.POSITION_REPORT_B_EQUIP;
|
||||
break;
|
||||
|
||||
case 19:
|
||||
result = new AIS_ClassBExt();
|
||||
result._type = AISType.POSITION_REPORT_B_EQUIP_EXT;
|
||||
break;
|
||||
|
||||
case 24:
|
||||
result = new AIS_ClassBStatic();
|
||||
result._type = AISType.CLASS_B_STATIC_DATA;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#region static helpers
|
||||
|
||||
protected static BitArray DecodeChar(char c)
|
||||
{
|
||||
Byte b = Convert.ToByte(c);
|
||||
return DecodeByte(b);
|
||||
}
|
||||
|
||||
protected static BitArray DecodeByte(byte c)
|
||||
{
|
||||
c += 40;
|
||||
if (c > 128) c += 32;
|
||||
else c += 40;
|
||||
|
||||
c &= 63;
|
||||
|
||||
BitArray b = new BitArray(6);
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
b[i] = ((c >> (5 - i)) & 1) == 1;
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
protected static BitArray DecodeBinary(string data)
|
||||
{
|
||||
BitArray result = new BitArray(data.Length * 6, false);
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
BitArray charBits = DecodeChar(data[i]);
|
||||
|
||||
for (int j = 0; j < charBits.Count; j++)
|
||||
result[i * 6 + j] = charBits[j];
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected static int GetInt(BitArray bits, int lo, int hi)
|
||||
{
|
||||
int result = 0;
|
||||
int test = 1;
|
||||
|
||||
for (int i = hi; i >= lo; i--, test <<= 1)
|
||||
{
|
||||
if (bits[i]) result |= test;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected static char GetAISChar(int val)
|
||||
{
|
||||
if (val < 32) return Convert.ToChar(val + 64);
|
||||
if (val < 64) return Convert.ToChar(val);
|
||||
return ' ';
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region public static helpers
|
||||
|
||||
/// <summary>
|
||||
/// mehr dazu hier:
|
||||
/// http://www.codeguru.com/cpp/cpp/algorithms/general/article.php/c5115/
|
||||
/// </summary>
|
||||
public static double GetDistance(double lat1, double lon1, double lat2, double lon2)
|
||||
{
|
||||
lat1 *= DE2RA;
|
||||
lon1 *= DE2RA;
|
||||
lat2 *= DE2RA;
|
||||
lon2 *= DE2RA;
|
||||
double d = Math.Sin(lat1) * Math.Sin(lat2) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Cos(lon1 - lon2);
|
||||
return (AVG_ERAD * Math.Acos(d));
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region overrides
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Enum.GetName(typeof(AISClass.AISType), this.MessageType);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
204
AIS/bsmd.AIS2Service/AISDecoder.cs
Normal file
204
AIS/bsmd.AIS2Service/AISDecoder.cs
Normal file
@ -0,0 +1,204 @@
|
||||
using log4net;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace bsmd.AIS2Service
|
||||
{
|
||||
internal class AISDecoder : IAISThread
|
||||
{
|
||||
|
||||
#region fields
|
||||
|
||||
private readonly ConcurrentQueue<string> _inputLines;
|
||||
private readonly ConcurrentQueue<AISClass> _outputAISClasses;
|
||||
private Thread _thread;
|
||||
private readonly Dictionary<string, List<AISQueueElement>> fragmentDict = new Dictionary<string, List<AISQueueElement>>();
|
||||
private const int sleepMS = 250;
|
||||
private bool _stopFlag = false;
|
||||
|
||||
private static readonly ILog _log = LogManager.GetLogger(typeof(AISDecoder));
|
||||
#endregion
|
||||
|
||||
#region construction
|
||||
|
||||
public AISDecoder(ConcurrentQueue<string> input, ConcurrentQueue<AISClass> output)
|
||||
{
|
||||
_inputLines = input;
|
||||
_outputAISClasses = output;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region class AISQueueElement
|
||||
|
||||
public class AISQueueElement
|
||||
{
|
||||
public int seq_nr;
|
||||
public int total_nr;
|
||||
public string id;
|
||||
public string data;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region private methods
|
||||
|
||||
/// <summary>
|
||||
/// Thread entry
|
||||
/// </summary>
|
||||
private void ReadData()
|
||||
{
|
||||
NMEA.Status status = NMEA.Status.OK;
|
||||
try
|
||||
{
|
||||
while (!_stopFlag)
|
||||
{
|
||||
if (_inputLines.TryDequeue(out string line))
|
||||
{
|
||||
NMEA decodedSentence = NMEA.Decode(line, ref status);
|
||||
if(decodedSentence != null)
|
||||
{
|
||||
if(decodedSentence is NMEA_AIS_Sentence aisSentence)
|
||||
{
|
||||
if(aisSentence.Total_Sentence_Nr == 1)
|
||||
{
|
||||
DecodeData(aisSentence.AIS_Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(aisSentence.Seq_Message_Ident.Length == 0)
|
||||
{
|
||||
_log.WarnFormat("message sequence ident is empty, but we have multipart message. Ignoring message");
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!fragmentDict.ContainsKey(aisSentence.Seq_Message_Ident))
|
||||
fragmentDict[aisSentence.Seq_Message_Ident] = new List<AISQueueElement>();
|
||||
fragmentDict[aisSentence.Seq_Message_Ident].Add(new AISQueueElement { data = aisSentence.AIS_Message, id = aisSentence.Seq_Message_Ident, seq_nr = aisSentence.Msg_Sentence_Nr, total_nr = aisSentence.Total_Sentence_Nr });
|
||||
if((fragmentDict[aisSentence.Seq_Message_Ident].Count > 1) && FragmentsComplete(fragmentDict[aisSentence.Seq_Message_Ident]))
|
||||
{
|
||||
string concatData = ConcatenateFragments(fragmentDict[aisSentence.Seq_Message_Ident]);
|
||||
fragmentDict.Remove(aisSentence.Seq_Message_Ident);
|
||||
DecodeData(concatData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(decodedSentence is NMEA_PNMLS_Sentence pnmlsSentence)
|
||||
{
|
||||
_log.Warn("cannot decode PNMLS sentence at this point");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.WarnFormat("NMEA decode failed with {0}", status);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(sleepMS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.ErrorFormat("Something bad has happened: {0}", ex.Message);
|
||||
this.FatalErrorOccurred?.Invoke(this, new EventArgs());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void DecodeData(string data)
|
||||
{
|
||||
AISClass.Status aisStatus = AISClass.Status.OK;
|
||||
AISClass decodedClass = AISClass.Decode(data, ref aisStatus);
|
||||
if(aisStatus == AISClass.Status.OK)
|
||||
{
|
||||
_outputAISClasses.Enqueue(decodedClass);
|
||||
_log.InfoFormat("Enqueuing AIS message for MMSI {0}", decodedClass.MMSI);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.WarnFormat("failed to decode AIS data: {0}", aisStatus);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region private helpers
|
||||
|
||||
/// <summary>
|
||||
/// check to see if all fragments are available
|
||||
/// </summary>
|
||||
private static bool FragmentsComplete(List<AISQueueElement> elements)
|
||||
{
|
||||
if (elements == null || elements.Count == 0) return false;
|
||||
int num = elements[0].total_nr;
|
||||
|
||||
for (int i = 1; i <= num; i++)
|
||||
{
|
||||
bool foundElements = false;
|
||||
for (int j = 0; j < elements.Count; j++)
|
||||
{
|
||||
if (elements[j].seq_nr == i)
|
||||
foundElements = true;
|
||||
}
|
||||
if (!foundElements) return false; // etwas fehlt noch
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// assembles message fragments. Care must be taken since fragments can appear
|
||||
/// out of order
|
||||
/// </summary>
|
||||
private static string ConcatenateFragments(List<AISQueueElement> elements)
|
||||
{
|
||||
if (elements == null || elements.Count == 0) return string.Empty;
|
||||
int num = elements[0].total_nr;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 1; i <= num; i++)
|
||||
{
|
||||
for (int j = 0; j < elements.Count; j++)
|
||||
if (elements[j].seq_nr == i)
|
||||
sb.Append(elements[j].data);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IAISThread implementation
|
||||
|
||||
public event EventHandler FatalErrorOccurred;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_thread != null) return; // may not run twice
|
||||
ThreadStart runReader = new ThreadStart(this.ReadData);
|
||||
_thread = new Thread(runReader);
|
||||
_thread.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_thread == null) return;
|
||||
_stopFlag = true;
|
||||
_thread.Join();
|
||||
_thread = null;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "AIS decoder"; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
41
AIS/bsmd.AIS2Service/AISManager.cs
Normal file
41
AIS/bsmd.AIS2Service/AISManager.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using log4net;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bsmd.AIS2Service
|
||||
{
|
||||
internal static class AISManager
|
||||
{
|
||||
private static readonly List<IAISThread> _tasks = new List<IAISThread>();
|
||||
private static readonly ConcurrentQueue<string> _inputLines = new ConcurrentQueue<string>();
|
||||
private static readonly ConcurrentQueue<AISClass> _decodedClasses = new ConcurrentQueue<AISClass>();
|
||||
private static readonly ILog _log = LogManager.GetLogger(typeof(AISManager));
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
_tasks.Add(new SerialTCPReader(Properties.Settings.Default.DataSourceHost, Properties.Settings.Default.DataSourcePort, _inputLines));
|
||||
_tasks.Add(new AISDecoder(_inputLines, _decodedClasses));
|
||||
|
||||
foreach (var task in _tasks)
|
||||
{
|
||||
task.Start();
|
||||
_log.InfoFormat("{0} started", task.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
foreach (var task in _tasks)
|
||||
{
|
||||
task.Stop();
|
||||
_log.InfoFormat("{0} stopped", task.Name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
155
AIS/bsmd.AIS2Service/AIS_ClassB.cs
Normal file
155
AIS/bsmd.AIS2Service/AIS_ClassB.cs
Normal file
@ -0,0 +1,155 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using log4net;
|
||||
|
||||
namespace bsmd.AIS2Service
|
||||
{
|
||||
internal class AIS_ClassB : AISClass
|
||||
{
|
||||
#region private members
|
||||
|
||||
private Guid? id;
|
||||
private int repeatIndicator;
|
||||
private int reserved;
|
||||
private int sog;
|
||||
private int accuracy;
|
||||
private int longitude;
|
||||
private int latitude;
|
||||
private int cog;
|
||||
private int trueHeading;
|
||||
private int utcTimestampSecs;
|
||||
private DateTime timestamp;
|
||||
private int reservedRegional;
|
||||
private int spare;
|
||||
private int assignedModeFlag;
|
||||
private int raimFlag;
|
||||
private int commStateSelectedFlag;
|
||||
private int commState;
|
||||
|
||||
private static ILog _log = LogManager.GetLogger(typeof(AIS_ClassB));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public Guid Id { get { if (!this.id.HasValue) this.id = Guid.NewGuid(); return this.id.Value; } }
|
||||
|
||||
public int CogVal { get { return this.cog; } }
|
||||
public int SogVal { get { return this.sog; } }
|
||||
public int LatitudeVal { get { return this.latitude; } }
|
||||
public int LongitudeVal { get { return this.longitude; } }
|
||||
|
||||
public string DBTimestamp
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.timestamp.ToString("yyyy-MM-ddTHH:mm:ss.000Z");
|
||||
}
|
||||
}
|
||||
|
||||
public double Cog
|
||||
{
|
||||
get { return this.cog / 10.0f; }
|
||||
}
|
||||
|
||||
public double Sog
|
||||
{
|
||||
get { return this.sog / 10.0f; }
|
||||
}
|
||||
|
||||
public double Latitude
|
||||
{
|
||||
get { return this.latitude / 600000.0f; }
|
||||
}
|
||||
|
||||
public double Longitude
|
||||
{
|
||||
get { return this.longitude / 600000.0f; }
|
||||
}
|
||||
|
||||
public DateTime Timestamp
|
||||
{
|
||||
get { return this.timestamp; }
|
||||
}
|
||||
|
||||
public int? TrueHeading
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.trueHeading == 511) return null;
|
||||
return this.trueHeading;
|
||||
}
|
||||
}
|
||||
|
||||
public int Reserved
|
||||
{
|
||||
get { return reserved; }
|
||||
}
|
||||
|
||||
public int Spare
|
||||
{
|
||||
get { return this.spare; }
|
||||
}
|
||||
|
||||
public int Raim
|
||||
{
|
||||
get { return this.raimFlag; }
|
||||
}
|
||||
|
||||
public int CommState
|
||||
{
|
||||
get { return this.commState; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region abstract method implementation
|
||||
|
||||
protected override Status Decode()
|
||||
{
|
||||
BitArray bits = DecodeBinary(this._data);
|
||||
Status result = Status.OK;
|
||||
|
||||
try
|
||||
{
|
||||
int type = GetInt(bits, 0, 5);
|
||||
if (type != 18)
|
||||
{
|
||||
result = Status.ILLEGAL_ARGUMENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.repeatIndicator = GetInt(bits, 6, 7);
|
||||
this._mmsi = GetInt(bits, 8, 37);
|
||||
this.reserved = GetInt(bits, 38, 45);
|
||||
this.sog = GetInt(bits, 46, 55);
|
||||
this.accuracy = GetInt(bits, 56, 56);
|
||||
this.longitude = GetInt(bits, 57, 84);
|
||||
this.latitude = GetInt(bits, 85, 111);
|
||||
this.cog = GetInt(bits, 112, 123);
|
||||
this.trueHeading = GetInt(bits, 124, 132);
|
||||
this.utcTimestampSecs = GetInt(bits, 133,138);
|
||||
this.timestamp = DateTime.Now;
|
||||
this.reservedRegional = GetInt(bits, 139, 140);
|
||||
this.spare = GetInt(bits, 141, 145);
|
||||
this.assignedModeFlag = GetInt(bits, 146, 146);
|
||||
this.raimFlag = GetInt(bits, 147, 147);
|
||||
this.commStateSelectedFlag = GetInt(bits, 148, 148);
|
||||
this.commState = GetInt(bits, 149, 167);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.WarnFormat("Error decoding AIS class B posreport: {0}", e.Message);
|
||||
result = Status.PARSE_ERROR;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
133
AIS/bsmd.AIS2Service/AIS_ClassBExt.cs
Normal file
133
AIS/bsmd.AIS2Service/AIS_ClassBExt.cs
Normal file
@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using log4net;
|
||||
|
||||
namespace bsmd.AIS2Service
|
||||
{
|
||||
/// <summary>
|
||||
/// Diese Nachricht wird normalerweise von Class B Geräten nicht verschickt.
|
||||
/// Sie wird nur als Antwort auf einen sog. "Base station poll" gesendet.
|
||||
/// Wir lesen sie trotzdem ;)
|
||||
/// </summary>
|
||||
// Todo
|
||||
internal class AIS_ClassBExt : AISClass
|
||||
{
|
||||
#region private members
|
||||
|
||||
private int repeatIndicator;
|
||||
private int spare1;
|
||||
private int sog;
|
||||
private int accuracy;
|
||||
private int longitude;
|
||||
private int latitude;
|
||||
private int cog;
|
||||
private int trueHeading;
|
||||
private int utcTimestampSecond;
|
||||
private DateTime timestamp;
|
||||
private int spare2;
|
||||
private string name;
|
||||
private int shipType;
|
||||
private int dimension;
|
||||
private int typeofDevice;
|
||||
private int raimFlag;
|
||||
private int dte;
|
||||
private int assignedMode;
|
||||
private int spare3;
|
||||
|
||||
private static ILog _log = LogManager.GetLogger(typeof(AIS_ClassBExt));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return this.name; }
|
||||
}
|
||||
|
||||
public DateTime Timestamp
|
||||
{
|
||||
get { return this.timestamp; }
|
||||
}
|
||||
|
||||
public double Latitude
|
||||
{
|
||||
get { return this.latitude / 600000.0f; }
|
||||
}
|
||||
|
||||
public double Longitude
|
||||
{
|
||||
get { return this.longitude / 600000.0f; }
|
||||
}
|
||||
|
||||
public int TrueHeading
|
||||
{
|
||||
get { return this.trueHeading; }
|
||||
}
|
||||
|
||||
public double Cog
|
||||
{
|
||||
get { return (double)this.cog / 10.0f; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override Status Decode()
|
||||
{
|
||||
BitArray bits = DecodeBinary(_data);
|
||||
Status result = Status.OK;
|
||||
|
||||
try
|
||||
{
|
||||
int type = GetInt(bits, 0, 5);
|
||||
if (type != 19)
|
||||
{
|
||||
result = Status.ILLEGAL_ARGUMENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.repeatIndicator = GetInt(bits, 6, 7);
|
||||
_mmsi = GetInt(bits, 8, 37);
|
||||
this.spare1 = GetInt(bits, 38, 45);
|
||||
this.sog = GetInt(bits, 46, 55);
|
||||
this.accuracy = GetInt(bits, 56, 56);
|
||||
this.longitude = GetInt(bits, 57, 84);
|
||||
this.latitude = GetInt(bits, 85, 111);
|
||||
this.cog = GetInt(bits, 112, 123);
|
||||
this.trueHeading = GetInt(bits, 124, 132);
|
||||
this.utcTimestampSecond = GetInt(bits, 133, 138);
|
||||
this.timestamp = DateTime.Now;
|
||||
this.spare2 = GetInt(bits, 139, 142);
|
||||
|
||||
StringBuilder sb_name = new StringBuilder(20);
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
int cval = GetInt(bits, 143 + (6 * i), 148 + (6 * i));
|
||||
char ch = GetAISChar(cval);
|
||||
if (ch == '@') ch = ' ';
|
||||
sb_name.Append(ch);
|
||||
}
|
||||
this.name = sb_name.ToString().Trim();
|
||||
|
||||
this.shipType = GetInt(bits, 263, 270);
|
||||
this.dimension = GetInt(bits, 271, 300);
|
||||
this.typeofDevice = GetInt(bits, 301, 304);
|
||||
this.raimFlag = GetInt(bits, 305, 305);
|
||||
this.dte = GetInt(bits, 306, 306);
|
||||
this.assignedMode = GetInt(bits, 307, 307);
|
||||
this.spare3 = GetInt(bits, 308, 311);
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.WarnFormat("Error decoding AIS class B Ext posreport: {0}", e.Message);
|
||||
result = Status.PARSE_ERROR;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
140
AIS/bsmd.AIS2Service/AIS_ClassBStatic.cs
Normal file
140
AIS/bsmd.AIS2Service/AIS_ClassBStatic.cs
Normal file
@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
using log4net;
|
||||
|
||||
namespace bsmd.AIS2Service
|
||||
{
|
||||
internal class AIS_ClassBStatic : AISClass
|
||||
{
|
||||
#region private members
|
||||
|
||||
private int repeatIndicator;
|
||||
private int partNumber;
|
||||
private string name;
|
||||
private int shipType;
|
||||
private string vendorId;
|
||||
private string callsign;
|
||||
private int dimension;
|
||||
private int spare;
|
||||
private static ILog _log = LogManager.GetLogger(typeof(AIS_ClassBStatic));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public bool IsPartA
|
||||
{
|
||||
get { return this.partNumber == 0; }
|
||||
}
|
||||
|
||||
public bool IsPartB
|
||||
{
|
||||
get { return this.partNumber == 1; }
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return this.name; }
|
||||
}
|
||||
|
||||
public string ShipType
|
||||
{
|
||||
get { return AIS_StaticData.GetShipType(this.shipType); }
|
||||
}
|
||||
|
||||
public string VendorId
|
||||
{
|
||||
get { return this.vendorId; }
|
||||
}
|
||||
|
||||
public string Callsign
|
||||
{
|
||||
get { return this.callsign; }
|
||||
}
|
||||
|
||||
public int ShipTypeVal
|
||||
{
|
||||
get { return this.shipType; }
|
||||
}
|
||||
|
||||
public int Dimension { get { return this.dimension; } }
|
||||
|
||||
public int Spare { get { return this.spare; } }
|
||||
|
||||
// Todo: Dimensions..
|
||||
|
||||
#endregion
|
||||
|
||||
#region abstract method implementation
|
||||
|
||||
protected override Status Decode()
|
||||
{
|
||||
BitArray bits = DecodeBinary(_data);
|
||||
Status result = Status.OK;
|
||||
|
||||
try
|
||||
{
|
||||
int type = GetInt(bits, 0, 5);
|
||||
if (type != 24)
|
||||
{
|
||||
result = Status.ILLEGAL_ARGUMENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.repeatIndicator = GetInt(bits, 6, 7);
|
||||
_mmsi = GetInt(bits, 8, 37);
|
||||
this.partNumber = GetInt(bits, 38, 39);
|
||||
if (this.IsPartA)
|
||||
{
|
||||
StringBuilder sb_name = new StringBuilder(20);
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
int cval = GetInt(bits, 40 + (6 * i), 45 + (6 * i));
|
||||
char ch = GetAISChar(cval);
|
||||
if (ch == '@') ch = ' ';
|
||||
sb_name.Append(ch);
|
||||
}
|
||||
this.name = sb_name.ToString().Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.shipType = GetInt(bits, 40, 47);
|
||||
|
||||
StringBuilder sb_vendor = new StringBuilder(7);
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
int cval = GetInt(bits, 48 + (6 * i), 53 + (6 * i));
|
||||
char ch = GetAISChar(cval);
|
||||
if (ch == '@') ch = ' ';
|
||||
sb_vendor.Append(ch);
|
||||
}
|
||||
this.vendorId = sb_vendor.ToString().Trim();
|
||||
|
||||
StringBuilder sb_callsign = new StringBuilder(7);
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
int cval = GetInt(bits, 90 + (6 * i), 95 + (6 * i));
|
||||
char ch = GetAISChar(cval);
|
||||
if (ch == '@') ch = ' ';
|
||||
sb_callsign.Append(ch);
|
||||
}
|
||||
this.callsign = sb_callsign.ToString().Trim();
|
||||
this.dimension = GetInt(bits, 141, 161);
|
||||
this.spare = GetInt(bits, 162, 167);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.WarnFormat("Error decoding AIS class B static data: {0}", e.Message);
|
||||
result = Status.PARSE_ERROR;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
198
AIS/bsmd.AIS2Service/AIS_PosReport.cs
Normal file
198
AIS/bsmd.AIS2Service/AIS_PosReport.cs
Normal file
@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using log4net;
|
||||
|
||||
namespace bsmd.AIS2Service
|
||||
{
|
||||
internal class AIS_PosReport : AISClass
|
||||
{
|
||||
private Guid? id;
|
||||
private int navstatus;
|
||||
private int rot;
|
||||
private int sog;
|
||||
private int accur;
|
||||
private int longitude;
|
||||
private int latitude;
|
||||
private int cog;
|
||||
private int trueheading;
|
||||
private DateTime timestamp;
|
||||
private int utcTimeSecond;
|
||||
private int reserved;
|
||||
private int spare;
|
||||
private int raim;
|
||||
private int commstate;
|
||||
|
||||
private static readonly ILog _log = LogManager.GetLogger(typeof(AIS_PosReport));
|
||||
|
||||
#region Properties
|
||||
|
||||
public Guid Id { get { if (!this.id.HasValue) this.id = Guid.NewGuid(); return this.id.Value; } }
|
||||
|
||||
public int NavStatusVal { get { return this.navstatus; } }
|
||||
public int ROTVal { get { return this.rot; } }
|
||||
public int SOGVal { get { return this.sog; } }
|
||||
public int COGVal { get { return this.cog; } }
|
||||
public int Accuracy { get { return this.accur; } }
|
||||
public int LatitudeVal { get { return this.latitude; } }
|
||||
public int LongitudeVal { get { return this.longitude; } }
|
||||
public string DBTimestamp
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.timestamp.ToString("yyyy-MM-ddTHH:mm:ss.000Z");
|
||||
}
|
||||
}
|
||||
|
||||
public double SOG
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((double)this.sog) / 10.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public double COG
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((double)this.cog) / 10.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public int ROT
|
||||
{
|
||||
get
|
||||
{
|
||||
return (int)((double)(this.rot * this.rot) / 22.401289);
|
||||
}
|
||||
}
|
||||
|
||||
public double Latitude
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((double)this.latitude) / 600000.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public double Longitude
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((double)this.longitude) / 600000.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public string NavStatus
|
||||
{
|
||||
get { return GetNavStatus(this.navstatus); }
|
||||
}
|
||||
|
||||
public DateTime Timestamp
|
||||
{
|
||||
get { return this.timestamp; }
|
||||
}
|
||||
|
||||
public int? TrueHeading
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.trueheading == 511) return null;
|
||||
return this.trueheading;
|
||||
}
|
||||
}
|
||||
|
||||
public int Reserved { get { return this.reserved; } }
|
||||
|
||||
public int Spare { get { return this.spare; } }
|
||||
|
||||
public int Raim { get { return this.raim; } }
|
||||
|
||||
public int CommState { get { return this.commstate; } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region static methods
|
||||
|
||||
public static string GetNavStatus(int navstatus)
|
||||
{
|
||||
switch (navstatus)
|
||||
{
|
||||
case 0:
|
||||
return "under way using engine";
|
||||
case 1:
|
||||
return "at anchor";
|
||||
case 2:
|
||||
return "not under command";
|
||||
case 3:
|
||||
return "restricted manoeuvrability";
|
||||
case 4:
|
||||
return "contrained by her draught";
|
||||
case 5:
|
||||
return "moored";
|
||||
case 6:
|
||||
return "aground";
|
||||
case 7:
|
||||
return "engaged in fishing";
|
||||
case 8:
|
||||
return "under way sailing";
|
||||
case 9:
|
||||
return "reserved for future amendment of Navigational Status for HSC";
|
||||
case 10:
|
||||
return "reserved for future amendment of Navigational Status for WIG";
|
||||
case 11:
|
||||
case 12:
|
||||
case 13:
|
||||
case 14:
|
||||
return "reserved for future use";
|
||||
default:
|
||||
return "not defined";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region overrides
|
||||
|
||||
protected override Status Decode()
|
||||
{
|
||||
Status result = Status.OK;
|
||||
BitArray bits = DecodeBinary(_data);
|
||||
|
||||
try
|
||||
{
|
||||
_mmsi = GetInt(bits, 8, 37);
|
||||
this.navstatus = GetInt(bits, 38, 41);
|
||||
this.rot = GetInt(bits, 42, 49);
|
||||
this.sog = GetInt(bits, 50, 59);
|
||||
this.accur = GetInt(bits, 60, 60);
|
||||
this.longitude = GetInt(bits, 61, 88);
|
||||
this.latitude = GetInt(bits, 89, 115);
|
||||
this.cog = GetInt(bits, 116, 127);
|
||||
this.trueheading = GetInt(bits, 128, 136);
|
||||
this.utcTimeSecond = GetInt(bits, 137, 142);
|
||||
this.reserved = GetInt(bits, 143, 146);
|
||||
this.spare = GetInt(bits, 147, 147);
|
||||
this.raim = GetInt(bits, 148, 148);
|
||||
this.commstate = GetInt(bits, 149, 167);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.WarnFormat("Error decoding AIS pos report: {0}", e.Message);
|
||||
result = Status.PARSE_ERROR;
|
||||
}
|
||||
|
||||
this.timestamp = DateTime.Now;
|
||||
return result;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} - MMSI {1}", base.ToString(), this.MMSI);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
368
AIS/bsmd.AIS2Service/AIS_StaticData.cs
Normal file
368
AIS/bsmd.AIS2Service/AIS_StaticData.cs
Normal file
@ -0,0 +1,368 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
using log4net;
|
||||
|
||||
namespace bsmd.AIS2Service
|
||||
{
|
||||
internal class AIS_StaticData : AISClass
|
||||
{
|
||||
#region private members
|
||||
|
||||
private int ais_version;
|
||||
private int imoNumber;
|
||||
private string callsign;
|
||||
private string name;
|
||||
private int shiptype;
|
||||
private int dimension;
|
||||
private int a;
|
||||
private int b;
|
||||
private int c;
|
||||
private int d;
|
||||
|
||||
private int typeofdevice;
|
||||
private int etamonth;
|
||||
private int etaday;
|
||||
private int etahour;
|
||||
private int etaminute;
|
||||
private DateTime? eta;
|
||||
|
||||
private int maxpresetstaticdraught;
|
||||
private string destination;
|
||||
private int dte;
|
||||
private int spare;
|
||||
|
||||
private static readonly ILog _log = LogManager.GetLogger(typeof(AIS_StaticData));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public int ShipTypeVal { get { return this.shiptype; } }
|
||||
|
||||
public string Callsign
|
||||
{
|
||||
get { return this.callsign; }
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return this.name; }
|
||||
}
|
||||
|
||||
public DateTime? ETA
|
||||
{
|
||||
get { return this.eta; }
|
||||
}
|
||||
|
||||
public string Destination
|
||||
{
|
||||
get { return this.destination; }
|
||||
}
|
||||
|
||||
public int IMONumber
|
||||
{
|
||||
get { return this.imoNumber; }
|
||||
}
|
||||
|
||||
public string DeviceName
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (typeofdevice)
|
||||
{
|
||||
case 1:
|
||||
return "GPS";
|
||||
case 2:
|
||||
return "GLONASS";
|
||||
case 3:
|
||||
return "Combined GPS/GLONASS";
|
||||
case 4:
|
||||
return "Loran-C";
|
||||
case 5:
|
||||
return "Chayka";
|
||||
case 6:
|
||||
return "Integrated Navigation System";
|
||||
case 7:
|
||||
return "surveyed";
|
||||
case 8:
|
||||
return "Galileo";
|
||||
default:
|
||||
return "undefined";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Draught
|
||||
{
|
||||
get { return this.maxpresetstaticdraught; }
|
||||
}
|
||||
|
||||
public int Breadth
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.c + this.d;
|
||||
}
|
||||
}
|
||||
|
||||
public int Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.a + this.b;
|
||||
}
|
||||
}
|
||||
|
||||
public string ShipType
|
||||
{
|
||||
get
|
||||
{
|
||||
return AIS_StaticData.GetShipType(this.shiptype);
|
||||
}
|
||||
}
|
||||
|
||||
public int DBShipType { get { return this.shiptype; } }
|
||||
|
||||
public int DBDimension { get { return this.dimension; } }
|
||||
|
||||
public int DBTypeOfDevice { get { return this.typeofdevice; } }
|
||||
|
||||
public int DTE { get { return this.dte; } }
|
||||
|
||||
public int Spare { get { return this.spare; } }
|
||||
|
||||
public string DBETA
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.eta.HasValue)
|
||||
{
|
||||
return this.eta.Value.ToString("yyyy-MM-ddTHH:mm:ss.000Z");
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region abstract method implementation
|
||||
|
||||
protected override Status Decode()
|
||||
{
|
||||
BitArray bits = DecodeBinary(_data);
|
||||
Status result = Status.OK;
|
||||
|
||||
if (bits.Count < 424)
|
||||
{
|
||||
_log.WarnFormat("AISStaticData truncated: {0}/424", bits.Count);
|
||||
result = Status.PARSE_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
int type = GetInt(bits, 0, 5);
|
||||
if (type != 5)
|
||||
{
|
||||
result = Status.ILLEGAL_ARGUMENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
_mmsi = GetInt(bits, 6, 37);
|
||||
this.ais_version = GetInt(bits, 38, 39);
|
||||
this.imoNumber = GetInt(bits, 40, 69);
|
||||
|
||||
StringBuilder sb_callsign = new StringBuilder(7);
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
int cval = GetInt(bits, 70 + (6 * i), 75 + (6 * i));
|
||||
char ch = GetAISChar(cval);
|
||||
if (ch == '@') ch = ' ';
|
||||
sb_callsign.Append(ch);
|
||||
}
|
||||
this.callsign = sb_callsign.ToString().Trim();
|
||||
|
||||
StringBuilder sb_name = new StringBuilder(20);
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
int cval = GetInt(bits, 112 + (6 * i), 117 + (6 * i));
|
||||
char ch = GetAISChar(cval);
|
||||
if (ch == '@') ch = ' ';
|
||||
sb_name.Append(ch);
|
||||
}
|
||||
this.name = sb_name.ToString().Trim();
|
||||
|
||||
this.shiptype = GetInt(bits, 232, 239);
|
||||
this.dimension = GetInt(bits, 240, 269);
|
||||
this.a = GetInt(bits, 240, 248);
|
||||
this.b = GetInt(bits, 249, 257);
|
||||
this.c = GetInt(bits, 258, 263);
|
||||
this.d = GetInt(bits, 264, 269);
|
||||
this.typeofdevice = GetInt(bits, 270, 273);
|
||||
this.etamonth = GetInt(bits, 274, 277);
|
||||
this.etaday = GetInt(bits, 278, 282);
|
||||
this.etahour = GetInt(bits, 283, 287);
|
||||
this.etaminute = GetInt(bits, 288, 293);
|
||||
try
|
||||
{
|
||||
if ((this.etahour < 24) && (this.etaday > 0) && (this.etaminute < 60) && (this.etamonth > 0))
|
||||
{
|
||||
this.eta = new DateTime(DateTime.Now.Year, this.etamonth, this.etaday, this.etahour, this.etaminute, 0);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.WarnFormat("ETA timestamp creation failed: {0}", e.Message);
|
||||
}
|
||||
this.maxpresetstaticdraught = GetInt(bits, 294, 301);
|
||||
|
||||
StringBuilder sb_destination = new StringBuilder(20);
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
int cval = GetInt(bits, 302 + (6 * i), 307 + (6 * i));
|
||||
char ch = GetAISChar(cval);
|
||||
if (ch == '@') break; //ch = ' '; // alles nach einem @ nicht mehr beachten
|
||||
sb_destination.Append(ch);
|
||||
}
|
||||
this.destination = sb_destination.ToString().Trim();
|
||||
|
||||
this.dte = GetInt(bits, 422, 422);
|
||||
this.spare = GetInt(bits, 423, 423);
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.WarnFormat("Error decoding AIS static data: {0}", e.Message);
|
||||
result = Status.PARSE_ERROR;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} - {1} [{2}]", base.ToString(), this.MMSI, this.Name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region public static methods
|
||||
|
||||
public static string GetShipType(int shiptype)
|
||||
{
|
||||
if (shiptype > 199) return "preserved for future use";
|
||||
if (shiptype > 99) return "preserved for regional use";
|
||||
int dig1, dig2;
|
||||
switch (shiptype)
|
||||
{
|
||||
case 50:
|
||||
return "Pilot vessel";
|
||||
case 51:
|
||||
return "SAR vessel";
|
||||
case 52:
|
||||
return "Tug";
|
||||
case 53:
|
||||
return "Port tender";
|
||||
case 54:
|
||||
return "Vessel with anti-pollution facility or equipment";
|
||||
case 55:
|
||||
return "Law enforcment vessel";
|
||||
case 56:
|
||||
return "Spare [local vessel]";
|
||||
case 57:
|
||||
return "Spare [local vessel]";
|
||||
case 58:
|
||||
return "Medical transport";
|
||||
case 59:
|
||||
return "Ship according to Resolution No. 18 (Mob-83)";
|
||||
default:
|
||||
{
|
||||
string comb = "";
|
||||
dig1 = shiptype / 10;
|
||||
dig2 = shiptype % 10;
|
||||
switch (dig1)
|
||||
{
|
||||
case 1:
|
||||
comb += "reserved for future use";
|
||||
break;
|
||||
case 2:
|
||||
comb += "WIG";
|
||||
break;
|
||||
case 3:
|
||||
comb += "Vessel";
|
||||
switch (dig2)
|
||||
{
|
||||
case 0:
|
||||
comb += " Fishing"; break;
|
||||
case 1:
|
||||
comb += " Towing"; break;
|
||||
case 2:
|
||||
comb += " Towing and length of tow exceeds 200m or breadth exceeds 25m"; break;
|
||||
case 3:
|
||||
comb += " Engaged in dredging or underwater operations"; break;
|
||||
case 4:
|
||||
comb += " Engaged in diving operations"; break;
|
||||
case 5:
|
||||
comb += " Engaged in military operations"; break;
|
||||
case 6:
|
||||
comb += " Sailing"; break;
|
||||
case 7:
|
||||
comb += " Pleasure craft"; break;
|
||||
default:
|
||||
comb += " reserved for future use";
|
||||
break;
|
||||
}
|
||||
return comb;
|
||||
case 4:
|
||||
comb += "HSC";
|
||||
break;
|
||||
case 6:
|
||||
comb += "Passenger ship";
|
||||
break;
|
||||
case 7:
|
||||
comb += "Cargo ship";
|
||||
break;
|
||||
case 8:
|
||||
comb += "Tanker";
|
||||
break;
|
||||
default:
|
||||
case 9:
|
||||
comb += "other";
|
||||
break;
|
||||
}
|
||||
switch (dig2)
|
||||
{
|
||||
case 0: break;
|
||||
case 1:
|
||||
comb += " carrying DG, HS or MP IMO hazard or pollutant category A"; break;
|
||||
case 2:
|
||||
comb += " carrying DG, HS or MP IMO hazard or pollutant category B"; break;
|
||||
case 3:
|
||||
comb += " carrying DG, HS or MP IMO hazard or pollutant category C"; break;
|
||||
case 4:
|
||||
comb += " carrying DG, HS or MP IMO hazard or pollutant category D"; break;
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
comb += " reserved for future use"; break;
|
||||
case 9:
|
||||
comb += " no additional information"; break;
|
||||
}
|
||||
return comb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@ -4,17 +4,41 @@
|
||||
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
|
||||
<section name="bsmd.AIS2Service.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
|
||||
</sectionGroup>
|
||||
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,Log4net"/>
|
||||
</configSections>
|
||||
<log4net>
|
||||
<root>
|
||||
<level value="ALL"/>
|
||||
<appender-ref ref="LogFileAppender"/>
|
||||
<appender-ref ref="TraceAppender"/>
|
||||
</root>
|
||||
<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
|
||||
<param name="File" value="E:\temp\AIS2Service.log"/>
|
||||
<param name="AppendToFile" value="true"/>
|
||||
<rollingStyle value="Size"/>
|
||||
<maxSizeRollBackups value="10"/>
|
||||
<maximumFileSize value="10MB"/>
|
||||
<staticLogFileName value="true"/>
|
||||
<layout type="log4net.Layout.PatternLayout">
|
||||
<param name="ConversionPattern" value="%date [%thread] %-5level [%logger] - %message%newline"/>
|
||||
</layout>
|
||||
</appender>
|
||||
<appender name="TraceAppender" type="log4net.Appender.TraceAppender">
|
||||
<layout type="log4net.Layout.PatternLayout">
|
||||
<conversionPattern value="%d [%t] %-5p %c %m%n"/>
|
||||
</layout>
|
||||
</appender>
|
||||
</log4net>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||
</startup>
|
||||
<applicationSettings>
|
||||
<bsmd.AIS2Service.Properties.Settings>
|
||||
<setting name="DataSourceHost" serializeAs="String">
|
||||
<value>192.168.2.24</value>
|
||||
<value>192.168.2.25</value>
|
||||
</setting>
|
||||
<setting name="DataSourcePort" serializeAs="String">
|
||||
<value>0</value>
|
||||
<value>32100</value>
|
||||
</setting>
|
||||
</bsmd.AIS2Service.Properties.Settings>
|
||||
</applicationSettings>
|
||||
|
||||
@ -22,5 +22,10 @@ namespace bsmd.AIS2Service
|
||||
/// if this happens the whole show must be stopped
|
||||
/// </summary>
|
||||
event EventHandler FatalErrorOccurred;
|
||||
|
||||
/// <summary>
|
||||
/// descriptive name of this thread to use in logging
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
}
|
||||
}
|
||||
|
||||
129
AIS/bsmd.AIS2Service/NMEA.cs
Normal file
129
AIS/bsmd.AIS2Service/NMEA.cs
Normal file
@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using log4net;
|
||||
|
||||
namespace bsmd.AIS2Service
|
||||
{
|
||||
internal abstract class NMEA
|
||||
{
|
||||
protected string type = "";
|
||||
protected string _data;
|
||||
protected string[] elements = null;
|
||||
|
||||
protected static ILog _log = LogManager.GetLogger(typeof(NMEA));
|
||||
|
||||
public enum Status
|
||||
{
|
||||
OK,
|
||||
UNKNOWN_TYPE,
|
||||
CHECKSUM,
|
||||
ILLEGAL_ARGUMENT
|
||||
}
|
||||
|
||||
protected abstract void Decode();
|
||||
|
||||
/// <summary>
|
||||
/// public method to decode input data strings into respective NMEA sentences
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="status"></param>
|
||||
/// <returns></returns>
|
||||
public static NMEA Decode(string data, ref Status status)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
status = Status.ILLEGAL_ARGUMENT;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data[0] != '$' && data[0] != '!')
|
||||
{
|
||||
status = Status.ILLEGAL_ARGUMENT;
|
||||
return null; // no NMEA sentence
|
||||
}
|
||||
|
||||
string[] elements = data.Trim().Substring(1).Split(',');
|
||||
|
||||
NMEA sentence = NMEA.CreateNMEAElement(elements[0]);
|
||||
if (sentence == null)
|
||||
{
|
||||
status = Status.UNKNOWN_TYPE;
|
||||
return null;
|
||||
}
|
||||
|
||||
sentence.elements = elements;
|
||||
sentence._data = data.Trim(); ;
|
||||
|
||||
if (!sentence.IsChecksumOK)
|
||||
{
|
||||
status = Status.CHECKSUM;
|
||||
return null;
|
||||
}
|
||||
|
||||
sentence.Decode();
|
||||
|
||||
return sentence;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.ErrorFormat("Error decoding sentence: {0}, {1}", ex.Message, ex.StackTrace);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory method for nmea types
|
||||
/// </summary>
|
||||
protected static NMEA CreateNMEAElement(string type)
|
||||
{
|
||||
NMEA result = null;
|
||||
|
||||
switch (type.ToUpper())
|
||||
{
|
||||
case "AIVDM":
|
||||
case "AIVDO":
|
||||
result = new NMEA_AIS_Sentence();
|
||||
break;
|
||||
|
||||
case "PNMLS":
|
||||
result = new NMEA_PNMLS_Sentence();
|
||||
break;
|
||||
|
||||
default:
|
||||
_log.WarnFormat("ignoring unsupported NMEA type {0}", type);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != null)
|
||||
result.type = type.ToUpper();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#region NMEA checksum
|
||||
|
||||
protected bool IsChecksumOK
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.Substring(_data.IndexOf('*') + 1) == this.CalculateChecksum();
|
||||
}
|
||||
}
|
||||
|
||||
private string CalculateChecksum()
|
||||
{
|
||||
int checksum = Convert.ToByte(_data[1]);
|
||||
for (int i = 2; i < _data.IndexOf('*'); i++)
|
||||
{
|
||||
checksum ^= Convert.ToByte(_data[i]);
|
||||
}
|
||||
return checksum.ToString("X2");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
93
AIS/bsmd.AIS2Service/NMEA_AIS_Sentence.cs
Normal file
93
AIS/bsmd.AIS2Service/NMEA_AIS_Sentence.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using System;
|
||||
|
||||
namespace bsmd.AIS2Service
|
||||
{
|
||||
internal class NMEA_AIS_Sentence : NMEA
|
||||
{
|
||||
|
||||
#region fields
|
||||
|
||||
private int total_sentence_nr;
|
||||
private int msg_sentence_nr;
|
||||
private string seq_message_ident;
|
||||
private string ais_channel_nr;
|
||||
private string ais_message;
|
||||
private int fillbits;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// 1-based total number of sentences for this ais message
|
||||
/// </summary>
|
||||
public int Total_Sentence_Nr
|
||||
{
|
||||
get { return this.total_sentence_nr; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 1-based fragment number of sentences
|
||||
/// </summary>
|
||||
public int Msg_Sentence_Nr
|
||||
{
|
||||
get { return this.msg_sentence_nr; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// sequential message id for multi-sentence messages (can be empty)
|
||||
/// </summary>
|
||||
public string Seq_Message_Ident
|
||||
{
|
||||
get { return this.seq_message_ident; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 'A' = 161.975Mhz (87B),
|
||||
/// 'B' = 162.025Mhz (88B)
|
||||
/// </summary>
|
||||
public string AIS_Channel_nr
|
||||
{
|
||||
get { return this.ais_channel_nr; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AIS message data
|
||||
/// </summary>
|
||||
public string AIS_Message
|
||||
{
|
||||
get { return this.ais_message; }
|
||||
}
|
||||
|
||||
public int FillBits
|
||||
{
|
||||
get { return this.fillbits; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region abstract method implementation
|
||||
|
||||
protected override void Decode()
|
||||
{
|
||||
this.total_sentence_nr = Convert.ToInt32(this.elements[1]);
|
||||
this.msg_sentence_nr = Convert.ToInt32(this.elements[2]);
|
||||
this.seq_message_ident = this.elements[3]; // can be an empty string
|
||||
this.ais_channel_nr = this.elements[4];
|
||||
this.ais_message = this.elements[5];
|
||||
try
|
||||
{
|
||||
string fillbits_string = this.elements[6].Substring(0, this.elements[6].IndexOf('*'));
|
||||
if(!Int32.TryParse(fillbits_string, out this.fillbits))
|
||||
_log.Warn("AIS_Sentence.Decode(): fillbits are no integer");
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
_log.Warn("AIS_Sentence.Decode(): split() problem, trouble decoding fillbits");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
60
AIS/bsmd.AIS2Service/NMEA_PNMLS_Sentence.cs
Normal file
60
AIS/bsmd.AIS2Service/NMEA_PNMLS_Sentence.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using System;
|
||||
|
||||
namespace bsmd.AIS2Service
|
||||
{
|
||||
/// <summary>
|
||||
/// NMEA PNMLS sentence
|
||||
/// sentence shows signal level for preceding message
|
||||
/// </summary>
|
||||
class NMEA_PNMLS_Sentence : NMEA
|
||||
{
|
||||
|
||||
#region fields
|
||||
|
||||
private int signal_level;
|
||||
private int detection_threshold;
|
||||
private int interval;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public int Signal_Level
|
||||
{
|
||||
get { return this.signal_level; }
|
||||
}
|
||||
|
||||
public int Detection_Threshold
|
||||
{
|
||||
get { return this.detection_threshold; }
|
||||
}
|
||||
|
||||
public int Interval
|
||||
{
|
||||
get { return this.interval; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region abstract method implementation
|
||||
|
||||
protected override void Decode()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.signal_level = Convert.ToInt32(this.elements[1]);
|
||||
this.detection_threshold = Convert.ToInt32(this.elements[2]);
|
||||
|
||||
string interval_string = this.elements[3].Substring(0, this.elements[3].IndexOf('*'));
|
||||
this.interval = Convert.ToInt32(interval_string);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
_log.Warn("NMEA [PNMLS] input format error");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using System.ServiceProcess;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace bsmd.AIS2Service
|
||||
{
|
||||
@ -14,12 +11,25 @@ namespace bsmd.AIS2Service
|
||||
/// </summary>
|
||||
static void Main()
|
||||
{
|
||||
ServiceBase[] ServicesToRun;
|
||||
ServicesToRun = new ServiceBase[]
|
||||
log4net.Config.XmlConfigurator.Configure();
|
||||
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
AISManager.Start();
|
||||
// TODO wait some
|
||||
Thread.Sleep(60000);
|
||||
// Test finish..
|
||||
AISManager.Stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
ServiceBase[] ServicesToRun;
|
||||
ServicesToRun = new ServiceBase[]
|
||||
{
|
||||
new AIS2_Service()
|
||||
};
|
||||
ServiceBase.Run(ServicesToRun);
|
||||
};
|
||||
ServiceBase.Run(ServicesToRun);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ namespace bsmd.AIS2Service.Properties {
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("192.168.2.24")]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("192.168.2.25")]
|
||||
public string DataSourceHost {
|
||||
get {
|
||||
return ((string)(this["DataSourceHost"]));
|
||||
@ -34,7 +34,7 @@ namespace bsmd.AIS2Service.Properties {
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("0")]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("32100")]
|
||||
public uint DataSourcePort {
|
||||
get {
|
||||
return ((uint)(this["DataSourcePort"]));
|
||||
|
||||
@ -3,10 +3,10 @@
|
||||
<Profiles />
|
||||
<Settings>
|
||||
<Setting Name="DataSourceHost" Type="System.String" Scope="Application">
|
||||
<Value Profile="(Default)">192.168.2.24</Value>
|
||||
<Value Profile="(Default)">192.168.2.25</Value>
|
||||
</Setting>
|
||||
<Setting Name="DataSourcePort" Type="System.UInt32" Scope="Application">
|
||||
<Value Profile="(Default)">0</Value>
|
||||
<Value Profile="(Default)">32100</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
@ -1,6 +1,8 @@
|
||||
using log4net;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
@ -13,48 +15,62 @@ namespace bsmd.AIS2Service
|
||||
{
|
||||
private readonly string _host;
|
||||
private readonly uint _port;
|
||||
private readonly ConcurrentQueue<string> _inputQueue;
|
||||
private bool _stopFlag = false;
|
||||
|
||||
private NetworkStream tcpStream;
|
||||
private TcpClient tcpSocket;
|
||||
private Thread _thread;
|
||||
|
||||
private static readonly ILog _log = LogManager.GetLogger(typeof(SerialTCPReader));
|
||||
|
||||
|
||||
public SerialTCPReader(string host, uint port)
|
||||
public SerialTCPReader(string host, uint port, ConcurrentQueue<string> inputQueue)
|
||||
{
|
||||
_host = host; _port = port;
|
||||
_host = host; _port = port; _inputQueue = inputQueue;
|
||||
}
|
||||
|
||||
private void ReadData()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
while (!_stopFlag)
|
||||
{
|
||||
if (this.tcpSocket == null || !this.tcpSocket.Connected) this.Connect();
|
||||
|
||||
|
||||
foreach(string line in ReadLines(this.tcpSocket.GetStream(), Encoding.ASCII))
|
||||
{
|
||||
_inputQueue.Enqueue(line);
|
||||
if (_stopFlag) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch(Exception ex)
|
||||
{
|
||||
_log.ErrorFormat("Something bad has happened: {0}", ex.Message);
|
||||
if(this.FatalErrorOccurred != null)
|
||||
this.FatalErrorOccurred(this, new EventArgs());
|
||||
this.FatalErrorOccurred?.Invoke(this, new EventArgs());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void Connect()
|
||||
{
|
||||
this.tcpSocket = new TcpClient(_host, (int)_port);
|
||||
this.tcpStream = tcpSocket.GetStream();
|
||||
this.tcpSocket = new TcpClient(_host, (int)_port);
|
||||
_log.InfoFormat("TCP stream connected ({0}:{1})", _host, _port);
|
||||
}
|
||||
|
||||
|
||||
private static IEnumerable<string> ReadLines(Stream source, Encoding encoding)
|
||||
{
|
||||
using(StreamReader reader = new StreamReader(source, encoding))
|
||||
{
|
||||
string line;
|
||||
|
||||
while((line = reader.ReadLine()) != null)
|
||||
{
|
||||
yield return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IAISThread implementation
|
||||
|
||||
@ -65,13 +81,20 @@ namespace bsmd.AIS2Service
|
||||
if (_thread != null) return; // may not run twice
|
||||
ThreadStart runReader = new ThreadStart(this.ReadData);
|
||||
_thread = new Thread(runReader);
|
||||
|
||||
|
||||
_thread.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
|
||||
if(_thread == null) return;
|
||||
_stopFlag = true;
|
||||
_thread.Join();
|
||||
_thread = null;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Serial stream reader"; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -55,7 +55,18 @@
|
||||
<Compile Include="AIS2_Service.Designer.cs">
|
||||
<DependentUpon>AIS2_Service.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="AISClass.cs" />
|
||||
<Compile Include="AISDecoder.cs" />
|
||||
<Compile Include="AISManager.cs" />
|
||||
<Compile Include="AIS_ClassB.cs" />
|
||||
<Compile Include="AIS_ClassBExt.cs" />
|
||||
<Compile Include="AIS_ClassBStatic.cs" />
|
||||
<Compile Include="AIS_PosReport.cs" />
|
||||
<Compile Include="AIS_StaticData.cs" />
|
||||
<Compile Include="IAISThread.cs" />
|
||||
<Compile Include="NMEA.cs" />
|
||||
<Compile Include="NMEA_AIS_Sentence.cs" />
|
||||
<Compile Include="NMEA_PNMLS_Sentence.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
@ -73,9 +84,7 @@
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Parser\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="AIS2_Service.resx">
|
||||
<DependentUpon>AIS2_Service.cs</DependentUpon>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user