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);
|
FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
|
||||||
string version = fvi.FileVersion;
|
string version = fvi.FileVersion;
|
||||||
_log.InfoFormat("Starting AIS2 Service. v.{0} -------------- ", version);
|
_log.InfoFormat("Starting AIS2 Service. v.{0} -------------- ", version);
|
||||||
|
AISManager.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnStop()
|
protected override void OnStop()
|
||||||
{
|
{
|
||||||
|
AISManager.Stop();
|
||||||
_log.Info("AIS2 Service stopped.");
|
_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" >
|
<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" />
|
<section name="bsmd.AIS2Service.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
|
||||||
</sectionGroup>
|
</sectionGroup>
|
||||||
|
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,Log4net"/>
|
||||||
</configSections>
|
</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>
|
<startup>
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||||
</startup>
|
</startup>
|
||||||
<applicationSettings>
|
<applicationSettings>
|
||||||
<bsmd.AIS2Service.Properties.Settings>
|
<bsmd.AIS2Service.Properties.Settings>
|
||||||
<setting name="DataSourceHost" serializeAs="String">
|
<setting name="DataSourceHost" serializeAs="String">
|
||||||
<value>192.168.2.24</value>
|
<value>192.168.2.25</value>
|
||||||
</setting>
|
</setting>
|
||||||
<setting name="DataSourcePort" serializeAs="String">
|
<setting name="DataSourcePort" serializeAs="String">
|
||||||
<value>0</value>
|
<value>32100</value>
|
||||||
</setting>
|
</setting>
|
||||||
</bsmd.AIS2Service.Properties.Settings>
|
</bsmd.AIS2Service.Properties.Settings>
|
||||||
</applicationSettings>
|
</applicationSettings>
|
||||||
|
|||||||
@ -22,5 +22,10 @@ namespace bsmd.AIS2Service
|
|||||||
/// if this happens the whole show must be stopped
|
/// if this happens the whole show must be stopped
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler FatalErrorOccurred;
|
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.Diagnostics;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.ServiceProcess;
|
using System.ServiceProcess;
|
||||||
using System.Text;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace bsmd.AIS2Service
|
namespace bsmd.AIS2Service
|
||||||
{
|
{
|
||||||
@ -13,6 +10,18 @@ namespace bsmd.AIS2Service
|
|||||||
/// The main entry point for the application.
|
/// The main entry point for the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static void Main()
|
static void Main()
|
||||||
|
{
|
||||||
|
log4net.Config.XmlConfigurator.Configure();
|
||||||
|
|
||||||
|
if (Debugger.IsAttached)
|
||||||
|
{
|
||||||
|
AISManager.Start();
|
||||||
|
// TODO wait some
|
||||||
|
Thread.Sleep(60000);
|
||||||
|
// Test finish..
|
||||||
|
AISManager.Stop();
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
ServiceBase[] ServicesToRun;
|
ServiceBase[] ServicesToRun;
|
||||||
ServicesToRun = new ServiceBase[]
|
ServicesToRun = new ServiceBase[]
|
||||||
@ -23,3 +32,4 @@ namespace bsmd.AIS2Service
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ namespace bsmd.AIS2Service.Properties {
|
|||||||
|
|
||||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Configuration.DefaultSettingValueAttribute("192.168.2.24")]
|
[global::System.Configuration.DefaultSettingValueAttribute("192.168.2.25")]
|
||||||
public string DataSourceHost {
|
public string DataSourceHost {
|
||||||
get {
|
get {
|
||||||
return ((string)(this["DataSourceHost"]));
|
return ((string)(this["DataSourceHost"]));
|
||||||
@ -34,7 +34,7 @@ namespace bsmd.AIS2Service.Properties {
|
|||||||
|
|
||||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Configuration.DefaultSettingValueAttribute("0")]
|
[global::System.Configuration.DefaultSettingValueAttribute("32100")]
|
||||||
public uint DataSourcePort {
|
public uint DataSourcePort {
|
||||||
get {
|
get {
|
||||||
return ((uint)(this["DataSourcePort"]));
|
return ((uint)(this["DataSourcePort"]));
|
||||||
|
|||||||
@ -3,10 +3,10 @@
|
|||||||
<Profiles />
|
<Profiles />
|
||||||
<Settings>
|
<Settings>
|
||||||
<Setting Name="DataSourceHost" Type="System.String" Scope="Application">
|
<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>
|
||||||
<Setting Name="DataSourcePort" Type="System.UInt32" Scope="Application">
|
<Setting Name="DataSourcePort" Type="System.UInt32" Scope="Application">
|
||||||
<Value Profile="(Default)">0</Value>
|
<Value Profile="(Default)">32100</Value>
|
||||||
</Setting>
|
</Setting>
|
||||||
</Settings>
|
</Settings>
|
||||||
</SettingsFile>
|
</SettingsFile>
|
||||||
@ -1,6 +1,8 @@
|
|||||||
using log4net;
|
using log4net;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -13,36 +15,40 @@ namespace bsmd.AIS2Service
|
|||||||
{
|
{
|
||||||
private readonly string _host;
|
private readonly string _host;
|
||||||
private readonly uint _port;
|
private readonly uint _port;
|
||||||
|
private readonly ConcurrentQueue<string> _inputQueue;
|
||||||
|
private bool _stopFlag = false;
|
||||||
|
|
||||||
private NetworkStream tcpStream;
|
|
||||||
private TcpClient tcpSocket;
|
private TcpClient tcpSocket;
|
||||||
private Thread _thread;
|
private Thread _thread;
|
||||||
|
|
||||||
private static readonly ILog _log = LogManager.GetLogger(typeof(SerialTCPReader));
|
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()
|
private void ReadData()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (true)
|
while (!_stopFlag)
|
||||||
{
|
{
|
||||||
if (this.tcpSocket == null || !this.tcpSocket.Connected) this.Connect();
|
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)
|
catch(Exception ex)
|
||||||
{
|
{
|
||||||
_log.ErrorFormat("Something bad has happened: {0}", ex.Message);
|
_log.ErrorFormat("Something bad has happened: {0}", ex.Message);
|
||||||
if(this.FatalErrorOccurred != null)
|
this.FatalErrorOccurred?.Invoke(this, new EventArgs());
|
||||||
this.FatalErrorOccurred(this, new EventArgs());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -50,11 +56,21 @@ namespace bsmd.AIS2Service
|
|||||||
private void Connect()
|
private void Connect()
|
||||||
{
|
{
|
||||||
this.tcpSocket = new TcpClient(_host, (int)_port);
|
this.tcpSocket = new TcpClient(_host, (int)_port);
|
||||||
this.tcpStream = tcpSocket.GetStream();
|
|
||||||
_log.InfoFormat("TCP stream connected ({0}:{1})", _host, _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
|
#region IAISThread implementation
|
||||||
|
|
||||||
@ -65,13 +81,20 @@ namespace bsmd.AIS2Service
|
|||||||
if (_thread != null) return; // may not run twice
|
if (_thread != null) return; // may not run twice
|
||||||
ThreadStart runReader = new ThreadStart(this.ReadData);
|
ThreadStart runReader = new ThreadStart(this.ReadData);
|
||||||
_thread = new Thread(runReader);
|
_thread = new Thread(runReader);
|
||||||
|
_thread.Start();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
|
if(_thread == null) return;
|
||||||
|
_stopFlag = true;
|
||||||
|
_thread.Join();
|
||||||
|
_thread = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return "Serial stream reader"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@ -55,7 +55,18 @@
|
|||||||
<Compile Include="AIS2_Service.Designer.cs">
|
<Compile Include="AIS2_Service.Designer.cs">
|
||||||
<DependentUpon>AIS2_Service.cs</DependentUpon>
|
<DependentUpon>AIS2_Service.cs</DependentUpon>
|
||||||
</Compile>
|
</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="IAISThread.cs" />
|
||||||
|
<Compile Include="NMEA.cs" />
|
||||||
|
<Compile Include="NMEA_AIS_Sentence.cs" />
|
||||||
|
<Compile Include="NMEA_PNMLS_Sentence.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Properties\Settings.Designer.cs">
|
<Compile Include="Properties\Settings.Designer.cs">
|
||||||
@ -73,9 +84,7 @@
|
|||||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup />
|
||||||
<Folder Include="Parser\" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="AIS2_Service.resx">
|
<EmbeddedResource Include="AIS2_Service.resx">
|
||||||
<DependentUpon>AIS2_Service.cs</DependentUpon>
|
<DependentUpon>AIS2_Service.cs</DependentUpon>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user