205 lines
7.1 KiB
C#
205 lines
7.1 KiB
C#
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 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(Properties.Settings.Default.ThreadSleepMS);
|
|
}
|
|
}
|
|
}
|
|
|
|
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.DebugFormat("Enqueuing {0} message for MMSI {1}", decodedClass.MessageType, decodedClass.MMSI);
|
|
}
|
|
else
|
|
{
|
|
if(aisStatus != AISClass.Status.UNSUPPORTED)
|
|
_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
|
|
|
|
}
|
|
}
|