diff --git a/AIS/bsmd.AISService/AIS/AIS.cs b/AIS/bsmd.AISService/AIS/AIS.cs new file mode 100644 index 00000000..47d85a2c --- /dev/null +++ b/AIS/bsmd.AISService/AIS/AIS.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +// ship details suchbar hier: http://www.itu.int/cgi-bin/htsh/mars/ship_search.sh + +namespace bsmd.AISService.AIS +{ + public abstract class AIS + { + protected const double ERAD = 6378.135; + protected const double DE2RA = 0.01745329252; + protected const double AVG_ERAD = 6371.0; + + + #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 private members + + private AISType type = AISType.AIS_NONE; + protected int userId; + protected string data; + private string station; + + #endregion + + #region Properties + + public AISType MessageType + { + get { return this.type; } + } + + public int MMSI + { + get { return this.userId; } + } + + public string Station + { + get { return this.station; } + set { this.station = value; } + } + + #endregion + + #region abstract method signatures + + protected abstract Status Decode(); + + #endregion + + #region static methods + + internal static AIS Decode(string data, ref Status status) + { + AIS result = null; + + if (data == null || data.Length == 0) + { + status = Status.ILLEGAL_ARGUMENT; + return null; + } + + BitArray bits = AIS.DecodeChar(data[0]); + int type = AIS.GetInt(bits, 0, 5); + + result = AIS.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 AIS CreateMessage(int type) + { + AIS 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 *= AIS.DE2RA; + lon1 *= AIS.DE2RA; + lat2 *= AIS.DE2RA; + lon2 *= AIS.DE2RA; + double d = Math.Sin(lat1) * Math.Sin(lat2) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Cos(lon1 -lon2); + return (AIS.AVG_ERAD * Math.Acos(d)); + } + + + #endregion + + #endregion + + #region overrides + + public override string ToString() + { + return Enum.GetName(typeof(AIS.AISType), this.MessageType); + } + + #endregion + + } +} diff --git a/AIS/bsmd.AISService/AIS/AIS_ClassB.cs b/AIS/bsmd.AISService/AIS/AIS_ClassB.cs new file mode 100644 index 00000000..afe47a95 --- /dev/null +++ b/AIS/bsmd.AISService/AIS/AIS_ClassB.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections; +using System.Text; +using System.Diagnostics; + +namespace bsmd.AISService.AIS +{ + public class AIS_ClassB : AIS + { + #region private members + + 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; + + #endregion + + #region Properties + + 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 string.Format("{0}-{1}-{2} {3}:{4}:{5}", + this.timestamp.Year, this.timestamp.Month, this.timestamp.Day, + this.timestamp.Hour, this.timestamp.Minute, this.timestamp.Second); + } + } + + + + 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; + } + } + + #endregion + + protected override AIS.Status Decode() + { + BitArray bits = AIS.DecodeBinary(this.data); + Status result = Status.OK; + + try + { + int type = AIS.GetInt(bits, 0, 5); + if (type != 18) + { + result = Status.ILLEGAL_ARGUMENT; + } + else + { + this.repeatIndicator = AIS.GetInt(bits, 6, 7); + this.userId = AIS.GetInt(bits, 8, 37); + this.reserved = AIS.GetInt(bits, 38, 45); + this.sog = AIS.GetInt(bits, 46, 55); + this.accuracy = AIS.GetInt(bits, 56, 56); + this.longitude = AIS.GetInt(bits, 57, 84); + this.latitude = AIS.GetInt(bits, 85, 111); + this.cog = AIS.GetInt(bits, 112, 123); + this.trueHeading = AIS.GetInt(bits, 124, 132); + this.utcTimestampSecs = AIS.GetInt(bits, 133,138); + this.timestamp = DateTime.Now; + this.reservedRegional = AIS.GetInt(bits, 139, 140); + this.spare = AIS.GetInt(bits, 141, 145); + this.assignedModeFlag = AIS.GetInt(bits, 146, 146); + this.raimFlag = AIS.GetInt(bits, 147, 147); + this.commStateSelectedFlag = AIS.GetInt(bits, 148, 148); + this.commState = AIS.GetInt(bits, 149, 167); + } + } + catch (Exception e) + { + Trace.WriteLine(string.Format("Error decoding AIS class B posreport: {0}", e.Message)); + result = Status.PARSE_ERROR; + } + + return result; + } + } +} diff --git a/AIS/bsmd.AISService/AIS/AIS_ClassBExt.cs b/AIS/bsmd.AISService/AIS/AIS_ClassBExt.cs new file mode 100644 index 00000000..a3956611 --- /dev/null +++ b/AIS/bsmd.AISService/AIS/AIS_ClassBExt.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections; +using System.Text; +using System.Diagnostics; + +namespace bsmd.AISService.AIS +{ + /// + /// 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 + public class AIS_ClassBExt : AIS + { + #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; + + #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 AIS.Status Decode() + { + BitArray bits = AIS.DecodeBinary(this.data); + Status result = Status.OK; + + try + { + int type = AIS.GetInt(bits, 0, 5); + if (type != 19) + { + result = Status.ILLEGAL_ARGUMENT; + } + else + { + this.repeatIndicator = AIS.GetInt(bits, 6, 7); + this.userId = AIS.GetInt(bits, 8, 37); + this.spare1 = AIS.GetInt(bits, 38, 45); + this.sog = AIS.GetInt(bits, 46, 55); + this.accuracy = AIS.GetInt(bits, 56, 56); + this.longitude = AIS.GetInt(bits, 57, 84); + this.latitude = AIS.GetInt(bits, 85, 111); + this.cog = AIS.GetInt(bits, 112, 123); + this.trueHeading = AIS.GetInt(bits, 124, 132); + this.utcTimestampSecond = AIS.GetInt(bits, 133, 138); + this.timestamp = DateTime.Now; + this.spare2 = AIS.GetInt(bits, 139, 142); + + StringBuilder sb_name = new StringBuilder(20); + for (int i = 0; i < 20; i++) + { + int cval = AIS.GetInt(bits, 143 + (6 * i), 148 + (6 * i)); + char ch = AIS.GetAISChar(cval); + if (ch == '@') ch = ' '; + sb_name.Append(ch); + } + this.name = sb_name.ToString().Trim(); + + this.shipType = AIS.GetInt(bits, 263, 270); + this.dimension = AIS.GetInt(bits, 271, 300); + this.typeofDevice = AIS.GetInt(bits, 301, 304); + this.raimFlag = AIS.GetInt(bits, 305, 305); + this.dte = AIS.GetInt(bits, 306, 306); + this.assignedMode = AIS.GetInt(bits, 307, 307); + this.spare3 = AIS.GetInt(bits, 308, 311); + } + } + + catch (Exception e) + { + Trace.WriteLine(string.Format("Error decoding AIS class B Ext posreport: {0}", e.Message)); + result = Status.PARSE_ERROR; + } + + return result; + } + } +} diff --git a/AIS/bsmd.AISService/AIS/AIS_ClassBStatic.cs b/AIS/bsmd.AISService/AIS/AIS_ClassBStatic.cs new file mode 100644 index 00000000..89c51757 --- /dev/null +++ b/AIS/bsmd.AISService/AIS/AIS_ClassBStatic.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections; +using System.Text; +using System.Diagnostics; + +namespace bsmd.AISService.AIS +{ + public class AIS_ClassBStatic : AIS + { + #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; + + #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; } + } + + // Todo: Dimensions.. + + #endregion + + protected override AIS.Status Decode() + { + BitArray bits = AIS.DecodeBinary(this.data); + Status result = Status.OK; + + try + { + int type = AIS.GetInt(bits, 0, 5); + if (type != 24) + { + result = Status.ILLEGAL_ARGUMENT; + } + else + { + this.repeatIndicator = AIS.GetInt(bits, 6, 7); + this.userId = AIS.GetInt(bits, 8, 37); + this.partNumber = AIS.GetInt(bits, 38, 39); + if (this.IsPartA) + { + StringBuilder sb_name = new StringBuilder(20); + for (int i = 0; i < 20; i++) + { + int cval = AIS.GetInt(bits, 40 + (6 * i), 45 + (6 * i)); + char ch = AIS.GetAISChar(cval); + if (ch == '@') ch = ' '; + sb_name.Append(ch); + } + this.name = sb_name.ToString().Trim(); + } + else + { + this.shipType = AIS.GetInt(bits, 40, 47); + + StringBuilder sb_vendor = new StringBuilder(7); + for (int i = 0; i < 7; i++) + { + int cval = AIS.GetInt(bits, 48 + (6 * i), 53 + (6 * i)); + char ch = AIS.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 = AIS.GetInt(bits, 90 + (6 * i), 95 + (6 * i)); + char ch = AIS.GetAISChar(cval); + if (ch == '@') ch = ' '; + sb_callsign.Append(ch); + } + this.callsign = sb_callsign.ToString().Trim(); + this.dimension = AIS.GetInt(bits, 141, 161); + this.spare = AIS.GetInt(bits, 162, 167); + } + } + } + catch (Exception e) + { + Trace.WriteLine(string.Format("Error decoding AIS class B static data: {0}", e.Message)); + result = Status.PARSE_ERROR; + } + + return result; + } + } +} diff --git a/AIS/bsmd.AISService/AIS/AIS_Configuration.cs b/AIS/bsmd.AISService/AIS/AIS_Configuration.cs new file mode 100644 index 00000000..53fb5a4f --- /dev/null +++ b/AIS/bsmd.AISService/AIS/AIS_Configuration.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Reflection; +using System.Xml; +using System.Xml.Serialization; + +namespace bsmd.AISService.AIS +{ + [Serializable] + public class AIS_Configuration + { + private string filename; + private string dbConnectionString; + private int dbUpdateInterval = 500; // milliseconds + private int dbMinPosReportTimeDifference = 120; // seconds + private int stationIsOfflineTimeDifferenceSecs = 180; // seconds + private int targetStaleMins = 31; // minutes + + public List SerialPorts = new List(); + public List TelnetConnections = new List(); + + #region Properties + + public string Configuration_Path + { + get { return this.filename; } + set { this.filename = value; } + } + + public string DBConnectionString + { + get { return this.dbConnectionString; } + set { this.dbConnectionString = value; } + } + + /// + /// timer interval for database updates + /// + public int DBUpdateInterval + { + get { return this.dbUpdateInterval; } + set { this.dbUpdateInterval = value; } + } + + /// + /// minimum amount of minutes between two position reports to be + /// written to database + /// + public int DBMinPosReportTimeDifference + { + get { return this.dbMinPosReportTimeDifference; } + set { this.dbMinPosReportTimeDifference = value; } + } + + /// + /// number of seconds after which a station is marked offline since + /// sending the last pos report + /// + public int StationIsOfflineTimeDifferenceSecs + { + get { return this.stationIsOfflineTimeDifferenceSecs; } + set { this.stationIsOfflineTimeDifferenceSecs = value; } + } + + /// + /// if last update is older than this value then the target ist removed from + /// the current target queue (target went offline or out of range) + /// + public int TargetStaleMins + { + get { return this.targetStaleMins; } + set { this.targetStaleMins = value; } + } + + /// + /// Root path to where Viewer stores OSM tiles + /// + public string TilePath { get; set; } + + /// + /// full path to logfile + /// + public string LogfilePath { get; set; } + + /// + /// outputs assembly version + /// + public static string VersionInfo + { + get + { + Version version = Assembly.GetExecutingAssembly().GetName().Version; + return version.ToString(); + } + } + + #endregion + + #region Load/Save + + public static AIS_Configuration Load(string filename) + { + if (!File.Exists(filename)) return null; + + // Create an instance of the XmlSerializer specifying type and namespace. + XmlSerializer serializer = new XmlSerializer(typeof(AIS_Configuration)); + + // A FileStream is needed to read the XML document. + FileStream fs = new FileStream(filename, FileMode.Open); + XmlReader reader = new XmlTextReader(fs); + + AIS_Configuration configuration = serializer.Deserialize(reader) as AIS_Configuration; + reader.Close(); + configuration.filename = filename; + + + return configuration; + } + + public bool Save() + { + bool retval = true; + try + { + XmlSerializer serializer = new XmlSerializer(typeof(AIS_Configuration)); + Stream fs = new FileStream(this.filename, FileMode.Create); + XmlWriter writer = new XmlTextWriter(fs, new UTF8Encoding()); + serializer.Serialize(writer, this); + writer.Close(); + } + catch (Exception e) + { + System.Diagnostics.Debug.Write("Error during Serialize: " + e.ToString()); + retval = false; + } + return retval; + } + + #endregion + + #region internal classes + + public class SerialPort + { + public string station; + public string ComPort; + public int BaudRate = 9600; + public bool enabled = false; + } + + public class TelnetConnection + { + public string ipAddress; + public int port; + } + + #endregion + + } +} diff --git a/AIS/bsmd.AISService/AIS/AIS_Decoder.cs b/AIS/bsmd.AISService/AIS/AIS_Decoder.cs new file mode 100644 index 00000000..46f8476e --- /dev/null +++ b/AIS/bsmd.AISService/AIS/AIS_Decoder.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Text; + +namespace bsmd.AISService.AIS +{ + + /// + /// Diese Klasse setzt fragmentierte AIS Telegramme wieder zusammen und decodiert sie + /// + public class AIS_Decoder + { + + public delegate void AISMessageHandler(AIS message); + public event AISMessageHandler AISMessageReceived; + + #region class AISQueueElement + + public class AISQueueElement + { + public int seq_nr; + public int total_nr; + public int? id; + public string data; + public string station; + } + + #endregion + + #region private members + + private Queue inputDataQueue = new Queue(); + private Thread decodingThread; + private bool runDecoder = true; + private int sleepMS = 250; + private Dictionary> fragmentDict = new Dictionary>(); + + #endregion + + #region Properties + + public int QueueSize + { + get { return this.inputDataQueue.Count; } + } + + #endregion + + #region public methods + + public void Decode(string data, int seq_nr, int total_nr, int? id, string station) + { + lock (this.inputDataQueue) + { + AISQueueElement element = new AISQueueElement(); + element.data = data; + element.seq_nr = seq_nr; + element.total_nr = total_nr; + element.id = id; + element.station = station; + + this.inputDataQueue.Enqueue(element); + } + } + + public void Start() + { + this.decodingThread = new Thread(new ThreadStart(this.Run)); + this.decodingThread.Start(); + } + + public void Stop() + { + this.runDecoder = false; + if((this.decodingThread != null) && + (this.decodingThread.ThreadState == ThreadState.Running)) + this.decodingThread.Join(); + this.inputDataQueue.Clear(); // discard unread elements + } + + #endregion + + /// + /// Thread worker method + /// + protected void Run() + { + while (this.runDecoder) + { + AISQueueElement inputData = null; + + lock (this.inputDataQueue) + { + if (this.inputDataQueue.Count > 0) + { + inputData = this.inputDataQueue.Dequeue(); + } + } + + if (inputData == null) + Thread.Sleep(this.sleepMS); + else + { + string aisRawData = null; + if (inputData.total_nr == 1) + { + aisRawData = inputData.data; + } + else + { + int id = inputData.id ?? -1; + + if (!this.fragmentDict.ContainsKey(id)) + this.fragmentDict.Add(id, new List()); + this.fragmentDict[id].Add(inputData); + + // sind alle Fragmente vorhanden? + if (AIS_Decoder.FragmentsComplete(this.fragmentDict[id])) + { + // Fragmente zusammensetzen + aisRawData = AIS_Decoder.ConcatenateFragments(this.fragmentDict[id]); + this.fragmentDict.Remove(id); + } + + } + + if (aisRawData != null) + { + AIS.Status status = AIS.Status.OK; + AIS message = AIS.Decode(aisRawData, ref status); + if (status == AIS.Status.OK) + { + message.Station = inputData.station; + this.OnAISMessageReceived(message); + } + } + + } + } + } + + #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 + + protected void OnAISMessageReceived(AIS message) + { + if (this.AISMessageReceived != null) + this.AISMessageReceived(message); + } + + } +} diff --git a/AIS/bsmd.AISService/AIS/AIS_PosReport.cs b/AIS/bsmd.AISService/AIS/AIS_PosReport.cs new file mode 100644 index 00000000..949e5203 --- /dev/null +++ b/AIS/bsmd.AISService/AIS/AIS_PosReport.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.Text; + +namespace bsmd.AISService.AIS +{ + public class AIS_PosReport : AIS + { + 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; + + #region Properties + + 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 string.Format("{0}-{1}-{2} {3}:{4}:{5}", + this.timestamp.Year, this.timestamp.Month, this.timestamp.Day, + this.timestamp.Hour, this.timestamp.Minute, this.timestamp.Second); + } + } + + 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; + } + } + + #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 = AIS.DecodeBinary(this.data); + + try + { + this.userId = AIS.GetInt(bits, 8, 37); + this.navstatus = AIS.GetInt(bits, 38, 41); + this.rot = AIS.GetInt(bits, 42, 49); + this.sog = AIS.GetInt(bits, 50, 59); + this.accur = AIS.GetInt(bits, 60, 60); + this.longitude = AIS.GetInt(bits, 61, 88); + this.latitude = AIS.GetInt(bits, 89, 115); + this.cog = AIS.GetInt(bits, 116, 127); + this.trueheading = AIS.GetInt(bits, 128, 136); + this.utcTimeSecond = AIS.GetInt(bits, 137, 142); + this.reserved = AIS.GetInt(bits, 143, 146); + this.spare = AIS.GetInt(bits, 147, 147); + this.raim = AIS.GetInt(bits, 148, 148); + this.commstate = AIS.GetInt(bits, 149, 167); + } + catch (Exception e) + { + Trace.WriteLine(string.Format("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.AISService/AIS/AIS_QueueManager.cs b/AIS/bsmd.AISService/AIS/AIS_QueueManager.cs new file mode 100644 index 00000000..faecdace --- /dev/null +++ b/AIS/bsmd.AISService/AIS/AIS_QueueManager.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Timers; +using System.Diagnostics; + +namespace bsmd.AISService.AIS +{ + /// + /// Hier laufen die Fäden zusammen. Diese Klasse enthält alle Objekte und kann direkt von + /// Konsolen / services und Windowsprogrammen verwendet werden + /// +public class AIS_QueueManager + { + + public delegate void AISQueueChangedHandler(AIS_Target target); + public event AISQueueChangedHandler AISQueueChanged; + public event AISQueueChangedHandler DBUpdateRequired; + + private Dictionary activeTargets = new Dictionary(); + private List activeTargetList = new List(); + private List databaseTargets = new List(); + private List watchkeeperTargets = new List(); + private AIS_Configuration configuration; + private List serialHandlerList = new List(); + private List telnetHandlerList = new List(); + private List dbUpdateQueue = new List(); + private Timer dbUpdateTimer = new Timer(); + private bool isStarted = false; + + #region Construction + + public AIS_QueueManager(AIS_Configuration configuration, List serialIOs, List ais_Telnets) + { + this.configuration = configuration; + + foreach (Serial_IO serialIO in serialIOs) + { + AIS_Decoder decoder = new AIS_Decoder(); + decoder.AISMessageReceived += new AIS_Decoder.AISMessageHandler(this.decoder_AISMessageReceived); + SerialDataHandler handler = new SerialDataHandler(serialIO, decoder); + this.serialHandlerList.Add(handler); + } + + foreach (AIS_Telnet aisTelnet in ais_Telnets) + { + AIS_Decoder decoder = new AIS_Decoder(); + decoder.AISMessageReceived += new AIS_Decoder.AISMessageHandler(this.decoder_AISMessageReceived); + TelnetDataHandler tdn = new TelnetDataHandler(aisTelnet, decoder); + this.telnetHandlerList.Add(tdn); + } + + AIS_Target.dbUpdateInterval = new TimeSpan(0, 0, configuration.DBMinPosReportTimeDifference); + this.dbUpdateTimer.Interval = configuration.DBUpdateInterval; + this.dbUpdateTimer.Elapsed += new ElapsedEventHandler(dbUpdateTimer_Elapsed); + } + + #endregion + + #region Properties + + public List ActiveTargets + { + get + { + return this.activeTargetList; + } + } + + public bool IsStarted + { + get { return this.isStarted; } + } + + #endregion + + #region event handler + + void dbUpdateTimer_Elapsed(object sender, ElapsedEventArgs e) + { + while (this.dbUpdateQueue.Count > 0) + { + AIS_Target currentTarget = null; + lock (this.dbUpdateQueue) + { + // Trace.WriteLine(string.Format("Update queue size: {0}", this.dbUpdateQueue.Count)); + currentTarget = this.dbUpdateQueue[0]; + this.dbUpdateQueue.RemoveAt(0); + } + this.OnDBUpdateRequired(currentTarget); + } + + // remove stale targets + lock (this.activeTargetList) + { + + for(int i=0;i this.configuration.TargetStaleMins) + { + this.activeTargetList.RemoveAt(i); + i--; + } + } + } + + } + + void decoder_AISMessageReceived(AIS message) + { + lock (this.activeTargets) + { + // Trace.WriteLine(string.Format("Queue manager: AIS message received, queue size: {0}", activeTargets.Count)); + if (!this.activeTargets.ContainsKey(message.MMSI)) + { + AIS_Target target = new AIS_Target(message.MMSI); + this.activeTargets.Add(message.MMSI, target); + lock (this.activeTargetList) + { + this.activeTargetList.Add(target); + } + } + + this.activeTargets[message.MMSI].Update(message); + this.OnAISQueueChanged(this.activeTargets[message.MMSI]); + + if (this.activeTargets[message.MMSI].UpdateDB) + { + lock (this.dbUpdateQueue) + { + if (!this.dbUpdateQueue.Contains(this.activeTargets[message.MMSI])) + this.dbUpdateQueue.Add(this.activeTargets[message.MMSI]); + } + } + } + } + + #endregion + + #region public methods + + public bool Start(ref string message) + { + bool retval = true; + if (this.isStarted) + { + message = "Queue manager already started"; + return true; + } + + foreach (SerialDataHandler sdh in this.serialHandlerList) + { + string messagePart = ""; + retval &= sdh.Start(ref messagePart); + if (!retval) + message += messagePart + Environment.NewLine; + if(retval) sdh.AIS_Decoder.Start(); + } + + foreach (TelnetDataHandler tdh in this.telnetHandlerList) + { + string messagePart = ""; + retval &= tdh.Start(ref messagePart); + if (!retval) + message += messagePart + Environment.NewLine; + if (retval) tdh.AIS_Decoder.Start(); + } + + if (retval) + this.dbUpdateTimer.Start(); + + if (retval) this.isStarted = true; + + return retval; + } + + public void Stop() + { + if (this.isStarted) + { + foreach (SerialDataHandler sdh in this.serialHandlerList) + { + sdh.Stop(); + sdh.AIS_Decoder.Stop(); + } + foreach (TelnetDataHandler tdh in this.telnetHandlerList) + { + tdh.Stop(); + tdh.AIS_Decoder.Stop(); + } + this.dbUpdateTimer.Stop(); + this.isStarted = false; + } + } + + #endregion + + #region OnEvent methods + + protected void OnAISQueueChanged(AIS_Target target) + { + if (this.AISQueueChanged != null) this.AISQueueChanged(target); + } + + protected void OnDBUpdateRequired(AIS_Target target) + { + if (this.DBUpdateRequired != null) this.DBUpdateRequired(target); + } + + #endregion + + } +} diff --git a/AIS/bsmd.AISService/AIS/AIS_StaticData.cs b/AIS/bsmd.AISService/AIS/AIS_StaticData.cs new file mode 100644 index 00000000..6bff5030 --- /dev/null +++ b/AIS/bsmd.AISService/AIS/AIS_StaticData.cs @@ -0,0 +1,381 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.Text; + +namespace bsmd.AISService.AIS +{ + public class AIS_StaticData : AIS + { + #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; + + #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 string DBETA + { + get + { + if (this.eta.HasValue) + { + return string.Format("{0}-{1}-{2} {3}:{4}:{5}", + this.eta.Value.Year, this.eta.Value.Month, this.eta.Value.Day, + this.eta.Value.Hour, this.eta.Value.Minute, this.eta.Value.Second); + } + else + return ""; + } + } + + + #endregion + + #region abstract method implementation + + protected override Status Decode() + { + BitArray bits = AIS.DecodeBinary(this.data); + Status result = Status.OK; + + + try + { + int type = AIS.GetInt(bits, 0, 5); + if (type != 5) + { + result = Status.ILLEGAL_ARGUMENT; + } + else + { + this.userId = AIS.GetInt(bits, 6, 37); + this.ais_version = AIS.GetInt(bits, 38, 39); + this.imoNumber = AIS.GetInt(bits, 40, 69); + + StringBuilder sb_callsign = new StringBuilder(7); + for (int i = 0; i < 7; i++) + { + int cval = AIS.GetInt(bits, 70 + (6 * i), 75 + (6 * i)); + char ch = AIS.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 = AIS.GetInt(bits, 112 + (6 * i), 117 + (6 * i)); + char ch = AIS.GetAISChar(cval); + if (ch == '@') ch = ' '; + sb_name.Append(ch); + } + this.name = sb_name.ToString().Trim(); + + this.shiptype = AIS.GetInt(bits, 232, 239); + this.dimension = AIS.GetInt(bits, 240, 269); + this.a = AIS.GetInt(bits, 240, 248); + this.b = AIS.GetInt(bits, 249, 257); + this.c = AIS.GetInt(bits, 258, 263); + this.d = AIS.GetInt(bits, 264, 269); + this.typeofdevice = AIS.GetInt(bits, 270, 273); + this.etamonth = AIS.GetInt(bits, 274, 277); + this.etaday = AIS.GetInt(bits, 278, 282); + this.etahour = AIS.GetInt(bits, 283, 287); + this.etaminute = AIS.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) { + Trace.WriteLine("ERROR creating ETA timestamp"); + } + this.maxpresetstaticdraught = AIS.GetInt(bits, 294, 301); + + StringBuilder sb_destination = new StringBuilder(20); + for (int i = 0; i < 20; i++) + { + int cval = AIS.GetInt(bits, 302 + (6 * i), 307 + (6 * i)); + char ch = AIS.GetAISChar(cval); + if (ch == '@') ch = ' '; + sb_destination.Append(ch); + } + this.destination = sb_destination.ToString().Trim(); + + this.dte = AIS.GetInt(bits, 422, 422); + this.spare = AIS.GetInt(bits, 423, 423); + + } + } + catch (Exception e) + { + Trace.WriteLine(string.Format("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 AIS_Target.Type GetShipTypeSimple(int shiptype) + { + switch (shiptype) + { + case 50: + case 51: + case 52: + case 53: + case 54: + case 55: + case 56: + case 57: + return AIS_Target.Type.TUG; + default: + int d1 = shiptype / 10; + switch (d1) + { + case 2: + return AIS_Target.Type.WIG; + case 3: + return AIS_Target.Type.OTHER; + case 4: + return AIS_Target.Type.HSC; + case 6: + return AIS_Target.Type.PASSENGER; + case 7: + return AIS_Target.Type.CARGO; + case 8: + return AIS_Target.Type.TANKER; + } + return AIS_Target.Type.OTHER; + } + } + + 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.AISService/AIS/AIS_Target.cs b/AIS/bsmd.AISService/AIS/AIS_Target.cs new file mode 100644 index 00000000..f3002406 --- /dev/null +++ b/AIS/bsmd.AISService/AIS/AIS_Target.cs @@ -0,0 +1,327 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace bsmd.AISService.AIS +{ + public class AIS_Target + { + + #region private members + + public static TimeSpan dbUpdateInterval = new TimeSpan(0, 2, 0); // neue Position in DB schreiben (min Interval) + private int mmsi; + private bool isClassB = false; + private bool? isWatchkeeper = null; + private DateTime? lastUpdate; + private bool updateDB = false; + private string name; + private string station; + private string lastDBName; + private string callSign; + private bool selected = false; + + private AIS staticInfo; + private AIS posReport; + private AIS lastAdditionalData; + + private AIS_Target.Type type = Type.OTHER; + private AIS_Target.NavStatus navStatus = AIS_Target.NavStatus.UNKNOWN; + + #endregion + + #region public defs + + + + public enum Type + { + PASSENGER, + CARGO, + TANKER, + HSC, + WIG, + TUG, + YACHT, + OTHER + } + + /// + /// vereinfacht + /// + public enum NavStatus + { + UNKNOWN, + UNDERWAY, + MOORED + } + + #endregion + + #region Construction + + public AIS_Target(int mmsi) + { + this.mmsi = mmsi; + } + + #endregion + + #region Properties + + public bool UpdateDB + { + get { return this.updateDB; } + set { this.updateDB = value; } + } + + public int MMSI + { + get { return this.mmsi; } + } + + public DateTime? LastUpdate + { + get { return this.lastUpdate; } + } + + public string Name + { + get { + if ((this.name == null) || (this.name.Length == 0)) + return this.LastDBName; + return this.name; + } + set { this.name = value; } + } + + public string Callsign + { + get { return this.callSign; } + set { this.callSign = value; } + } + + public string LastDBName + { + get { return this.lastDBName; } + set { this.lastDBName = value; } + } + + public string ReceivedFrom + { + get { return this.station; } + } + + public AIS LastPosReport + { + get { return this.posReport; } + } + + public AIS LastStaticData + { + get { return this.staticInfo; } + } + + public double? Latitude + { + get + { + if (this.LastPosReport == null) return null; + if (this.LastPosReport is AIS_PosReport) + return ((AIS_PosReport)this.LastPosReport).Latitude; + if (this.LastPosReport is AIS_ClassB) + return ((AIS_ClassB)this.LastPosReport).Latitude; + if (this.LastPosReport is AIS_ClassBExt) + return ((AIS_ClassBExt)this.LastPosReport).Latitude; + return null; + } + } + + public Type TargetType + { + get { return this.type; } + set { this.type = value; } + } + + public NavStatus TargetNavStatus + { + get { return this.navStatus; } + } + + public double? Longitude + { + get + { + if (this.LastPosReport == null) return null; + if (this.LastPosReport is AIS_PosReport) + return ((AIS_PosReport)this.LastPosReport).Longitude; + if (this.LastPosReport is AIS_ClassB) + return ((AIS_ClassB)this.LastPosReport).Longitude; + if (this.LastPosReport is AIS_ClassBExt) + return ((AIS_ClassBExt)this.LastPosReport).Longitude; + return null; + } + } + + public bool? IsClassB + { + get + { + return this.isClassB; + } + } + + public int? Heading + { + get + { + if (this.LastPosReport == null) return null; + if (this.LastPosReport is AIS_PosReport) + return ((AIS_PosReport)this.LastPosReport).TrueHeading; + if (this.LastPosReport is AIS_ClassB) + return ((AIS_ClassB)this.LastPosReport).TrueHeading; + if (this.LastPosReport is AIS_ClassBExt) + return ((AIS_ClassBExt)this.LastPosReport).TrueHeading; + return null; + } + } + + public int? COG + { + get + { + if (this.LastPosReport == null) return null; + if (this.LastPosReport is AIS_PosReport) + return (int)((AIS_PosReport)this.LastPosReport).COG; + if (this.LastPosReport is AIS_ClassB) + return (int)((AIS_ClassB)this.LastPosReport).Cog; + if (this.LastPosReport is AIS_ClassBExt) + return (int) ((AIS_ClassBExt)this.LastPosReport).Cog; + return null; + } + } + + public bool? IsWatchkeeperShip + { + get { return this.isWatchkeeper; } + set { this.isWatchkeeper = value; } + } + + public bool Selected + { + get { return this.selected; } + set { this.selected = value; } + } + + public string Station + { + get { return this.station; } + set { this.station = value; } + } + + #endregion + + #region public methods + + public static AIS_Target.NavStatus GetCurrentNavstatus(int status) + { + AIS_Target.NavStatus result = NavStatus.UNKNOWN; + switch (status) + { + case 0: + case 8: + result = NavStatus.UNDERWAY; + break; + default: + result = NavStatus.MOORED; + break; + } + return result; + } + + public void Update(AIS message) + { + this.station = message.Station; + + switch (message.MessageType) + { + case AIS.AISType.POSITION_REPORT: + case AIS.AISType.POSITION_REPORT_ASSIGNED: + case AIS.AISType.POSITION_REPORT_SPECIAL: + if ((this.lastUpdate.HasValue && + (((AIS_PosReport)message).Timestamp - this.lastUpdate.Value) > AIS_Target.dbUpdateInterval) + || (!this.lastUpdate.HasValue)) + { + this.updateDB = true; + this.lastUpdate = ((AIS_PosReport)message).Timestamp; + } + this.posReport = message; + this.navStatus = AIS_Target.GetCurrentNavstatus(((AIS_PosReport)message).NavStatusVal); + // System.Diagnostics.Trace.WriteLine(string.Format("pos report at {0}", this.lastUpdate)); + break; + case AIS.AISType.POSITION_REPORT_B_EQUIP: + if ((this.lastUpdate.HasValue && + (((AIS_ClassB)message).Timestamp - this.lastUpdate.Value) > AIS_Target.dbUpdateInterval) + || (!this.lastUpdate.HasValue)) + { + this.updateDB = true; + this.lastUpdate = ((AIS_ClassB)message).Timestamp; + this.isClassB = true; + this.type = Type.YACHT; + this.navStatus = NavStatus.UNDERWAY; + } + this.posReport = message; + break; + case AIS.AISType.POSITION_REPORT_B_EQUIP_EXT: + if ((this.lastUpdate.HasValue && + (((AIS_ClassBExt)message).Timestamp - this.lastUpdate.Value) > AIS_Target.dbUpdateInterval) + || (!this.lastUpdate.HasValue)) + { + this.updateDB = true; + this.lastUpdate = ((AIS_ClassBExt)message).Timestamp; + this.isClassB = true; + this.type = Type.YACHT; + this.navStatus = NavStatus.UNDERWAY; + } + this.posReport = message; + break; + case AIS.AISType.STATIC_VOYAGE_DATA: + this.staticInfo = message; + this.name = ((AIS_StaticData)message).Name; + this.callSign = ((AIS_StaticData)message).Callsign; + this.type = AIS_StaticData.GetShipTypeSimple(((AIS_StaticData)message).ShipTypeVal); + + break; + case AIS.AISType.CLASS_B_STATIC_DATA: + if (((AIS_ClassBStatic)message).IsPartA) + { + this.name = ((AIS_ClassBStatic)message).Name; + } + else + { + this.callSign = ((AIS_ClassBStatic)message).Callsign; + } + this.staticInfo = message; + this.type = Type.YACHT; + this.isClassB = true; + break; + default: + this.lastAdditionalData = message; + break; + } + + } + + #endregion + + #region overrides + + public override string ToString() + { + return string.Format("{0} [{1}]", this.Name, this.MMSI); + } + + #endregion + + } +} diff --git a/AIS/bsmd.AISService/AIS/AIS_Target_Comparer.cs b/AIS/bsmd.AISService/AIS/AIS_Target_Comparer.cs new file mode 100644 index 00000000..300b38da --- /dev/null +++ b/AIS/bsmd.AISService/AIS/AIS_Target_Comparer.cs @@ -0,0 +1,104 @@ +using System; +using System.ComponentModel; +using System.Collections.Generic; +using System.Text; + +namespace bsmd.AISService.AIS +{ + public class AIS_Target_Comparer : IComparer + { + private SortPropertyEnum sortProperty = SortPropertyEnum.NAME; + private ListSortDirection sortDirection = ListSortDirection.Ascending; + + public enum SortPropertyEnum + { + NONE, + MMSI, + NAME, + CALLSIGN, + LASTUPDATE, + STATION + } + + #region Properties + + public ListSortDirection SortDirection + { + get { return this.sortDirection; } + set { this.sortDirection = value; } + } + + public SortPropertyEnum SortProperty + { + get { return this.sortProperty; } + set { this.sortProperty = value; } + } + + #endregion + + + #region IComparer Members + + public int Compare(AIS_Target x, AIS_Target y) + { + switch (this.sortProperty) + { + case SortPropertyEnum.NONE: + return 0; + case SortPropertyEnum.NAME: + { + string xName = x.LastDBName; + if (xName == null) xName = ""; + string yName = y.LastDBName; + if (yName == null) yName = ""; + if (this.sortDirection == ListSortDirection.Ascending) + return xName.CompareTo(yName); + else + return yName.CompareTo(xName); + } + case SortPropertyEnum.CALLSIGN: + { + string xCallsign = x.Callsign; + if (xCallsign == null) xCallsign = ""; + string yCallsign = y.Callsign; + if (yCallsign == null) yCallsign = ""; + if (this.sortDirection == ListSortDirection.Ascending) + return xCallsign.CompareTo(yCallsign); + else + return yCallsign.CompareTo(xCallsign); + } + case SortPropertyEnum.LASTUPDATE: + { + DateTime xTime = x.LastUpdate ?? DateTime.MinValue; + DateTime yTime = y.LastUpdate ?? DateTime.MinValue; + if (this.sortDirection == ListSortDirection.Ascending) + return xTime.CompareTo(yTime); + else + return yTime.CompareTo(xTime); + } + case SortPropertyEnum.MMSI: + { + if (this.sortDirection == ListSortDirection.Ascending) + return x.MMSI.CompareTo(y.MMSI); + else + return y.MMSI.CompareTo(x.MMSI); + } + case SortPropertyEnum.STATION: + { + if (this.sortDirection == ListSortDirection.Ascending) + return x.ReceivedFrom.CompareTo(y.ReceivedFrom); + else + return y.ReceivedFrom.CompareTo(x.ReceivedFrom); + } + + default: + return 0; + } + + + + } + + #endregion + } +} diff --git a/AIS/bsmd.AISService/AIS/AIS_Telnet.cs b/AIS/bsmd.AISService/AIS/AIS_Telnet.cs new file mode 100644 index 00000000..5a8ae60b --- /dev/null +++ b/AIS/bsmd.AISService/AIS/AIS_Telnet.cs @@ -0,0 +1,150 @@ +// +// Class: AIS_Telnet +// Current CLR: 4.0.30319.296 +// System: Microsoft Visual Studio 10.0 +// Author: dani +// Created: 3/16/2013 12:58:03 PM +// +// Copyright (c) 2013 Informatikbüro Daniel Schick. All rights reserved. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Net.Sockets; + +namespace bsmd.AISService.AIS +{ + public class AIS_Telnet + { + + #region private fields + + private const int BFSIZE = 1024; + + private NetworkStream tcpStream; + private TcpClient tcpSocket; + private string currentString = ""; + private string hostname; + private int port; + private DateTime? lastRead; + + #endregion + + public AIS_Telnet(string theHostname, int thePort) + { + this.port = thePort; + this.hostname = theHostname; + this.Connect(); + } + + #region Properties + + public bool IsConnected + { + get { return tcpSocket.Connected; } + } + + public string Hostname { get { return this.hostname; } } + + public int Port { get { return this.port; } } + + public string StationName { get; set; } + + #endregion + + #region public methods + + private string ReadCurrentUptoNewline() + { + int newlineIndex = currentString.IndexOf('\n'); + string result = this.currentString.Substring(0, newlineIndex); + if (currentString.Length > (newlineIndex + 1)) + currentString = currentString.Substring(newlineIndex + 1); + else + currentString = ""; + return result; + } + + public string ReadLine() + { + + string result = ""; + if (currentString.IndexOf('\n') >= 0) + return ReadCurrentUptoNewline(); + + + byte[] inputBuffer = new byte[1024]; + + if ((tcpSocket == null) || (!tcpSocket.Connected) || !this.tcpStream.CanRead) + this.Connect(); + if ((tcpSocket == null) || !tcpSocket.Connected) + { + System.Threading.Thread.Sleep(30000); // wait 5 mins if connect is unsuccessful + return result; + } + if (this.tcpStream.DataAvailable) + { + try + { + int bytesRead = this.tcpStream.Read(inputBuffer, 0, 1024); + if (bytesRead > 0) + { + this.lastRead = DateTime.Now; + this.currentString += Encoding.ASCII.GetString(inputBuffer, 0, bytesRead); + if (currentString.IndexOf('\n') >= 0) + return ReadCurrentUptoNewline(); + if (this.currentString.Length > 1024) this.currentString = ""; // truncate to avoid overflow for wrong client data flow + } + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine(string.Format("exception reading from tcp stream: {0}", ex.Message)); + result = ""; + } + } + else + { + // wenn die Verbindung wegkracht ist immer noch connected true, aber DataAvailable false + // es gibt anscheinend keinen richtig guten Workaround. Hard case: Nach einer Stunde Inaktivität schließt der Client hier die + // Verbindung und versucht reconnects. Das bekommt der LS100PortProxy aber nicht immer mit.. Folge sind dann die "stehengebliebenen" + // Verbindungen + if (lastRead == null) lastRead = DateTime.Now; + if ((DateTime.Now - lastRead.Value).TotalSeconds > 600) + { + this.tcpSocket.Close(); + this.tcpSocket = null; + System.Diagnostics.Trace.WriteLine("closing inactive TcpClient"); + this.lastRead = DateTime.Now; // reset timer + } + } + + return result; + } + + public void Close() + { + if (this.tcpStream != null) this.tcpStream.Close(); + this.tcpSocket.Close(); + this.tcpStream.Dispose(); + } + + #endregion + + public void Connect() + { + try + { + if ((this.tcpSocket != null) && (this.tcpSocket.Connected)) return; + this.tcpSocket = new TcpClient(this.hostname, this.port); + this.tcpStream = tcpSocket.GetStream(); + System.Diagnostics.Trace.WriteLine(string.Format("TCP stream connected ({0}:{1})", this.hostname, this.port)); + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine( + string.Format("AIS_Telnet: cannot connect to ({0}:{1}) : {2}", this.hostname, this.port, ex.Message)); + } + } + } +} diff --git a/AIS/bsmd.AISService/AIS/NMEA.cs b/AIS/bsmd.AISService/AIS/NMEA.cs new file mode 100644 index 00000000..71b74f2d --- /dev/null +++ b/AIS/bsmd.AISService/AIS/NMEA.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace bsmd.AISService.AIS +{ + internal abstract class NMEA + { + protected string type = ""; + protected string data; + protected string[] elements = null; + + public enum Status + { + OK, + UNKNOWN_TYPE, + CHECKSUM, + ILLEGAL_ARGUMENT + } + + protected abstract void Decode(); + + 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) + { + System.Diagnostics.Trace.WriteLine(string.Format("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": + result = new NMEA_AIS_Sentence(); + break; + + case "PNMLS": + result = new NMEA_PNMLS_Sentence(); + break; + + default: + break; + } + + if (result != null) + result.type = type.ToUpper(); + + return result; + } + + protected bool IsChecksumOK + { + get + { + return this.data.Substring(this.data.IndexOf('*') + 1) == this.CalculateChecksum(); + + } + } + + private string CalculateChecksum() + { + int checksum = Convert.ToByte(this.data[1]); + for (int i = 2; i < this.data.IndexOf('*'); i++) + { + checksum ^= Convert.ToByte(this.data[i]); + } + return checksum.ToString("X2"); + } + + + } +} diff --git a/AIS/bsmd.AISService/AIS/NMEA_AIS_Sentence.cs b/AIS/bsmd.AISService/AIS/NMEA_AIS_Sentence.cs new file mode 100644 index 00000000..9b916451 --- /dev/null +++ b/AIS/bsmd.AISService/AIS/NMEA_AIS_Sentence.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace bsmd.AISService.AIS +{ + internal class NMEA_AIS_Sentence : NMEA + { + private int total_sentence_nr; + private int msg_sentence_nr; + private int? seq_message_ident; + private string ais_channel_nr; + private string ais_message; + private int fillbits; + + #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 int? 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 + + protected override void Decode() + { + this.total_sentence_nr = Convert.ToInt32(this.elements[1]); + this.msg_sentence_nr = Convert.ToInt32(this.elements[2]); + if (this.elements[3].Length > 0) + this.seq_message_ident = Convert.ToInt32(this.elements[3]); + 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)) + System.Diagnostics.Trace.WriteLine("AIS_Sentence.Decode(): fillbits are no integer"); + } + catch (ArgumentOutOfRangeException) + { + System.Diagnostics.Trace.WriteLine("AIS_Sentence.Decode(): split() problem, trouble decoding fillbits"); + } + } + } +} diff --git a/AIS/bsmd.AISService/AIS/NMEA_PNMLS_Sentence.cs b/AIS/bsmd.AISService/AIS/NMEA_PNMLS_Sentence.cs new file mode 100644 index 00000000..65a61dea --- /dev/null +++ b/AIS/bsmd.AISService/AIS/NMEA_PNMLS_Sentence.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace bsmd.AISService.AIS +{ + /// + /// NMEA PNMLS sentence + /// sentence shows signal level for preceding message + /// + class NMEA_PNMLS_Sentence : NMEA + { + private int signal_level; + private int detection_threshold; + private int interval; + + #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 decode func + + 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) + { + Trace.WriteLine("NMEA [PNMLS] input format error"); + } + } + + #endregion + + } +} diff --git a/AIS/bsmd.AISService/AIS/SerialDataHandler.cs b/AIS/bsmd.AISService/AIS/SerialDataHandler.cs new file mode 100644 index 00000000..9e0395d8 --- /dev/null +++ b/AIS/bsmd.AISService/AIS/SerialDataHandler.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace bsmd.AISService.AIS +{ + public class SerialDataHandler + { + private Serial_IO serial_IO; + private AIS_Decoder decoder; + + public SerialDataHandler(Serial_IO io, AIS_Decoder decoder) + { + this.serial_IO = io; + this.decoder = decoder; + this.serial_IO.LineRead += new Serial_IO.LineReadHandler(serial_IO_LineRead); + } + + public Serial_IO Serial_IO + { + get { return this.serial_IO; } + } + + public AIS_Decoder AIS_Decoder + { + get { return this.decoder; } + } + + public bool Start(ref string message) + { + return this.serial_IO.Open(ref message); + } + + public void Stop() + { + this.serial_IO.Close(); + } + + protected void serial_IO_LineRead(string data) + { + NMEA.Status nmea_Status = NMEA.Status.OK; + if (data == null || data.Length == 0) return; + + NMEA decodedSentence = NMEA.Decode(data, ref nmea_Status); + if (decodedSentence != null) + { + if (decodedSentence is NMEA_AIS_Sentence) + { + NMEA_AIS_Sentence aisSentence = decodedSentence as NMEA_AIS_Sentence; + this.decoder.Decode(aisSentence.AIS_Message, aisSentence.Msg_Sentence_Nr, + aisSentence.Total_Sentence_Nr, aisSentence.Seq_Message_Ident, this.Serial_IO.StationName); + } + } + else + { + Trace.WriteLine("Serial data handler: NMEA decoder returned null sentence"); + } + } + + public override string ToString() + { + return string.Format("Serial AIS Receiver {0} on {1}", + this.serial_IO.StationName, + this.serial_IO.ComPort); + } + + } +} diff --git a/AIS/bsmd.AISService/AIS/Serial_IO.cs b/AIS/bsmd.AISService/AIS/Serial_IO.cs new file mode 100644 index 00000000..05817b3c --- /dev/null +++ b/AIS/bsmd.AISService/AIS/Serial_IO.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO.Ports; +using System.Threading; + +namespace bsmd.AISService.AIS +{ + public class Serial_IO + { + #region private fields + private string stationName; + private SerialPort port; + private bool runReader = true; + private Thread readerThread = null; + #endregion + + // event fired if input line is available + public delegate void LineReadHandler(string data); + public event LineReadHandler LineRead; + + + public Serial_IO() + { + this.port = new SerialPort(); + } + + public bool Open(ref string message) + { + bool retval = true; + try + { + this.port.Open(); + } + catch (Exception ex) + { + message = ex.Message; + retval = false; + } + if (retval) + { + this.readerThread = new Thread(new ThreadStart(this.Read)); + this.runReader = true; + this.readerThread.Start(); + } + return retval; + } + + public void Close() + { + this.runReader = false; + if(readerThread != null) + if(readerThread.ThreadState == ThreadState.Running) + this.readerThread.Join(); + if (this.port.IsOpen) + { + this.port.BaseStream.Flush(); + this.port.Close(); + } + } + + public string[] GetComPorts() + { + return SerialPort.GetPortNames(); + } + + #region Properties + + public int BaudRate + { + get { return this.port.BaudRate; } + set { this.port.BaudRate = value; } + } + + public string ComPort + { + get { return this.port.PortName; } + set { this.port.PortName = value; } + } + + public string StationName + { + get { return this.stationName; } + set { this.stationName = value; } + } + + #endregion + + #region protected methods + + protected void Read() + { + while (runReader) + { + try + { + string line = this.port.ReadLine(); + this.OnInputLineRead(line); + //System.Diagnostics.Trace.WriteLine(line); + } + catch (Exception) { } + } + } + + protected void OnInputLineRead(string line) + { + if (this.LineRead != null) + this.LineRead(line); + } + + #endregion + + } + + + +} diff --git a/AIS/bsmd.AISService/AIS/TelnetDataHandler.cs b/AIS/bsmd.AISService/AIS/TelnetDataHandler.cs new file mode 100644 index 00000000..686c737d --- /dev/null +++ b/AIS/bsmd.AISService/AIS/TelnetDataHandler.cs @@ -0,0 +1,112 @@ +// +// Class: TelnetDataHandler +// Current CLR: 4.0.30319.296 +// System: Microsoft Visual Studio 10.0 +// Author: dani +// Created: 3/16/2013 2:12:35 PM +// +// Copyright (c) 2013 Informatikbüro Daniel Schick. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Diagnostics; + +namespace bsmd.AISService.AIS +{ + public class TelnetDataHandler + { + AIS_Telnet aisTelnet; + AIS_Decoder decoder; + Thread readerThread; + bool requestStop; + + public TelnetDataHandler(AIS_Telnet telnetConnection, AIS_Decoder aisDecoder) + { + this.aisTelnet = telnetConnection; + this.decoder = aisDecoder; + } + + public AIS_Decoder AIS_Decoder + { + get { return this.decoder; } + } + + public override string ToString() + { + return string.Format("Telnet AIS Receiver {0}:{1}", + this.aisTelnet.Hostname, + this.aisTelnet.Port); + } + + public bool Start(ref string message) + { + if (readerThread != null) return true; // already running + try + { + this.readerThread = new Thread(new ThreadStart(this.ReaderThread)); + readerThread.Start(); + this.requestStop = false; + message = "reader thread started"; + return true; + } + catch (Exception ex) + { + message = ex.Message; + return false; + } + } + + public void Stop() + { + if (readerThread.IsAlive) + { + this.requestStop = true; + readerThread.Join(); + } + this.readerThread = null; + } + + private void ReaderThread() + { + NMEA.Status nmea_Status = NMEA.Status.OK; + System.Diagnostics.Trace.WriteLine("starting telnet reader thread"); + while (!requestStop) + { + try + { + + string data = this.aisTelnet.ReadLine(); + // Trace.WriteLine(data); + if (data != null && data.Length > 0) + { + NMEA decodedSentence = NMEA.Decode(data, ref nmea_Status); + if (decodedSentence != null) + { + if (decodedSentence is NMEA_AIS_Sentence) + { + NMEA_AIS_Sentence aisSentence = decodedSentence as NMEA_AIS_Sentence; + this.decoder.Decode(aisSentence.AIS_Message, aisSentence.Msg_Sentence_Nr, + aisSentence.Total_Sentence_Nr, aisSentence.Seq_Message_Ident, this.aisTelnet.StationName); + } + } + else + { + Trace.WriteLine("Serial data handler: NMEA decoder returned null/empty sentence"); + } + } + } + catch (Exception ex) + { + var st = new StackTrace(ex, true); + var frame = st.GetFrame(0); + var line = frame.GetFileLineNumber(); + Trace.WriteLine(string.Format("Exception in telnet reader thread: {0}, top frame ln {1}", ex.Message, line)); + Trace.WriteLine(ex.StackTrace); + } + Thread.Sleep(100); + } + aisTelnet.Close(); + } + } +} diff --git a/AIS/bsmd.AISService/AISService.Designer.cs b/AIS/bsmd.AISService/AISService.Designer.cs new file mode 100644 index 00000000..6285cc2e --- /dev/null +++ b/AIS/bsmd.AISService/AISService.Designer.cs @@ -0,0 +1,37 @@ +namespace bsmd.AISService +{ + partial class AISService + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + this.ServiceName = "Service1"; + } + + #endregion + } +} diff --git a/AIS/bsmd.AISService/AISService.cs b/AIS/bsmd.AISService/AISService.cs new file mode 100644 index 00000000..8f8444b9 --- /dev/null +++ b/AIS/bsmd.AISService/AISService.cs @@ -0,0 +1,93 @@ +// Copyright (c) 2008-2018 schick Informatik +// Description: +// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Linq; +using System.ServiceProcess; +using System.Text; +using System.Threading.Tasks; + +using bsmd.AISService.AIS; +using bsmd.AISService.DB; + +using log4net; +using System.IO; + +namespace bsmd.AISService +{ + public partial class AISService : ServiceBase + { + private const string config_filename = "ais_config.xml"; + private ILog _log = LogManager.GetLogger(typeof(AISService)); + private AIS_QueueManager qManager; + + public AISService() + { + Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); + InitializeComponent(); + } + + protected override void OnStart(string[] args) + { + string errorMessage = ""; + + this.EventLog.Source = this.ServiceName; + this.EventLog.Log = "Application"; + this.Init(args); + if (qManager.Start(ref errorMessage)) + { + this.EventLog.WriteEntry("BSMD AIS Service started.", EventLogEntryType.Information); + System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly(); + FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location); + string version = fvi.FileVersion; + _log.InfoFormat("Starting AIS Service. v.{0} -------------- ", version); + } else + { + _log.ErrorFormat("AIS Service start failed: {0}", errorMessage); + } + + } + + protected override void OnStop() + { + this.qManager.Stop(); + } + + protected void Init(string[] args) + { + AIS_Configuration configuration = AIS_Configuration.Load(config_filename); + + if (configuration == null) + { + Console.WriteLine(string.Format("cannot read configuration {0}", config_filename)); + return; + } + + DBConnector dbConnector = new DBConnector(); + dbConnector.ConnectionString = configuration.DBConnectionString; + if (!dbConnector.Open()) + { + Console.WriteLine("Error connecting to database"); + return; + } + + List stationList = AISStation.LoadStations(dbConnector); + + this.qManager = new AIS_QueueManager(configuration, AISStation.CreateSerial_IOs(stationList), AISStation.CreateAIS_Telnets(stationList)); + qManager.DBUpdateRequired += new AIS_QueueManager.AISQueueChangedHandler(dbConnector.Update); + qManager.AISQueueChanged += new AIS_QueueManager.AISQueueChangedHandler(aisDecoder_AISMessageReceived); + } + + protected void aisDecoder_AISMessageReceived(AIS_Target target) + { + Console.WriteLine(string.Format("{0}: {1} Pos:{2} {3} at {4}", target.Station, target.Name, target.Latitude, target.Longitude, target.LastUpdate)); + } + + + } +} diff --git a/AIS/bsmd.AISService/App.config b/AIS/bsmd.AISService/App.config new file mode 100644 index 00000000..0ab7508f --- /dev/null +++ b/AIS/bsmd.AISService/App.config @@ -0,0 +1,18 @@ + + + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/AIS/bsmd.AISService/DB/AISPosReport.cs b/AIS/bsmd.AISService/DB/AISPosReport.cs new file mode 100644 index 00000000..ab6e6bc7 --- /dev/null +++ b/AIS/bsmd.AISService/DB/AISPosReport.cs @@ -0,0 +1,79 @@ +using System; +using System.Diagnostics; +using System.Collections.Generic; +using System.Text; + +using bsmd.AISService.AIS; + +namespace bsmd.AISService.DB +{ + + internal class AISPosReport + { + /// + /// Saves a (class A or B) position report + /// + /// target to save + /// id of insert operation (to update hotposition table) + public static int? Save(AIS_Target target, DBConnector con, AISStation aisStation) + { + if (target.LastPosReport == null) return null; + + if (target.LastPosReport is AIS_PosReport) + { + // Trace.WriteLine("saving class A pos report"); + AIS_PosReport pr = target.LastPosReport as AIS_PosReport; + + if (aisStation != null) + { + aisStation.UpdateWithPositionReport(pr.MMSI, pr.Latitude, pr.Longitude, pr.Timestamp); + aisStation.LastPosTimestamp = pr.Timestamp; + aisStation.OnAir = true; + } + + string query = string.Format("INSERT INTO aisposreport (mmsi, navstatus, rot, cog, sog, accur, longitude, latitude, heading, timestamp, stationid) VALUES ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, '{9}', {10})", + pr.MMSI, pr.NavStatusVal, pr.ROTVal, pr.COGVal, pr.SOGVal, pr.Accuracy, + pr.LongitudeVal, pr.LatitudeVal, pr.TrueHeading ?? 511, pr.DBTimestamp, + (aisStation != null) ? aisStation.Id : 0); + + con.ExecuteNonQuery(query); + + object result = con.ExecuteScalar("SELECT LAST_INSERT_ID()"); + if (result == null) return null; + int pid = Convert.ToInt32(result); + return pid; + } + + if (target.LastPosReport is AIS_ClassB) + { + // Trace.WriteLine("saving class B pos report"); + AIS_ClassB pr = target.LastPosReport as AIS_ClassB; + aisStation.UpdateWithPositionReport(pr.MMSI, pr.Latitude, pr.Longitude, pr.Timestamp); + + string query = string.Format("INSERT INTO aisposreport (mmsi, navstatus, rot, cog, sog, accur, longitude, latitude, heading, timestamp, stationid) VALUES ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, '{9}', {10})", + pr.MMSI, 0, 0, pr.CogVal, pr.SogVal, 0, pr.LongitudeVal, pr.LatitudeVal, + pr.TrueHeading ?? 511, pr.DBTimestamp, (aisStation != null) ? aisStation.Id : 0); + + con.ExecuteNonQuery(query); + + object result = con.ExecuteScalar("SELECT LAST_INSERT_ID()"); + if (result == null) return null; + int pid = Convert.ToInt32(result); + return pid; + } + + if (target.LastPosReport is AIS_ClassBExt) + { + Trace.WriteLine("AIS class B ext not supported (yet)"); + // TODO: Import ClassB Extended report! + + } + + Trace.WriteLine(string.Format("save pos report: we should not be here.. class type: {0}", target)); + + return null; + } + + + } +} diff --git a/AIS/bsmd.AISService/DB/AISStaticData.cs b/AIS/bsmd.AISService/DB/AISStaticData.cs new file mode 100644 index 00000000..7bcc3cce --- /dev/null +++ b/AIS/bsmd.AISService/DB/AISStaticData.cs @@ -0,0 +1,521 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Data; +using System.ComponentModel; +using System.Diagnostics; + +using bsmd.AISService.AIS; + +namespace bsmd.AISService.DB +{ + + public class AISStaticData + { + + public enum DisplayStringType + { + NAME, + MMSI, + CALLSIGN + } + + private const string LoadDBShipsQuery = "SELECT aisposreport.MMSI, aisposreport.timestamp, aisposreport.latitude, aisposreport.longitude, aisposreport.stationid, aisposreport.cog, aisposreport.heading, aisposreport.navstatus, aisstaticdata.callsign, aisstaticdata.name, aisstaticdata.shiptype, aisstaticdata.classb, aisstaticdata.shipdescription FROM aisstaticdata JOIN hotposition ON aisstaticdata.mmsi = hotposition.mmsi JOIN aisposreport ON aisposreport.id = hotposition.pid ORDER BY aisstaticdata.name"; + + #region Fields + + private int mmsi; + private string name; + private string callsign; + private DateTime? lastposition; + private double? lastLatitude; + private double? lastLongitude; + private int stationid; + private int navstatus; + private int shiptype; + private string shipdescription; + private int cog; + private int heading; + private bool isClassB; + private bool isWatchkeeper; + private bool isTimedOut = false; + private bool isSelected = false; + private static DisplayStringType displayStringType = DisplayStringType.NAME; + + #endregion + + #region Properties + + public int MMSI + { + get { return this.mmsi; } + } + + public string Name + { + get { return this.name; } + } + + public string Callsign + { + get { return this.callsign; } + } + + public DateTime? LastPositionReport + { + get { return this.lastposition; } + } + + public double? LastLatitude + { + get { return this.lastLatitude; } + } + + public double? LastLongitude + { + get { return this.lastLongitude; } + } + + public bool IsClassB + { + get { return this.isClassB; } + } + + public int ShipType + { + get { return this.shiptype; } + } + + public string Description + { + get { return this.shipdescription; } + } + + public bool IsWatchkeeperShip + { + get { return this.isWatchkeeper; } + set { this.isWatchkeeper = value; } + } + + public int StationId + { + get { return this.stationid; } + } + + public int Heading + { + get { return this.heading; } + } + + public int COG + { + get { return this.cog; } + } + + public int NavStatus + { + get { return this.navstatus; } + } + + public bool Selected + { + get { return this.isSelected; } + set { this.isSelected = value; } + } + + public static DisplayStringType DisplayStringTyp + { + get { return displayStringType; } + set { displayStringType = value; } + } + + public bool IsTimedOut + { + get { return this.isTimedOut; } + set { this.isTimedOut = value; } + } + #endregion + + #region public methods + + public string ToHtmlString() + { + StringBuilder sb = new StringBuilder(); + + sb.Append(string.Format("MMSI: {0}
", this.MMSI)); + sb.Append(string.Format("Name: {0}
", this.Name)); + sb.Append(string.Format("Latitude: {0}°
", this.LastLatitude.HasValue ? this.LastLatitude.Value.ToString("N3") : "?")); + sb.Append(string.Format("Longitude: {0}°
", this.LastLongitude.HasValue ? this.LastLongitude.Value.ToString("N3") : "?")); + sb.Append(string.Format("Last report: {0}
", this.LastPositionReport.HasValue ? this.LastPositionReport.Value.ToString() : "?")); + sb.Append(string.Format("Type: {0} [{1}]
", this.Description, this.ShipType)); + sb.Append(string.Format("Navstatus: {0}
", this.NavStatus)); + + return sb.ToString(); + } + + #endregion + + #region overrides + + public override string ToString() + { + switch (displayStringType) + { + case DisplayStringType.NAME: + string result = "?"; + if (this.name != null && this.name.Length > 0) + result = this.name; + return result; + case DisplayStringType.MMSI: + return this.mmsi.ToString(); + case DisplayStringType.CALLSIGN: + if (this.Callsign == null || this.Callsign.Length == 0) + return "?"; + return this.Callsign; + default: + return string.Format("{0} - {1}", this.name, this.mmsi); + } + } + + #endregion + + #region static methods + + #region save a position report + + /// + /// Saves a (class A or B) position report + /// + /// target to save + /// id of insert operation (to update hotposition table) + public static int? Save(AIS_Target target, DBConnector con, AISStation aisStation) + { + if(target.LastStaticData == null) return null; + + int mmsi = -1; + int id = -1; + + if(target.LastStaticData is AIS_StaticData) { + mmsi = ((AIS_StaticData)target.LastStaticData).MMSI; + } + if(target.LastStaticData is AIS_ClassBStatic) { + mmsi = ((AIS_ClassBStatic)target.LastStaticData).MMSI; + } + + string query = string.Format("SELECT id FROM aisstaticdata WHERE mmsi={0}", mmsi); + object result = con.ExecuteScalar(query); + + if (result != null) // update + { + id = Convert.ToInt32(result); + } + + #region Class A + + if (target.LastStaticData is AIS_StaticData) + { + AIS_StaticData staticData = target.LastStaticData as AIS_StaticData; + + if (id >= 0) + { + if (staticData.ETA.HasValue) + { + query = string.Format("UPDATE aisstaticdata SET imonumber={0}, callsign='{1}', name='{2}', shiptype={3}, typeofdevice='{4}', shipdescription='{5}', eta='{6}', destination='{7}', breadth={8}, length={9}, draught='{10}', stationid={11}, classb=0 WHERE id={12}", + staticData.IMONumber, + staticData.Callsign.Replace("'","''"), + staticData.Name.Replace("'", "''"), + staticData.ShipTypeVal, + staticData.DeviceName, + staticData.ShipType, + staticData.DBETA, + staticData.Destination.Replace("'", "''"), + staticData.Breadth, + staticData.Length, + staticData.Draught, + aisStation.Id, + id); + } + else + { + query = string.Format("UPDATE aisstaticdata SET imonumber={0}, callsign='{1}', name='{2}', shiptype={3}, typeofdevice='{4}', shipdescription='{5}', destination='{6}', breadth={7}, length={8}, draught='{9}', stationid={10}, classb=0 WHERE id={11}", + staticData.IMONumber, + staticData.Callsign.Replace("'", "''"), + staticData.Name.Replace("'", "''"), + staticData.ShipTypeVal, + staticData.DeviceName, + staticData.ShipType, + staticData.Destination.Replace("'", "''"), + staticData.Breadth, + staticData.Length, + staticData.Draught, + aisStation.Id, + id); + + } + con.ExecuteNonQuery(query); + } + else + { + if (staticData.ETA.HasValue) + { + + query = string.Format("INSERT INTO aisstaticdata SET imonumber={0}, callsign='{1}', name='{2}', shiptype={3}, typeofdevice='{4}', shipdescription='{5}', eta='{6}', destination='{7}', breadth={8}, length={9}, draught='{10}', stationid={11}, mmsi={12}, classb=0", + staticData.IMONumber, + staticData.Callsign.Replace("'", "''"), + staticData.Name.Replace("'", "''"), + staticData.ShipTypeVal, + staticData.DeviceName, + staticData.ShipType, + staticData.DBETA, + staticData.Destination.Replace("'", "''"), + staticData.Breadth, + staticData.Length, + staticData.Draught, + aisStation.Id, + staticData.MMSI); + } + else + { + query = string.Format("INSERT INTO aisstaticdata SET imonumber={0}, callsign='{1}', name='{2}', shiptype={3}, typeofdevice='{4}', shipdescription='{5}', destination='{6}', breadth={7}, length={8}, draught='{9}', stationid={10}, mmsi={11}, classb=0", + staticData.IMONumber, + staticData.Callsign.Replace("'", "''"), + staticData.Name.Replace("'", "''"), + staticData.ShipTypeVal, + staticData.DeviceName, + staticData.ShipType, + staticData.Destination.Replace("'", "''"), + staticData.Breadth, + staticData.Length, + staticData.Draught, + aisStation.Id, + staticData.MMSI); + } + + con.ExecuteNonQuery(query); + + id = Convert.ToInt32(con.ExecuteScalar("SELECT LAST_INSERT_ID()")); + + } + + } + + #endregion + + #region Class B + + if (target.LastStaticData is AIS_ClassBStatic) + { + AIS_ClassBStatic staticData = target.LastStaticData as AIS_ClassBStatic; + + if (id >= 0) // Update + { + query = string.Format("UPDATE aisstaticdata SET stationid={0}, shiptype={1}, classb=1", aisStation.Id, staticData.ShipTypeVal); + if(staticData.Callsign != null) query += string.Format(", callsign='{0}'", staticData.Callsign); + if(staticData.Name != null) query += string.Format(", name='{0}'", staticData.Name); + if(staticData.VendorId != null) query += string.Format(", typeofdevice='{0}'", staticData.VendorId); + if(staticData.ShipType != null) query += string.Format(", shipdescription='{0}'", staticData.ShipType); + query += string.Format(" WHERE id={0}", id); + + con.ExecuteNonQuery(query); + } + else // Insert + { + query = string.Format("INSERT INTO aisstaticdata SET callsign='{0}', name='{1}', shiptype={2}, typeofdevice='{3}', shipdescription='{4}', stationid={5}, mmsi={6}, classb=1", + staticData.Callsign, + staticData.Name, + staticData.ShipTypeVal, + staticData.VendorId, + staticData.ShipType, + aisStation.Id, + staticData.MMSI + ); + + con.ExecuteNonQuery(query); + id = Convert.ToInt32(con.ExecuteScalar("SELECT LAST_INSERT_ID()")); + } + } + + #endregion + + return id; + } + + #endregion + + /// + /// Loads shipname for display (until static data has been received) + /// + public static string LoadName(int mmsi, DBConnector con) + { + string query = string.Format("SELECT name FROM aisstaticdata where mmsi={0}", mmsi); + string result = con.ExecuteScalar(query) as string; + if (result == null) result = ""; + return result; + } + + /// + /// Loads callsign for display (until static data has been received) + /// + public static string LoadCallsign(int mmsi, DBConnector con) + { + string query = string.Format("SELECT callsign FROM aisstaticdata where mmsi={0}", mmsi); + string result = con.ExecuteScalar(query) as string; + if (result == null) result = ""; + return result; + } + + /// + /// preload target with data from database until static data has been received + /// + /// target to load + public static void PreloadTarget(AIS_Target target, DBConnector con) + { + if (target.MMSI == 0) return; + string query = string.Format("SELECT name, callsign, shiptype FROM aisstaticdata where mmsi={0}", target.MMSI); + IDataReader reader = con.ExecuteQuery(query); + if (reader.Read()) + { + if (!reader.IsDBNull(0)) target.LastDBName = reader.GetString(0); + if (!reader.IsDBNull(1)) target.Callsign = reader.GetString(1); + if (!reader.IsDBNull(2)) + target.TargetType = AIS_StaticData.GetShipTypeSimple(reader.GetInt32(2)); + } + reader.Close(); + } + + /// + /// Load all ships that have a position and static data from database + /// + public static List LoadDBShips(DBConnector con) + { + List result = new List(); + IDataReader reader = con.ExecuteQuery(AISStaticData.LoadDBShipsQuery); + while (reader.Read()) + { + AISStaticData ship = new AISStaticData(); + ship.mmsi = reader.GetInt32(0); + ship.lastposition = reader.GetDateTime(1); + ship.lastLatitude = (double) Math.Round(reader.GetInt32(2) / 600000.0, 4); + ship.lastLongitude = (double) Math.Round(reader.GetInt32(3) / 600000.0, 4); + ship.stationid = reader.GetInt32(4); + ship.cog = reader.GetInt32(5); + ship.heading = reader.GetInt32(6); + ship.navstatus = reader.GetInt32(7); + if(!reader.IsDBNull(8)) + ship.callsign = reader.GetString(8); + if(!reader.IsDBNull(9)) + ship.name = reader.GetString(9); + ship.shiptype = reader.GetInt32(10); + if (reader.IsDBNull(11)) ship.isClassB = false; + else ship.isClassB = reader.GetBoolean(11); + ship.shipdescription = reader.GetString(12); + result.Add(ship); + } + reader.Close(); + Trace.WriteLine(string.Format("AISStaticData: {0} ships loaded from DB", result.Count)); + return result; + } + + #endregion + + } + + #region Comparer Class for grid + + public class AISStaticData_Comparer : IComparer + { + private SortPropertyEnum sortProperty = SortPropertyEnum.NAME; + private ListSortDirection sortDirection = ListSortDirection.Ascending; + + public enum SortPropertyEnum + { + NONE, + MMSI, + NAME, + CALLSIGN, + LASTUPDATE, + DESCRIPTION + } + + #region Properties + + public ListSortDirection SortDirection + { + get { return this.sortDirection; } + set { this.sortDirection = value; } + } + + public SortPropertyEnum SortProperty + { + get { return this.sortProperty; } + set { this.sortProperty = value; } + } + + #endregion + + public int Compare(AISStaticData x, AISStaticData y) + { + switch (this.sortProperty) + { + case SortPropertyEnum.NONE: + return 0; + case SortPropertyEnum.NAME: + { + string xName = x.Name; + if (xName == null) xName = ""; + string yName = y.Name; + if (yName == null) yName = ""; + if (this.sortDirection == ListSortDirection.Ascending) + return xName.CompareTo(yName); + else + return yName.CompareTo(xName); + } + case SortPropertyEnum.LASTUPDATE: + { + DateTime xTime = x.LastPositionReport ?? DateTime.MinValue; + DateTime yTime = y.LastPositionReport ?? DateTime.MinValue; + if (this.sortDirection == ListSortDirection.Ascending) + return xTime.CompareTo(yTime); + else + return yTime.CompareTo(xTime); + } + case SortPropertyEnum.MMSI: + { + if (this.sortDirection == ListSortDirection.Ascending) + return x.MMSI.CompareTo(y.MMSI); + else + return y.MMSI.CompareTo(x.MMSI); + } + case SortPropertyEnum.CALLSIGN: + { + string xCallsign = x.Callsign; + if (xCallsign == null) xCallsign = ""; + string yCallsign = y.Callsign; + if (yCallsign == null) yCallsign = ""; + if (this.sortDirection == ListSortDirection.Ascending) + return xCallsign.CompareTo(yCallsign); + else + return yCallsign.CompareTo(xCallsign); + } + case SortPropertyEnum.DESCRIPTION: + { + string xDescription = x.Description; + if (xDescription == null) xDescription = ""; + string yDescription = y.Description; + if (yDescription == null) yDescription = ""; + if (this.sortDirection == ListSortDirection.Ascending) + return xDescription.CompareTo(yDescription); + else + return yDescription.CompareTo(xDescription); + } + default: + return 0; + } + + + + } + + #endregion + } +} diff --git a/AIS/bsmd.AISService/DB/AISStation.cs b/AIS/bsmd.AISService/DB/AISStation.cs new file mode 100644 index 00000000..b3d159ea --- /dev/null +++ b/AIS/bsmd.AISService/DB/AISStation.cs @@ -0,0 +1,258 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Data; +using System.Globalization; + +using bsmd.AISService.AIS; + +namespace bsmd.AISService.DB +{ + + public class AISStation + { + #region private members + + private int station_Id; + private string name; + private bool active; + private string comport; + private int baudrate; + private int telnetPort; + private string telnetHost; + private bool onAir = false; + private double rangeMax = 0; + private double rangeAverage; + private double coverage; + private double latitude; + private double longitude; + private string address; + private DateTime? lastPosTimestamp; + private bool isDirty = false; + private Dictionary targets = new Dictionary(); + + #endregion + + #region Properties + + public string Name { get { return this.name; } set { this.name = value; } } + public int Id { get { return this.station_Id; } } + public bool Active { get { return this.active; } set { this.active = value; } } + public string COMPort { get { return this.comport; } set { this.comport = value; } } + public int Baudrate { get { return this.baudrate; } set { this.baudrate = value; } } + public string TelnetHost { get { return this.telnetHost; } set { this.telnetHost = value; } } + public int TelnetPort { get { return this.telnetPort; } set { this.telnetPort = value; } } + public bool OnAir { get { return this.onAir; } set { this.onAir = value; } } + public double RangeMax { get { return this.rangeMax; } } + public double RangeAverage { get { return this.rangeAverage; } } + public double Coverage { get { return this.coverage; } } + public string CoverageText { get { return string.Format("{0} qkm", this.coverage.ToString("N2")); } } + public double Latitude + { + get { return this.latitude; } + set + { + this.latitude = value; + this.isDirty = true; + this.rangeMax = 0; + } + } + public double Longitude + { + get { return this.longitude; } + set + { + this.longitude = value; + this.isDirty = true; + this.rangeMax = 0; + } + } + public string Address { get { return this.address; } } + public bool IsDirty { get { return this.isDirty; } } + + public DateTime? LastPosTimestamp + { + get { return this.lastPosTimestamp; } + set + { + this.lastPosTimestamp = value; + } + } + + public int NumTargets + { + get { return this.targets.Count; } + } + + public Dictionary Targets { get { return this.targets; } } + + public bool MustDelete { get; set; } + + #endregion + + #region public methods + + public bool Save(DBConnector con) + { + string query = string.Format("UPDATE aisstation SET lat={0}, lon={1}, telnetHost='{2}', telnetPort={3}, comPort='{4}', name='{5}', baudrate={6} WHERE id={7}", + (int) (this.latitude * 600000), + (int) (this.longitude * 600000), + this.telnetHost, + this.telnetPort, + this.comport, + this.name, + this.baudrate, + this.station_Id); + + if (con.ExecuteNonQuery(query) == 1) + { + this.isDirty = false; + return true; + } + return false; + } + + public void UpdateWithPositionReport(int mmsi, double lat, double lon, DateTime timestamp) + { + + double distance = bsmd.AISService.AIS.AIS.GetDistance(this.Latitude, this.Longitude, lat, lon); + if (distance > this.rangeMax) + { + this.rangeMax = distance; + this.coverage = Math.PI * this.rangeMax * this.rangeMax; + } + + lock (this.Targets) + { + if (!this.targets.ContainsKey(mmsi)) + this.targets.Add(mmsi, distance); + else this.targets[mmsi] = distance; + } + + // durchschnittl. Reichweite + double sumRange = 0; + foreach (int key in this.targets.Keys) + sumRange += this.targets[mmsi]; + this.rangeAverage = sumRange / (double)this.targets.Count; + + if (!this.lastPosTimestamp.HasValue) this.lastPosTimestamp = timestamp; + else + { + if (this.lastPosTimestamp.Value < timestamp) + this.lastPosTimestamp = timestamp; + } + + } + + /// + /// clear targets and reset coverage + /// + public void ResetStation() + { + this.targets.Clear(); + this.coverage = 0; + this.rangeAverage = 0; + this.rangeMax = 0; + } + + /// + /// deletes this station + /// + public void Delete(DBConnector con) + { + string query = string.Format("DELETE FROM aisstation WHERE id={0}", this.Id); + con.ExecuteNonQuery(query); + } + + #endregion + + #region static methods + + public static List LoadStations(DBConnector con) + { + List result = new List(); + string query = "SELECT id, name, active, lat, lon, address, telnetHost, telnetPort, comPort, baudrate FROM aisstation"; + IDataReader reader = con.ExecuteQuery(query); + if (reader == null) return result; + + while (reader.Read()) + { + AISStation station = new AISStation(); + station.station_Id = reader.GetInt32(0); + station.name = reader.GetString(1); + station.active = reader.GetBoolean(2); + station.latitude = (double) reader.GetInt32(3) / 600000; + station.longitude = (double) reader.GetInt32(4) / 600000; + if(!reader.IsDBNull(5)) + station.address = reader.GetString(5); + if (!reader.IsDBNull(6)) + station.telnetHost = reader.GetString(6); + if (!reader.IsDBNull(7)) + station.telnetPort = reader.GetInt32(7); + if (!reader.IsDBNull(8)) + station.comport = reader.GetString(8); + if (!reader.IsDBNull(9)) + station.baudrate = reader.GetInt32(9); + result.Add(station); + } + reader.Close(); + return result; + } + + public static AISStation CreateStation(string name, DBConnector con) + { + AISStation newStation = new AISStation(); + newStation.name = name; + newStation.active = true; + string query = string.Format("INSERT INTO aisstation SET name='{0}',active=1", + name); + con.ExecuteNonQuery(query); + newStation.station_Id = Convert.ToInt32(con.ExecuteScalar("SELECT LAST_INSERT_ID()")); + + return newStation; + } + + public static List CreateSerial_IOs(List stationList) + { + List result = new List(); + foreach (AISStation station in stationList) + { + if ((station.COMPort != null) && (station.COMPort.Length > 0)) + { + Serial_IO serialIO = new Serial_IO(); + serialIO.BaudRate = (station.Baudrate == 0) ? 9600 : station.Baudrate; + serialIO.ComPort = station.COMPort; + serialIO.StationName = station.Name; + result.Add(serialIO); + } + } + return result; + } + + public static List CreateAIS_Telnets(List stationList) + { + List result = new List(); + foreach (AISStation station in stationList) + { + if ((station.TelnetHost != null) && (station.TelnetHost.Length > 0)) + { + try + { + AIS_Telnet telnet = new AIS_Telnet(station.TelnetHost, station.TelnetPort); + telnet.StationName = station.Name; + result.Add(telnet); + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine(string.Format("AIS_Telnet: cannot connect to host {0} port {1}: {2}", + station.TelnetHost ?? "", station.TelnetPort, ex.Message)); + } + } + } + return result; + } + + #endregion + + } +} diff --git a/AIS/bsmd.AISService/DB/AISWatchkeeper.cs b/AIS/bsmd.AISService/DB/AISWatchkeeper.cs new file mode 100644 index 00000000..0732ce9d --- /dev/null +++ b/AIS/bsmd.AISService/DB/AISWatchkeeper.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Data; + +using bsmd.AISService.AIS; + +namespace bsmd.AISService.DB +{ + public class AISWatchkeeper + { + private static string GetAllWatchkeeperShipsQuery = "SELECT mmsi, name, aktiv, watching, tracking FROM wk_ship"; + private int mmsi; + private string name; + private bool aktiv; + private bool watching; + private bool tracking; + + public int MMSI + { + get { return this.mmsi; } + } + + public bool Aktiv + { + get { return this.aktiv; } + } + + public static List GetWatchkeeperShips(DBConnector con) + { + List result = new List(); + IDataReader reader = con.ExecuteQuery(AISWatchkeeper.GetAllWatchkeeperShipsQuery); + if (reader == null) return result; + while (reader.Read()) + { + AISWatchkeeper wkShip = new AISWatchkeeper(); + wkShip.mmsi = reader.GetInt32(0); + wkShip.name = reader.GetString(1); + wkShip.aktiv = reader.GetBoolean(2); + wkShip.watching = reader.GetBoolean(3); + wkShip.tracking = reader.GetBoolean(4); + result.Add(wkShip); + } + reader.Close(); + return result; + } + + + } +} diff --git a/AIS/bsmd.AISService/DB/DBConnector.cs b/AIS/bsmd.AISService/DB/DBConnector.cs new file mode 100644 index 00000000..d0faf89e --- /dev/null +++ b/AIS/bsmd.AISService/DB/DBConnector.cs @@ -0,0 +1,212 @@ +// Copyright (c) 2008-2018 schick Informatik +// Description: Database connector +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Data; +using System.Data.SqlClient; + +using bsmd.AISService.AIS; + +namespace bsmd.AISService.DB +{ + + public class DBConnector + { + private string connectionString; + private SqlConnection dbCon = null; + private List watchkeeperShips = null; + private List dbShips = null; + private Dictionary updateStations = null; + + public DBConnector() { } + + #region Properties + + public string ConnectionString + { + get { return this.connectionString; } + set { this.connectionString = value; } + } + + public List DBShips + { + get + { + if (this.dbShips == null) + { + lock (this.dbCon) + { + this.dbShips = AISStaticData.LoadDBShips(this); + } + } + return this.dbShips; + } + set { this.dbShips = value; } + } + + public List WatchkeeperShips + { + get + { + if (this.watchkeeperShips == null) this.watchkeeperShips = AISWatchkeeper.GetWatchkeeperShips(this); + return this.watchkeeperShips; + } + } + + #endregion + + #region public methods + + public SqlDataReader ExecuteQuery(string query) + { + if (!this.CheckConnection()) return null; + + SqlCommand cmd = new SqlCommand(query, this.dbCon); + return cmd.ExecuteReader(); + } + + public int ExecuteNonQuery(string query) + { + if (!this.CheckConnection()) return 0; + SqlCommand cmd = new SqlCommand(query, this.dbCon); + return cmd.ExecuteNonQuery(); + } + + public object ExecuteScalar(string query) + { + if (!this.CheckConnection()) return 0; + SqlCommand cmd = new SqlCommand(query, this.dbCon); + return cmd.ExecuteScalar(); + } + + public bool Open() + { + if (this.dbCon != null && this.dbCon.State == System.Data.ConnectionState.Open) return true; + try + { + this.dbCon = new SqlConnection(this.connectionString); + this.dbCon.Open(); + if (this.dbCon.State == System.Data.ConnectionState.Open) + return true; + } + catch (SqlException anException) + { + Trace.WriteLine(string.Format("cannot open SQL DB connection: {0}", anException.Message)); + } + return false; + } + + public void Close() + { + try + { + if (this.dbCon != null && this.dbCon.State == System.Data.ConnectionState.Open) + this.dbCon.Close(); + } + catch (Exception) { } // egal + } + + public void Update(AIS_Target target) + { + if (this.dbCon.State != System.Data.ConnectionState.Open) // reopen + { + this.dbCon.Close(); + this.dbCon.Open(); + } + + if (this.updateStations == null) + { + this.updateStations = new Dictionary(); + Trace.WriteLine("loading stations.."); + List stations = AISStation.LoadStations(this); + Trace.WriteLine(string.Format("{0} stations loaded", stations.Count)); + foreach (AISStation station in stations) + if (!updateStations.ContainsKey(station.Name)) + updateStations.Add(station.Name, station); + } + + if (target.LastPosReport != null) + { + Hotposition hotposition = Hotposition.LoadForMMSI(target.MMSI, this); + int? pid = AISPosReport.Save(target, this, updateStations.ContainsKey(target.Station) ? updateStations[target.Station] : null); + if (pid.HasValue) + { + hotposition.PosReportId = pid.Value; + hotposition.Save(this); + } + } + else + { + Trace.WriteLine(string.Format("last pos report is null for target {0}", target.MMSI)); + } + if (target.LastStaticData != null) + { + AISStaticData.Save(target, this, updateStations.ContainsKey(target.Station) ? updateStations[target.Station] : null); + } + if ((target.Name == null || target.LastDBName == null) && (target.MMSI > 0)) + { + // preload values from DB + AISStaticData.PreloadTarget(target, this); + } + + target.UpdateDB = false; // reset update flag + // Watchkeeper check + + if (this.watchkeeperShips == null) + this.watchkeeperShips = AISWatchkeeper.GetWatchkeeperShips(this); + + + if (!target.IsWatchkeeperShip.HasValue && this.watchkeeperShips != null) + { + for (int i = 0; i < this.watchkeeperShips.Count; i++) + { + if (this.watchkeeperShips[i].MMSI == target.MMSI) // found it + target.IsWatchkeeperShip = true; + } + + if (!target.IsWatchkeeperShip.HasValue) // didn't find it + target.IsWatchkeeperShip = false; + } + + + } + + public void ResetWatchkeeperList() + { + this.watchkeeperShips = null; + } + + public void SaveStation(AISStation station) + { + station.Save(this); + } + + #endregion + + #region private methods + + private bool CheckConnection() + { + // if connection has been closed, re-open the connection + if (this.dbCon.State != System.Data.ConnectionState.Open) + { + try + { + this.dbCon.Close(); + this.Open(); + } + catch (SqlException ex) + { + System.Diagnostics.Trace.WriteLine(ex.ToString()); + } + } + return this.dbCon.State == System.Data.ConnectionState.Open; + } + + #endregion + + } +} diff --git a/AIS/bsmd.AISService/DB/Hotposition.cs b/AIS/bsmd.AISService/DB/Hotposition.cs new file mode 100644 index 00000000..25d85d7c --- /dev/null +++ b/AIS/bsmd.AISService/DB/Hotposition.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Data; +using System.Data.SqlClient; +using bsmd.AISService.AIS; + +namespace bsmd.AISService.DB +{ + internal class Hotposition + { + private int id; + private int mmsi; + private int pid; + + public int MMSI + { + get { return this.mmsi; } + } + + public int PosReportId + { + get { return this.pid; } + set { this.pid = value; } + } + + public void Save(DBConnector con) + { + string query = string.Format("UPDATE hotposition SET mmsi={0}, pid={1} WHERE id={2}", + this.mmsi, this.pid, this.id); + con.ExecuteNonQuery(query); + } + + public static Hotposition LoadForMMSI(int mmsi, DBConnector con) + { + List results = new List(); + string query = string.Format("SELECT id, pid FROM hotposition WHERE mmsi={0}", mmsi); + SqlDataReader reader = con.ExecuteQuery(query); + if (reader != null) + { + while (reader.Read()) + { + Hotposition hp = new Hotposition(); + hp.id = reader.GetInt32(0); + hp.mmsi = mmsi; + if (!reader.IsDBNull(1)) + hp.pid = reader.GetInt32(1); + results.Add(hp); + } + reader.Close(); + } + + if (results.Count == 0) + { + // neuen Eintrag erzeugen + Hotposition hp = new Hotposition(); + string insertQuery = string.Format("INSERT INTO hotposition SET mmsi={0}", mmsi); + con.ExecuteNonQuery(insertQuery); + + object ob = con.ExecuteScalar("SELECT LAST_INSERT_ID()"); + hp.id = Convert.ToInt32(ob); + hp.mmsi = mmsi; + return hp; + } + else if (results.Count == 1) + { + return results[0]; + } + else + { + // überschüssige HP's löschen (jeweils nur eins pro MMSI) + for (int i = 1; i < results.Count; i++) + { + string delQuery = string.Format("DELETE FROM hotposition WHERE id={0}", results[i].id); + con.ExecuteNonQuery(delQuery); + } + + return results[0]; + } + } + + } +} diff --git a/AIS/bsmd.AISService/Program.cs b/AIS/bsmd.AISService/Program.cs new file mode 100644 index 00000000..28f96537 --- /dev/null +++ b/AIS/bsmd.AISService/Program.cs @@ -0,0 +1,127 @@ +// Copyright (c) 2008-2018 schick Informatik +// Description: +// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Configuration.Install; +using System.Linq; +using System.Reflection; +using System.ServiceProcess; +using System.Text; +using System.Threading.Tasks; + +namespace bsmd.AISService +{ + static class Program + { + /// + /// The main entry point for the application. + /// + public static int Main(string[] args) + { + if (Environment.UserInteractive) + { + if (args.Length > 0) + { + string arg = args[0].ToLowerInvariant().Substring(0, 2); + switch (arg) + { + case "/i": // install + return InstallService(); + + case "/u": // uninstall + return UninstallService(); + + default: // unknown option + Console.WriteLine("Argument not recognized: {0}", args[0]); + Console.WriteLine(string.Empty); + DisplayUsage(); + return 1; + } + } + else + { + DisplayUsage(); + } + } + else + { + ServiceBase[] ServicesToRun; + ServicesToRun = new ServiceBase[] + { + new AISService() + }; + ServiceBase.Run(ServicesToRun); + } + return 0; + } + + private static void DisplayUsage() + { + //.. + } + + private static int InstallService() + { + var service = new AISService(); + + try + { + // perform specific install steps for our queue service. + //service.InstallService(); + + // install the service with the Windows Service Control Manager (SCM) + ManagedInstallerClass.InstallHelper(new string[] { Assembly.GetExecutingAssembly().Location }); + } + catch (Exception ex) + { + if (ex.InnerException != null && ex.InnerException.GetType() == typeof(Win32Exception)) + { + Win32Exception wex = (Win32Exception)ex.InnerException; + Console.WriteLine("Error(0x{0:X}): Service already installed!", wex.ErrorCode); + return wex.ErrorCode; + } + else + { + Console.WriteLine(ex.ToString()); + return -1; + } + } + + return 0; + } + + private static int UninstallService() + { + var service = new AISService(); + + try + { + // perform specific uninstall steps for our queue service + //service.UninstallService(); + + // uninstall the service from the Windows Service Control Manager (SCM) + ManagedInstallerClass.InstallHelper(new string[] { "/u", Assembly.GetExecutingAssembly().Location }); + } + catch (Exception ex) + { + if (ex.InnerException.GetType() == typeof(Win32Exception)) + { + Win32Exception wex = (Win32Exception)ex.InnerException; + Console.WriteLine("Error(0x{0:X}): Service not installed!", wex.ErrorCode); + return wex.ErrorCode; + } + else + { + Console.WriteLine(ex.ToString()); + return -1; + } + } + + return 0; + } + + } +} diff --git a/AIS/bsmd.AISService/Properties/AssemblyInfo.cs b/AIS/bsmd.AISService/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..27dbd0ff --- /dev/null +++ b/AIS/bsmd.AISService/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("bsmd.AISService")] +[assembly: AssemblyDescription("Windows Service zum Einlesen von AIS Daten aus einem TCP/IP Datenstrom")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("schick Informatik")] +[assembly: AssemblyProduct("bsmd.AISService")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("a26a6de3-8505-4ec2-9eb5-12e5ebe83b11")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AIS/bsmd.AISService/Properties/Settings.Designer.cs b/AIS/bsmd.AISService/Properties/Settings.Designer.cs new file mode 100644 index 00000000..d3648523 --- /dev/null +++ b/AIS/bsmd.AISService/Properties/Settings.Designer.cs @@ -0,0 +1,35 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace bsmd.AISService.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string ConnectionString { + get { + return ((string)(this["ConnectionString"])); + } + } + } +} diff --git a/AIS/bsmd.AISService/Properties/Settings.settings b/AIS/bsmd.AISService/Properties/Settings.settings new file mode 100644 index 00000000..ef60b87c --- /dev/null +++ b/AIS/bsmd.AISService/Properties/Settings.settings @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/AIS/bsmd.AISService/bsmd.AISService.csproj b/AIS/bsmd.AISService/bsmd.AISService.csproj new file mode 100644 index 00000000..158fffcc --- /dev/null +++ b/AIS/bsmd.AISService/bsmd.AISService.csproj @@ -0,0 +1,126 @@ + + + + + Debug + AnyCPU + {A26A6DE3-8505-4EC2-9EB5-12E5EBE83B11} + WinExe + Properties + bsmd.AISService + bsmd.AISService + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + + + bsmdKey.snk + + + + packages\log4net.2.0.8\lib\net45-full\log4net.dll + True + + + + + + + + + + + + + + + + Component + + + AISService.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + Component + + + ProjectInstaller.cs + + + + True + True + Settings.settings + + + + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + ProjectInstaller.cs + + + + + \ No newline at end of file diff --git a/AIS/bsmd.AISService/bsmd.AISService.licenseheader b/AIS/bsmd.AISService/bsmd.AISService.licenseheader new file mode 100644 index 00000000..fa18f4f0 --- /dev/null +++ b/AIS/bsmd.AISService/bsmd.AISService.licenseheader @@ -0,0 +1,16 @@ +extensions: designer.cs generated.cs +extensions: .cs .cpp .h +// Copyright (c) 2008-2018 schick Informatik +// Description: +// + +extensions: .aspx .ascx +<%-- +Copyright (c) 2008-2018 schick Informatik +--%> +extensions: .vb +'Sample license text. +extensions: .xml .config .xsd + \ No newline at end of file diff --git a/AIS/bsmd.AISService/bsmd.AISService.sln b/AIS/bsmd.AISService/bsmd.AISService.sln new file mode 100644 index 00000000..4b965867 --- /dev/null +++ b/AIS/bsmd.AISService/bsmd.AISService.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "bsmd.AISService", "bsmd.AISService.csproj", "{A26A6DE3-8505-4EC2-9EB5-12E5EBE83B11}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A26A6DE3-8505-4EC2-9EB5-12E5EBE83B11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A26A6DE3-8505-4EC2-9EB5-12E5EBE83B11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A26A6DE3-8505-4EC2-9EB5-12E5EBE83B11}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A26A6DE3-8505-4EC2-9EB5-12E5EBE83B11}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AIS/bsmd.AISService/bsmdKey.snk b/AIS/bsmd.AISService/bsmdKey.snk new file mode 100644 index 00000000..fd20ba24 Binary files /dev/null and b/AIS/bsmd.AISService/bsmdKey.snk differ diff --git a/AIS/bsmd.AISService/packages.config b/AIS/bsmd.AISService/packages.config new file mode 100644 index 00000000..2b3696ab --- /dev/null +++ b/AIS/bsmd.AISService/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ENI-2/ENI2/ENI2/App.config b/ENI-2/ENI2/ENI2/App.config index 097725ac..2233c98e 100644 --- a/ENI-2/ENI2/ENI2/App.config +++ b/ENI-2/ENI2/ENI2/App.config @@ -26,12 +26,12 @@ 1000 - http://192.168.2.4/LockingService/LockingService.svc - + + http://heupferd/bsmd.LockingService/LockingService.svc - Data Source=192.168.2.12;Initial Catalog=nsw;Uid=dfuser;Pwd=dfpasswd;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False - + + Data Source=(localdb)\Projects;Initial Catalog=nsw;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False diff --git a/Stundensheet.xlsx b/Stundensheet.xlsx index ae7317bb..fc3530a8 100644 Binary files a/Stundensheet.xlsx and b/Stundensheet.xlsx differ diff --git a/nsw/Source/bsmd.ExcelReadService/ExcelReader.cs b/nsw/Source/bsmd.ExcelReadService/ExcelReader.cs index 959f44e1..b33d3df0 100644 --- a/nsw/Source/bsmd.ExcelReadService/ExcelReader.cs +++ b/nsw/Source/bsmd.ExcelReadService/ExcelReader.cs @@ -310,8 +310,8 @@ namespace bsmd.ExcelReadService if (val.Length == 2) { - this.Conf.ConfirmText(lookup, val, ReadState.OK); val = val.ToUpper(); + this.Conf.ConfirmText(lookup, val, ReadState.OK); } } return val; diff --git a/nsw/Source/bsmd.database/INFO.cs b/nsw/Source/bsmd.database/INFO.cs index 694e2de7..57140b88 100644 --- a/nsw/Source/bsmd.database/INFO.cs +++ b/nsw/Source/bsmd.database/INFO.cs @@ -196,7 +196,7 @@ namespace bsmd.database public override void Validate(List errors, List violations) { - if ((PortArea.Length >= 2) && (PortArea.Length <= 4)) + if ((PortArea != null) && (PortArea.Length >= 2) && (PortArea.Length <= 4)) { if ((RuleEngine.PortAreaChecker != null) && (this.MessageCore != null)) if (!RuleEngine.PortAreaChecker(this.MessageCore.PoC, this.PortArea)) @@ -204,7 +204,8 @@ namespace bsmd.database } else { - errors.Add(RuleEngine.CreateError(ValidationCode.PORTAREA, "PortArea", this.PortArea, "INFO", "", this.Tablename)); + if(this.MessageCore.PoC != "DEHAM") + errors.Add(RuleEngine.CreateError(ValidationCode.PORTAREA, "PortArea", this.PortArea ?? "", "INFO", "", this.Tablename)); } }