diff --git a/AIS/bsmd.AIS2Service/AIS2_Service.cs b/AIS/bsmd.AIS2Service/AIS2_Service.cs
index 2b9c767d..6fcc3842 100644
--- a/AIS/bsmd.AIS2Service/AIS2_Service.cs
+++ b/AIS/bsmd.AIS2Service/AIS2_Service.cs
@@ -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.");
}
}
diff --git a/AIS/bsmd.AIS2Service/AISClass.cs b/AIS/bsmd.AIS2Service/AISClass.cs
new file mode 100644
index 00000000..76185a1a
--- /dev/null
+++ b/AIS/bsmd.AIS2Service/AISClass.cs
@@ -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;
+ }
+
+ ///
+ /// Factory method to create messages based on the message type
+ ///
+ 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
+
+ ///
+ /// mehr dazu hier:
+ /// http://www.codeguru.com/cpp/cpp/algorithms/general/article.php/c5115/
+ ///
+ 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
+ }
+}
diff --git a/AIS/bsmd.AIS2Service/AISDecoder.cs b/AIS/bsmd.AIS2Service/AISDecoder.cs
new file mode 100644
index 00000000..d7b84809
--- /dev/null
+++ b/AIS/bsmd.AIS2Service/AISDecoder.cs
@@ -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 _inputLines;
+ private readonly ConcurrentQueue _outputAISClasses;
+ private Thread _thread;
+ private readonly Dictionary> fragmentDict = new Dictionary>();
+ private const int sleepMS = 250;
+ private bool _stopFlag = false;
+
+ private static readonly ILog _log = LogManager.GetLogger(typeof(AISDecoder));
+ #endregion
+
+ #region construction
+
+ public AISDecoder(ConcurrentQueue input, ConcurrentQueue 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
+
+ ///
+ /// Thread entry
+ ///
+ 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();
+ 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
+
+ ///
+ /// check to see if all fragments are available
+ ///
+ private static bool FragmentsComplete(List 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;
+ }
+
+ ///
+ /// assembles message fragments. Care must be taken since fragments can appear
+ /// out of order
+ ///
+ private static string ConcatenateFragments(List 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
+
+ }
+}
diff --git a/AIS/bsmd.AIS2Service/AISManager.cs b/AIS/bsmd.AIS2Service/AISManager.cs
new file mode 100644
index 00000000..0748fbd8
--- /dev/null
+++ b/AIS/bsmd.AIS2Service/AISManager.cs
@@ -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 _tasks = new List();
+ private static readonly ConcurrentQueue _inputLines = new ConcurrentQueue();
+ private static readonly ConcurrentQueue _decodedClasses = new ConcurrentQueue();
+ 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);
+ }
+
+ }
+
+ }
+}
diff --git a/AIS/bsmd.AIS2Service/AIS_ClassB.cs b/AIS/bsmd.AIS2Service/AIS_ClassB.cs
new file mode 100644
index 00000000..6c3926d9
--- /dev/null
+++ b/AIS/bsmd.AIS2Service/AIS_ClassB.cs
@@ -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
+
+ }
+}
diff --git a/AIS/bsmd.AIS2Service/AIS_ClassBExt.cs b/AIS/bsmd.AIS2Service/AIS_ClassBExt.cs
new file mode 100644
index 00000000..a4d4b453
--- /dev/null
+++ b/AIS/bsmd.AIS2Service/AIS_ClassBExt.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections;
+using System.Text;
+using System.Diagnostics;
+using log4net;
+
+namespace bsmd.AIS2Service
+{
+ ///
+ /// 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 ;)
+ ///
+ // 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;
+ }
+ }
+}
diff --git a/AIS/bsmd.AIS2Service/AIS_ClassBStatic.cs b/AIS/bsmd.AIS2Service/AIS_ClassBStatic.cs
new file mode 100644
index 00000000..01be35f8
--- /dev/null
+++ b/AIS/bsmd.AIS2Service/AIS_ClassBStatic.cs
@@ -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
+
+ }
+}
diff --git a/AIS/bsmd.AIS2Service/AIS_PosReport.cs b/AIS/bsmd.AIS2Service/AIS_PosReport.cs
new file mode 100644
index 00000000..475d4f6e
--- /dev/null
+++ b/AIS/bsmd.AIS2Service/AIS_PosReport.cs
@@ -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
+ }
+}
diff --git a/AIS/bsmd.AIS2Service/AIS_StaticData.cs b/AIS/bsmd.AIS2Service/AIS_StaticData.cs
new file mode 100644
index 00000000..764d4317
--- /dev/null
+++ b/AIS/bsmd.AIS2Service/AIS_StaticData.cs
@@ -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
+
+ }
+}
diff --git a/AIS/bsmd.AIS2Service/App.config b/AIS/bsmd.AIS2Service/App.config
index cf891f51..5aa8e79d 100644
--- a/AIS/bsmd.AIS2Service/App.config
+++ b/AIS/bsmd.AIS2Service/App.config
@@ -4,17 +4,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- 192.168.2.24
+ 192.168.2.25
- 0
+ 32100
diff --git a/AIS/bsmd.AIS2Service/IAISThread.cs b/AIS/bsmd.AIS2Service/IAISThread.cs
index 1778cae1..2335c127 100644
--- a/AIS/bsmd.AIS2Service/IAISThread.cs
+++ b/AIS/bsmd.AIS2Service/IAISThread.cs
@@ -22,5 +22,10 @@ namespace bsmd.AIS2Service
/// if this happens the whole show must be stopped
///
event EventHandler FatalErrorOccurred;
+
+ ///
+ /// descriptive name of this thread to use in logging
+ ///
+ string Name { get; }
}
}
diff --git a/AIS/bsmd.AIS2Service/NMEA.cs b/AIS/bsmd.AIS2Service/NMEA.cs
new file mode 100644
index 00000000..9d9f3bde
--- /dev/null
+++ b/AIS/bsmd.AIS2Service/NMEA.cs
@@ -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();
+
+ ///
+ /// public method to decode input data strings into respective NMEA sentences
+ ///
+ ///
+ ///
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Factory method for nmea types
+ ///
+ 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
+
+ }
+}
diff --git a/AIS/bsmd.AIS2Service/NMEA_AIS_Sentence.cs b/AIS/bsmd.AIS2Service/NMEA_AIS_Sentence.cs
new file mode 100644
index 00000000..5e95993f
--- /dev/null
+++ b/AIS/bsmd.AIS2Service/NMEA_AIS_Sentence.cs
@@ -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
+
+ ///
+ /// 1-based total number of sentences for this ais message
+ ///
+ public int Total_Sentence_Nr
+ {
+ get { return this.total_sentence_nr; }
+ }
+
+ ///
+ /// 1-based fragment number of sentences
+ ///
+ public int Msg_Sentence_Nr
+ {
+ get { return this.msg_sentence_nr; }
+ }
+
+ ///
+ /// sequential message id for multi-sentence messages (can be empty)
+ ///
+ public string Seq_Message_Ident
+ {
+ get { return this.seq_message_ident; }
+ }
+
+ ///
+ /// 'A' = 161.975Mhz (87B),
+ /// 'B' = 162.025Mhz (88B)
+ ///
+ public string AIS_Channel_nr
+ {
+ get { return this.ais_channel_nr; }
+ }
+
+ ///
+ /// AIS message data
+ ///
+ 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
+
+ }
+}
diff --git a/AIS/bsmd.AIS2Service/NMEA_PNMLS_Sentence.cs b/AIS/bsmd.AIS2Service/NMEA_PNMLS_Sentence.cs
new file mode 100644
index 00000000..c852bac0
--- /dev/null
+++ b/AIS/bsmd.AIS2Service/NMEA_PNMLS_Sentence.cs
@@ -0,0 +1,60 @@
+using System;
+
+namespace bsmd.AIS2Service
+{
+ ///
+ /// NMEA PNMLS sentence
+ /// sentence shows signal level for preceding message
+ ///
+ 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
+
+ }
+}
diff --git a/AIS/bsmd.AIS2Service/Program.cs b/AIS/bsmd.AIS2Service/Program.cs
index 97c417fc..a518a447 100644
--- a/AIS/bsmd.AIS2Service/Program.cs
+++ b/AIS/bsmd.AIS2Service/Program.cs
@@ -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
///
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);
+ }
}
}
}
diff --git a/AIS/bsmd.AIS2Service/Properties/Settings.Designer.cs b/AIS/bsmd.AIS2Service/Properties/Settings.Designer.cs
index 0ccdbc3e..9e308f68 100644
--- a/AIS/bsmd.AIS2Service/Properties/Settings.Designer.cs
+++ b/AIS/bsmd.AIS2Service/Properties/Settings.Designer.cs
@@ -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"]));
diff --git a/AIS/bsmd.AIS2Service/Properties/Settings.settings b/AIS/bsmd.AIS2Service/Properties/Settings.settings
index e76be480..7da83a50 100644
--- a/AIS/bsmd.AIS2Service/Properties/Settings.settings
+++ b/AIS/bsmd.AIS2Service/Properties/Settings.settings
@@ -3,10 +3,10 @@
- 192.168.2.24
+ 192.168.2.25
- 0
+ 32100
\ No newline at end of file
diff --git a/AIS/bsmd.AIS2Service/SerialTCPReader.cs b/AIS/bsmd.AIS2Service/SerialTCPReader.cs
index 04df3ea2..3a6e05da 100644
--- a/AIS/bsmd.AIS2Service/SerialTCPReader.cs
+++ b/AIS/bsmd.AIS2Service/SerialTCPReader.cs
@@ -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 _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 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 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
diff --git a/AIS/bsmd.AIS2Service/bsmd.AIS2Service.csproj b/AIS/bsmd.AIS2Service/bsmd.AIS2Service.csproj
index 66f045b6..462f2444 100644
--- a/AIS/bsmd.AIS2Service/bsmd.AIS2Service.csproj
+++ b/AIS/bsmd.AIS2Service/bsmd.AIS2Service.csproj
@@ -55,7 +55,18 @@
AIS2_Service.cs
+
+
+
+
+
+
+
+
+
+
+
@@ -73,9 +84,7 @@
Settings.Designer.cs
-
-
-
+
AIS2_Service.cs