using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Collections; using System.Windows.Forms; namespace ArcMapNav { /// /// Control for connecting to UDP datagrams to get ship and ROV position depth and headings. /// public sealed class NavViaSocket { private static byte[] messageSent = new byte[1024]; private static byte[] dataReturned = new byte[1024]; private static IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0); private static EndPoint Remote = (EndPoint)sender; private static int size = 30; private Socket m_server; private static IPEndPoint m_ipep; private static string m_headerString; PositionSingleton posInstance = PositionSingleton.Instance; private string m_hostName; private int m_socketNum; /// /// Get Navigation data thru UDP socket connection /// /// public NavViaSocket(string connectionStr) { try { string[] parts = connectionStr.Split(new char[] { ':' }, 2); m_hostName = parts[0]; m_socketNum = Convert.ToInt32(parts[1]); } catch { string msg = "Invalid connection string: " + connectionStr; System.Diagnostics.Trace.TraceError(msg); MessageBox.Show(msg, "Error"); } // Initial position for debugging //posInstance.RovLat = 36.80216; //posInstance.RovLon = -121.787198; //posInstance.RovHeading = 293; } /// /// Connect to UPD server and begin getting data /// public void connect() { try { string dataString; int recv = 0; IPHostEntry iphe = null; try { // Try first assuming m_hostName is an IP address string m_ipep = new IPEndPoint(IPAddress.Parse(m_hostName), m_socketNum); } catch (FormatException) { iphe = Dns.GetHostEntry(m_hostName); m_ipep = new IPEndPoint(iphe.AddressList[0], m_socketNum); } m_server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); int sockopt = (int)m_server.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout); m_server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 3000); sockopt = (int)m_server.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout); //Send "HEADER" until we get back header string, DateTime startTime = DateTime.Now; TimeSpan ts; int timeOutPeriod = 10; // time to wait in seconds do { messageSent = Encoding.ASCII.GetBytes("HEADER"); recv = AdvSndRcvData(m_server, messageSent, m_ipep); ts = DateTime.Now - startTime; } while (recv == 0 && ts.Seconds < timeOutPeriod); // If we don't get a header after this then there is a connectivity problem m_headerString = Encoding.ASCII.GetString(dataReturned, 0, recv); if ("".Equals(m_headerString) || m_headerString.StartsWith("\0")) { string msg = "No response from " + m_hostName + ":" + m_socketNum + ". Is there a network problem?"; System.Diagnostics.Trace.TraceError(msg); MessageBox.Show(msg, "Error"); disconnect(); } else if (m_server != null) { // Send anything else (in this case "DATA") to get a data record while (true) { messageSent = Encoding.ASCII.GetBytes("DATA"); recv = AdvSndRcvData(m_server, dataReturned, m_ipep); if (recv > 0) { dataString = Encoding.ASCII.GetString(dataReturned, 0, recv); parseData(m_headerString, dataString); } Thread.Sleep(Properties.Settings.Default.updateRate * 1000); } } } catch (System.Net.Sockets.SocketException e) { // This needs to be a Singleton or have some other mechanism to not generate so many pupups should the network go away string msg = "Cannot connect to host " + m_hostName + " (" + e.Message + ")"; System.Diagnostics.Trace.TraceError(msg); MessageBox.Show(msg, "Error"); disconnect(); } catch (System.Threading.ThreadAbortException e) { //System.Windows.Forms.MessageBox.Show("Warning: switching data feed connection to the " + Properties.Settings.Default.selectedShip); string msg = "NavViaSocket and DataFeedThread aborted for host = " + m_hostName + " (" + e.Message + ")"; System.Diagnostics.Trace.TraceError(msg); } catch (ArgumentOutOfRangeException e) { string msg = "ArgumentOutOfRangeException for host = " + m_hostName + " (" + e.Message + ")"; System.Diagnostics.Trace.TraceError(msg); MessageBox.Show(msg + "\n\n" + e.StackTrace, "Error"); disconnect(); } catch (NullReferenceException e) { string msg = "NullReferenceException for host = " + m_hostName + " (" + e.Message + ")"; System.Diagnostics.Trace.TraceError(msg); MessageBox.Show(msg + "\n\n" + e.StackTrace, "Error"); disconnect(); } catch (IndexOutOfRangeException e) { string msg = "IndexOutOfRangeException for host = " + m_hostName + " (" + e.Message + ")"; System.Diagnostics.Trace.TraceError(msg); MessageBox.Show(msg + "\n\n" + e.StackTrace, "Error"); disconnect(); } } // End connect() /// /// To call before closing window /// public void disconnect() { try { if (m_server != null) m_server.Close(); } catch (Exception e) { string msg = "Cannot connect to host " + m_hostName + " (" + e.Message + ")"; System.Diagnostics.Trace.TraceError(msg); MessageBox.Show(msg, "Error"); disconnect(); } } #region private methods private static int AdvSndRcvData(Socket s, byte[] message, EndPoint rmtdevice) { int recv = 0; int retry = 0; while (true) { try { s.SendTo(message, message.Length, SocketFlags.None, rmtdevice); dataReturned = new byte[size]; recv = s.ReceiveFrom(dataReturned, ref Remote); } catch (SocketException e) { if (e.ErrorCode == 10054) recv = 0; else if (e.ErrorCode == 10040) { size += 10; recv = 0; } } catch (ObjectDisposedException e) { if (!s.Connected) { return -1; } System.Windows.Forms.MessageBox.Show("AdvSndRcvData() Exception caught: " + e.ToString(), "Error"); } if (recv > 0) { return recv; } else { retry++; if (retry > 4) { return 0; } } } } // End AdvSndRcvData() private void parseData(String headerStr, String dataStr) { // str = "1194626433,1194630032,0.10, 0.5,36.802155,-121.787207,1,NO_ITEM,0.9,1194626441,292.8,NO_ITEM,NO_ITEM,NO_ITEM,1194570065,36.746427,-122.048790,-0.240000,23.209999,0.374000,0.540000,-1.138000,2007,313,08:40:33,+0" // navlogr1167782400,1167785999,0.13, 0.3,36.802152,-121.787205,1,NO_PROV,1.0,1167782400,291.0,NO_ITEM,NO_ITEM,NO_ITEM,1167782400,36.710313,-122.184227,-0.200000,45.020000,1.454000,-0.228000,-1.138000,2007,002,16:00:00,+0 /* * 0. #LOG LOGHOST.SYSTEM.UTC %d AUTO 1. #LOG PT_LOBOS.GPS.TIME %d SLAVE 2. #LOG VENTANA.PRESSURE %4.2f SLAVE 3. #LOG VENTANA.SENSOR.PRESSURE.DBAR %5.1lf SLAVE 4. #LOG PT_LOBOS.GPS.LATITUDE %3.6lf SLAVE 5. #LOG PT_LOBOS.GPS.LONGITUDE %4.6lf SLAVE 6. #LOG PT_LOBOS.GPS.DIFFERENTIAL %d SLAVE 7. #LOG PT_LOBOS.GPS.HORIZONTAL_VELOCITY %lf SLAVE 8. #LOG PT_LOBOS.GPS.HDOP %3.1f SLAVE 9. #LOG PT_LOBOS.GYRO.DEGREES.UTC %d AUTO 10. #LOG PT_LOBOS.GYRO.DEGREES %3.1f SLAVE 11. #LOG PT_LOBOS.WIND.TRUE_SPEED.UTC %d AUTO 12. #LOG PT_LOBOS.WIND.TRUE_SPEED %lf SLAVE 13. #LOG PT_LOBOS.WIND.TRUE_DIRECTION %lf SLAVE 14. #LOG VENTANA.LATITUDE.UTC %d AUTO 15. #LOG VENTANA.LATITUDE %lf SLAVE 16. #LOG VENTANA.LONGITUDE %lf SLAVE 17. #LOG VENTANA.DEPTH %lf SLAVE 18. #LOG VENTANA.HEADING %lf SLAVE 19. #LOG VENTANA.PITCH %lf SLAVE 20. #LOG VENTANA.ROLL %lf SLAVE 21. #LOG VENTANA.ALTITUDE %lf SLAVE 22. #LOG LOGHOST.SYSTEM.CLOCKTIME.YEAR %d AUTO 23. #LOG LOGHOST.SYSTEM.CLOCKTIME.YEARDAY %d AUTO 24. #LOG LOGHOST.SYSTEM.CLOCKTIME.TIMESTRING %2d:%2d:%2d AUTO 25. #LOG LOGHOST.SYSTEM.CLOCKTIME.TIMEZONE %+d AUTO * * 0. #LOG LOGHOST.SYSTEM.UTC %d AUTO 1. #LOG WESTERN_FLYER.GPS.TIME %d SLAVE 2. #LOG TIBURON.VEHICLE.IN_WATER %1d SLAVE 3. #LOG WESTERN_FLYER.GPS.LATITUDE %3.6lf SLAVE 4. #LOG WESTERN_FLYER.GPS.LONGITUDE %4.6lf SLAVE 5. #LOG WESTERN_FLYER.GPS.DIFFERENTIAL %d SLAVE 6. #LOG WESTERN_FLYER.GPS.QUALITY %d SLAVE 7. #LOG WESTERN_FLYER.GPS.HDOP %3.1f SLAVE 8. #LOG WESTERN_FLYER.GYRO.DEGREES.UTC %d AUTO 9. #LOG WESTERN_FLYER.GYRO.DEGREES %3.1f SLAVE 10. #LOG WESTERN_FLYER.WIND.TRUE_SPEED.UTC %d AUTO 11. #LOG WESTERN_FLYER.WIND.TRUE_SPEED %lf SLAVE 12. #LOG WESTERN_FLYER.WIND.TRUE_DIRECTION %lf SLAVE 13. #LOG TIBURON.POSITION.LATITUDE.UTC %d AUTO 14. #LOG TIBURON.POSITION.LATITUDE %lf SLAVE 15. #LOG TIBURON.POSITION.LONGITUDE %lf SLAVE 16. #LOG TIBURON.SENSOR_IBC.PRESSURE %lf SLAVE 17. #LOG TIBURON.SENSOR.FOG.HEADING.DEGREES %lf SLAVE 18. #LOG TIBURON.SENSOR.PITCH.DEGREES %lf SLAVE 19. #LOG TIBURON.SENSOR.ROLL.DEGREES %lf SLAVE 20. #LOG TIBURON.SENSOR.ALTITUDE.METERS %lf SLAVE 21. #LOG LOGHOST.SYSTEM.CLOCKTIME.YEAR %d AUTO 22. #LOG LOGHOST.SYSTEM.CLOCKTIME.YEARDAY %d AUTO 23. #LOG LOGHOST.SYSTEM.CLOCKTIME.TIMESTRING %2d:%2d:%2d AUTO 24. #LOG LOGHOST.SYSTEM.CLOCKTIME.TIMEZONE %+d AUTO */ // Strings delivered by DataView - Headers on both ships now use Ship and ROV, use something else to tell which is which // Case 1 Rachel Carson: //"LOGHOST.SYSTEM.UTC,SHIP.GPS.TIME,ROV.PRESSURE,ROV.CTD.PRESSURE,SHIP.GPS.LAT,SHIP.GPS.LON,SHIP.GPS.DIFFERENTIAL,SHIP.GPS.HORIZONTAL_VELOCITY,SHIP.GPS.HDOP,SHIP.GYRO.DEGREES.UTC,SHIP.GYRO.DEGREES,SHIP.WIND.TRUE_SPEED.UTC,SHIP.WIND.TRUE_SPEED,SHIP.WIND.TRUE_DIRECTION,ROV.POSITION.LAT.UTC,ROV.POSITION.LAT,ROV.POSITION.LON,ROV.MBARI.DEPTH,ROV.HEADING,ROV.PITCH,ROV.ROLL,ROV.ALTITUDE,LOGHOST.SYSTEM.CLOCKTIME.YEAR,LOGHOST.SYSTEM.CLOCKTIME.YEARDAY,LOGHOST.SYSTEM.CLOCKTIME.TIMESTRING,LOGHOST.SYSTEM.CLOCKTIME.TIMEZONE" // (fieldNames[2].StartsWith('ROV.PRESSURE') //string vnta = "ROV"; // The Rachel Carson / Ventana now has "ROV" in the item names. // Case 2 Western Flyer: //string wfly = "WESTERN FLYER"; // "LOGHOST.SYSTEM.UTC,SHIP.GPS.TIME,ROV.CTD.INWATER,SHIP.GPS.LAT,SHIP.GPS.LON,SHIP.GPS.DIFFERENTIAL,SHIP.GPS.QUALITY,SHIP.GPS.HDOP,SHIP.GYRO.DEGREES.UTC,SHIP.GYRO.DEGREES,SHIP.WIND.TRUE_SPEED.UTC,SHIP.WIND.TRUE_SPEED,SHIP.WIND.TRUE_DIRECTION,ROV.POSITION.LAT.UTC,ROV.POSITION.LAT,ROV.POSITION.LON,ROV.DIGIQ.SCALED_PRESSURE,ROV.DVECS.HEADING,ROV.DVECS.PITCH,ROV.DVECS.ROLL,ROV.DVECS.ALTITUDE,ROV.CTD.PRESSURE,ROV.MBARI.DEPTH,ROV.SURVEY.POD_UP,ROV.SURVEY.DIGIQ_UP,ROV.SURVEY.OCTANS_UP,ROV.SURVEY.DVL_UP,ROV.SURVEY.ALTIMETER_UP,LOGHOST.SYSTEM.CLOCKTIME.YEAR,LOGHOST.SYSTEM.CLOCKTIME.YEARDAY,LOGHOST.SYSTEM.CLOCKTIME.TIMESTRING,LOGHOST.SYSTEM.CLOCKTIME.TIMEZONE" string tibr = "TIBURON"; string rov = "ROV"; // The Doc Ricketts field names are not name specific, just 'Ship' and 'ROV' // Case 3: string drifter = "DRIFTER"; // For Gene Massion's special Hawaii ESP app double pressure; string[] lines = dataStr.Split('\n'); string[] fieldNames = headerStr.Split(','); string[] fieldValues = dataStr.Split(','); if (fieldValues.Length > 1) { // Case 1: if (fieldNames[2].EndsWith("PRESSURE")) { // Rachel Carson / Ventana posInstance.ShipName = MBARIEnvironment.RACHELCARSON; posInstance.RovName = MBARIEnvironment.VENTANA; if (fieldValues[1].StartsWith("NO")) // NO_PROV, NO_ITEM, etc. { posInstance.ShipTime = ""; posInstance.ShipDate = ""; } else { DateTime sdt = new DateTime(1970, 1, 1).AddSeconds(double.Parse(fieldValues[0])); // Use System becasue GPS doesn't adjust for PST.89=\ //sdt = sdt.AddMinutes(-10); // Subtract 10 minutes for testing thread restart posInstance.ShipEsecs = double.Parse(fieldValues[0]); posInstance.ShipDateTime = sdt; posInstance.ShipTime = String.Format("{0}", sdt.TimeOfDay); posInstance.ShipDate = String.Format("{0:yyyy-MM-dd}", sdt); posInstance.ShipLat = double.Parse(fieldValues[4]); // PL posInstance.ShipLon = double.Parse(fieldValues[5]); posInstance.ShipHeading = float.Parse(fieldValues[10]); } if (fieldValues[14].StartsWith("NO")) { posInstance.RovTime = ""; posInstance.RovDate = ""; } else { DateTime rdt = new DateTime(1970, 1, 1).AddSeconds(double.Parse(fieldValues[14])); posInstance.RovTime = String.Format("{0}", rdt.TimeOfDay); posInstance.RovDate = String.Format("{0:yyyy-MM-dd}", rdt); } // Ventana posInstance.RovLat = (fieldValues[15].StartsWith("NO")) ? double.NaN : double.Parse(fieldValues[15]); posInstance.RovLon = (fieldValues[16].StartsWith("NO")) ? double.NaN : double.Parse(fieldValues[16]); posInstance.RovHeading = (fieldValues[18].StartsWith("NO")) ? float.NaN : float.Parse(fieldValues[18]); pressure = (fieldValues[17].StartsWith("NO")) ? double.NaN : double.Parse(fieldValues[17]); posInstance.RovDepth = (float)dbarToMeters(pressure, posInstance.ShipLat); } // End Case 1 // Case 2: else if (fieldNames[2].StartsWith(tibr) || fieldNames[2].StartsWith(rov)) { // Western Flyer / Tiburon / Doc Ricketts posInstance.ShipName = MBARIEnvironment.WESTERNFLYER; //posInstance.RovName = MBARIEnvironment.TIBURON; posInstance.RovName = MBARIEnvironment.DOCRICKETTS; if (fieldValues[1].StartsWith("NO")) { posInstance.ShipTime = ""; posInstance.ShipDate = ""; } else { DateTime sdt = new DateTime(1970, 1, 1).AddSeconds(double.Parse(fieldValues[1])); posInstance.ShipEsecs = double.Parse(fieldValues[1]); posInstance.ShipDateTime = sdt; posInstance.ShipTime = String.Format("{0}", sdt.TimeOfDay); posInstance.ShipDate = String.Format("{0:yyyy-MM-dd}", sdt); posInstance.ShipLat = double.Parse(fieldValues[3]); // WF posInstance.ShipLon = double.Parse(fieldValues[4]); posInstance.ShipHeading = float.Parse(fieldValues[9]); } if (fieldValues[14].StartsWith("NO")) { posInstance.RovTime = ""; posInstance.RovDate = ""; } else { DateTime rdt = new DateTime(1970, 1, 1).AddSeconds(double.Parse(fieldValues[13])); posInstance.RovTime = String.Format("{0}", rdt.TimeOfDay); posInstance.RovDate = String.Format("{0:yyyy-MM-dd}", rdt); } // Tiburon or Doc Ricketts posInstance.RovLat = (fieldValues[14].StartsWith("NO")) ? double.NaN : double.Parse(fieldValues[14]); posInstance.RovLon = (fieldValues[15].StartsWith("NO")) ? double.NaN : double.Parse(fieldValues[15]); posInstance.RovHeading = (fieldValues[17].StartsWith("NO")) ? float.NaN : float.Parse(fieldValues[17]); pressure = (fieldValues[16].StartsWith("NO")) ? double.NaN : double.Parse(fieldValues[16]); posInstance.RovDepth = (float)dbarToMeters(pressure, posInstance.ShipLat); if (Properties.Settings.Default.testWithRandomROVPos) { // Generate some random data so that this can be tested with a production install by changing the testWithRandomROVPos value to True in the user.config file Random random = new Random(); posInstance.RovLat = posInstance.RovLat + (random.NextDouble() - 0.5) * 0.005; posInstance.RovLon = posInstance.RovLon + (random.NextDouble() - 0.5) * 0.005; posInstance.RovHeading = (float) random.NextDouble() * 360; } } // End Case 2 // str = "1194626433,1194630032,0.10, 0.5,36.802155,-121.787207,1,NO_ITEM,0.9,1194626441,292.8,NO_ITEM,NO_ITEM,NO_ITEM,1194570065,36.746427,-122.048790,-0.240000,23.209999,0.374000,0.540000,-1.138000,2007,313,08:40:33,+0" // Case 3: else if (fieldNames[5].StartsWith(drifter)) { // 0. body = "LOGHOST.SYSTEM.UTC" // 1. body = body + ",SHIP.GPS.UTC" // 2. body = body + ",SHIP.GPS.LAT" // 3. body = body + ",SHIP.GPS.LON" // 4. body = body + ",SHIP.HEADING.DEGREES" // 5. body = body + ",DRIFTER.POSITION.UTC" // 6. body = body + ",DRIFTER.POSITION.LAT" // 7. body = body + ",DRIFTER.POSITION.LON" // 8. body = body + ",DRIFTER.PRESSURE" // 9. body = body + ",DRIFTER.HEADING" // Western Flyer / Tiburon / Doc Ricketts posInstance.ShipName = MBARIEnvironment.SHIP; posInstance.RovName = MBARIEnvironment.DRIFTER; if (fieldValues[1].StartsWith("NO")) { posInstance.ShipTime = ""; posInstance.ShipDate = ""; } else { DateTime sdt = new DateTime(1970, 1, 1).AddSeconds(double.Parse(fieldValues[1])); posInstance.ShipEsecs = double.Parse(fieldValues[1]); posInstance.ShipDateTime = sdt; posInstance.ShipTime = String.Format("{0}", sdt.TimeOfDay); posInstance.ShipDate = String.Format("{0:yyyy-MM-dd}", sdt); posInstance.ShipLat = double.Parse(fieldValues[2]); // Drifter posInstance.ShipLon = double.Parse(fieldValues[3]); posInstance.ShipHeading = float.Parse(fieldValues[4]); } // Drifter - but we still use the Rov fields... if (fieldValues[5].StartsWith("NO")) { posInstance.RovTime = ""; posInstance.RovDate = ""; } else { DateTime rdt = new DateTime(1970, 1, 1).AddSeconds(double.Parse(fieldValues[5])); posInstance.RovTime = String.Format("{0}", rdt.TimeOfDay); posInstance.RovDate = String.Format("{0:yyyy-MM-dd}", rdt); } posInstance.RovLat = (fieldValues[6].StartsWith("NO")) ? double.NaN : double.Parse(fieldValues[6]); posInstance.RovLon = (fieldValues[7].StartsWith("NO")) ? double.NaN : double.Parse(fieldValues[7]); posInstance.RovHeading = (fieldValues[9].StartsWith("NO")) ? float.NaN : float.Parse(fieldValues[9]); pressure = (fieldValues[8].StartsWith("NO")) ? double.NaN : double.Parse(fieldValues[8]); posInstance.RovDepth = (float)dbarToMeters(pressure, posInstance.ShipLat); } // End Case 3 if (Properties.Settings.Default.testWithRandomROVPos) { // Generate some random data so that this can be tested with a production install by changing the testWithRandomROVPos value to True in the Setup dialog Random random = new Random(); posInstance.RovLat = posInstance.RovLat + (random.NextDouble() - 0.5) * 0.005; posInstance.RovLon = posInstance.RovLon + (random.NextDouble() - 0.5) * 0.005; posInstance.RovHeading = (float)random.NextDouble() * 360; } } // End if (fieldValues.Length > 1) } private double dbarToMeters(double pressure, double latitude) { // Test values for pressure to depth calculation // Pressure Depth Depth Depth Depth Depth // (Lat=0 (lat=30) (lat=45) (lat=60) (lat=90) // // 500.0 496.65 496.00 495.34 494.69 494.03 // 1000.0 992.12 990.81 989.50 988.19 986.88 // 2000.0 1979.55 1976.94 1974.33 1971.72 1969.11 // 3000.0 2962.43 2958.52 2954.61 2950.71 2946.81 // 4000.0 3940.88 3935.68 3930.49 3925.30 3920.10 // 5000.0 4915.04 4908.56 4902.08 4895.60 4889.13 // 6000.0 5885.03 5877.27 5869.51 5861.76 5854.01 // 7000.0 6850.95 6841.92 6832.89 6823.86 6814.84 // 8000.0 7812.93 7802.63 7792.33 7782.04 7771.76 // 9000.0 8771.07 8759.51 8747.95 8736.40 8724.85 //10000.0 9725.47 9712.65 9699.84 9687.03 9674.23 // double C1, C2, C3, C4, G0, G1, G2, Gamma, X, gravity, Z; double PI = 3.14159265358979; double depth; if (pressure == double.NaN || latitude == double.NaN) { return double.NaN; } C1 = 9.72659; C2 = -0.000022512; C3 = 0.0000000002279; C4 = -1.82E-15; G0 = 9.780318; G1 = 0.0052788; G2 = 0.0000236; Gamma = 0.000002184; //X = (Math.Sin(PI * latitude / 180.0)); X = Math.Pow((Math.Sin(PI * latitude / 180.0)), 2); gravity = G0 * (1 + (G1 + G2 * X) * X) + (Gamma / 2) * pressure; Z = (C1 + (C2 + (C3 + C4 * pressure) * pressure) * pressure) * pressure; depth = Z / gravity; return depth; } // End dbarToMeters() #endregion } // End class NavViaSocket } // End namespace