diff --git a/CumulusMX/Api.cs b/CumulusMX/Api.cs
index 877f1bd6..afebfe1d 100644
--- a/CumulusMX/Api.cs
+++ b/CumulusMX/Api.cs
@@ -14,118 +14,118 @@
 
 namespace CumulusMX
 {
-    public static class Api
-    {
-        private const string RelativePath = "/api/";
-        internal static WeatherStation Station;
-        public static StationSettings stationSettings;
-        public static InternetSettings internetSettings;
-        public static CalibrationSettings calibrationSettings;
-        public static NOAASettings noaaSettings;
-        public static MysqlSettings mySqlSettings;
-        internal static DataEditor dataEditor;
-
-        public static string Utf16ToUtf8(string utf16String)
-        {
-            /**************************************************************
-             * Every .NET string will store text with the UTF16 encoding, *
-             * known as Encoding.Unicode. Other encodings may exist as    *
-             * Byte-Array or incorrectly stored with the UTF16 encoding.  *
-             *                                                            *
-             * UTF8 = 1 bytes per char                                    *
-             *    ["100" for the ansi 'd']                                *
-             *    ["206" and "186" for the russian 'κ']                   *
-             *                                                            *
-             * UTF16 = 2 bytes per char                                   *
-             *    ["100, 0" for the ansi 'd']                             *
-             *    ["186, 3" for the russian 'κ']                          *
-             *                                                            *
-             * UTF8 inside UTF16                                          *
-             *    ["100, 0" for the ansi 'd']                             *
-             *    ["206, 0" and "186, 0" for the russian 'κ']             *
-             *                                                            *
-             * We can use the convert encoding function to convert an     *
-             * UTF16 Byte-Array to an UTF8 Byte-Array. When we use UTF8   *
-             * encoding to string method now, we will get a UTF16 string. *
-             *                                                            *
-             * So we imitate UTF16 by filling the second byte of a char   *
-             * with a 0 byte (binary 0) while creating the string.        *
-             **************************************************************/
-
-            // Storage for the UTF8 string
-            string utf8String = String.Empty;
-
-            // Get UTF16 bytes and convert UTF16 bytes to UTF8 bytes
-            byte[] utf16Bytes = Encoding.Unicode.GetBytes(utf16String);
-            byte[] utf8Bytes = Encoding.Convert(Encoding.Unicode, Encoding.UTF8, utf16Bytes);
-
-            // Fill UTF8 bytes inside UTF8 string
-            for (int i = 0; i < utf8Bytes.Length; i++)
-            {
-                // Because char always saves 2 bytes, fill char with 0
-                byte[] utf8Container = new byte[2] { utf8Bytes[i], 0 };
-                utf8String += BitConverter.ToChar(utf8Container, 0);
-            }
-
-            // Return UTF8
-            return utf8String;
-        }
-
-        private static string EscapeUnicode(string input)
-        {
-            StringBuilder sb = new StringBuilder(input.Length);
-            foreach (char ch in input)
-            {
-                if (ch <= 0x7f)
-                    sb.Append(ch);
-                else
-                    sb.AppendFormat(CultureInfo.InvariantCulture, "\\u{0:x4}", (int)ch);
-            }
-            return sb.ToString();
-        }
-
-        public static void Setup(WebServer server)
-        {
-            server.RegisterModule(new WebApiModule());
-
-            server.Module<WebApiModule>().RegisterController<GraphDataController>();
-            server.Module<WebApiModule>().RegisterController<DataController>();
-            server.Module<WebApiModule>().RegisterController<RecordsController>();
-            server.Module<WebApiModule>().RegisterController<TodayYestDataController>();
-            server.Module<WebApiModule>().RegisterController<ExtraDataController>();
-            server.Module<WebApiModule>().RegisterController<GetSettingsController>();
-            server.Module<WebApiModule>().RegisterController<SetSettingsController>();
+	public static class Api
+	{
+		private const string RelativePath = "/api/";
+		internal static WeatherStation Station;
+		public static StationSettings stationSettings;
+		public static InternetSettings internetSettings;
+		public static CalibrationSettings calibrationSettings;
+		public static NOAASettings noaaSettings;
+		public static MysqlSettings mySqlSettings;
+		internal static DataEditor dataEditor;
+
+		public static string Utf16ToUtf8(string utf16String)
+		{
+			/**************************************************************
+				* Every .NET string will store text with the UTF16 encoding, *
+				* known as Encoding.Unicode. Other encodings may exist as    *
+				* Byte-Array or incorrectly stored with the UTF16 encoding.  *
+				*                                                            *
+				* UTF8 = 1 bytes per char                                    *
+				*    ["100" for the ansi 'd']                                *
+				*    ["206" and "186" for the russian 'κ']                   *
+				*                                                            *
+				* UTF16 = 2 bytes per char                                   *
+				*    ["100, 0" for the ansi 'd']                             *
+				*    ["186, 3" for the russian 'κ']                          *
+				*                                                            *
+				* UTF8 inside UTF16                                          *
+				*    ["100, 0" for the ansi 'd']                             *
+				*    ["206, 0" and "186, 0" for the russian 'κ']             *
+				*                                                            *
+				* We can use the convert encoding function to convert an     *
+				* UTF16 Byte-Array to an UTF8 Byte-Array. When we use UTF8   *
+				* encoding to string method now, we will get a UTF16 string. *
+				*                                                            *
+				* So we imitate UTF16 by filling the second byte of a char   *
+				* with a 0 byte (binary 0) while creating the string.        *
+				**************************************************************/
+
+			// Storage for the UTF8 string
+			string utf8String = String.Empty;
+
+			// Get UTF16 bytes and convert UTF16 bytes to UTF8 bytes
+			byte[] utf16Bytes = Encoding.Unicode.GetBytes(utf16String);
+			byte[] utf8Bytes = Encoding.Convert(Encoding.Unicode, Encoding.UTF8, utf16Bytes);
+
+			// Fill UTF8 bytes inside UTF8 string
+			for (int i = 0; i < utf8Bytes.Length; i++)
+			{
+				// Because char always saves 2 bytes, fill char with 0
+				byte[] utf8Container = new byte[2] { utf8Bytes[i], 0 };
+				utf8String += BitConverter.ToChar(utf8Container, 0);
+			}
+
+			// Return UTF8
+			return utf8String;
+		}
+
+		private static string EscapeUnicode(string input)
+		{
+			StringBuilder sb = new StringBuilder(input.Length);
+			foreach (char ch in input)
+			{
+				if (ch <= 0x7f)
+					sb.Append(ch);
+				else
+					sb.AppendFormat(CultureInfo.InvariantCulture, "\\u{0:x4}", (int)ch);
+			}
+			return sb.ToString();
+		}
+
+		public static void Setup(WebServer server)
+		{
+			server.RegisterModule(new WebApiModule());
+
+			server.Module<WebApiModule>().RegisterController<GraphDataController>();
+			server.Module<WebApiModule>().RegisterController<DataController>();
+			server.Module<WebApiModule>().RegisterController<RecordsController>();
+			server.Module<WebApiModule>().RegisterController<TodayYestDataController>();
+			server.Module<WebApiModule>().RegisterController<ExtraDataController>();
+			server.Module<WebApiModule>().RegisterController<GetSettingsController>();
+			server.Module<WebApiModule>().RegisterController<SetSettingsController>();
 			server.Module<WebApiModule>().RegisterController<EditControllerGet>();
 			server.Module<WebApiModule>().RegisterController<EditControllerPost>();
 		}
 
 		public class EditControllerGet : WebApiController
-        {
+		{
 			public EditControllerGet(IHttpContext context) : base(context)
 			{
 			}
 	
-            [WebApiHandler(HttpVerbs.Get, RelativePath + "edit/*")]
+			[WebApiHandler(HttpVerbs.Get, RelativePath + "edit/*")]
 			public bool EditData()
 			{
 				try
-                {
+				{
 					// read the last segment of the URL to determine what data the caller wants
 					string lastSegment = Request.Url.Segments.Last();
-                                        
-                    switch (lastSegment)
-                    {
-                        case "raintodayeditdata.json":
-                            return this.JsonResponse(dataEditor.GetRainTodayEditData());
-                        
-                        case "raintoday":
-                            return this.JsonResponse(dataEditor.EditRainToday(this));                        
-                    }
-
-                    throw new KeyNotFoundException("Key Not Found: " + lastSegment);
-                }
-                catch (Exception ex)
-                {
+
+					switch (lastSegment)
+					{
+						case "raintodayeditdata.json":
+							return this.JsonResponse(dataEditor.GetRainTodayEditData());
+
+						case "raintoday":
+							return this.JsonResponse(dataEditor.EditRainToday(this));
+					}
+
+					throw new KeyNotFoundException("Key Not Found: " + lastSegment);
+				}
+				catch (Exception ex)
+				{
 					return HandleError(ex, 404);
 				}
 
@@ -134,19 +134,19 @@ public bool EditData()
 			private bool HandleError(Exception ex, int statusCode)
 			{
 				var errorResponse = new
-                {
-                    Title = "Unexpected Error",
-                    ErrorCode = ex.GetType().Name,
-                    Description = ex.Message,
-                };
+				{
+					Title = "Unexpected Error",
+					ErrorCode = ex.GetType().Name,
+					Description = ex.Message,
+				};
 
 				this.Response.StatusCode = statusCode;
-                return this.JsonResponse(errorResponse);
-            }
-        }
+				return this.JsonResponse(errorResponse);
+			}
+		}
 
-        public class EditControllerPost : WebApiController
-        {
+		public class EditControllerPost : WebApiController
+		{
 			public EditControllerPost(IHttpContext context) : base(context)
 			{
 			}
@@ -155,17 +155,17 @@ public EditControllerPost(IHttpContext context) : base(context)
 			public bool EditData()
 			{
 				try
-                {
+				{
 					// read the last segment of the URL to determine what data the caller wants
 					string lastSegment = Request.Url.Segments.Last();
 
-                    switch (lastSegment)
-                    {
-                        case "raintodayeditdata.json":
-                            return this.JsonResponse(dataEditor.GetRainTodayEditData());
+					switch (lastSegment)
+					{
+						case "raintodayeditdata.json":
+							return this.JsonResponse(dataEditor.GetRainTodayEditData());
 
-                        case "raintoday":
-                            return this.JsonResponse(dataEditor.EditRainToday(this));
+						case "raintoday":
+							return this.JsonResponse(dataEditor.EditRainToday(this));
 
 						case "diarydata":
 							return this.JsonResponse(dataEditor.EditDiary(this));
@@ -175,30 +175,30 @@ public bool EditData()
 					}
 
 					throw new KeyNotFoundException("Key Not Found: " + lastSegment);
-                }
-                catch (Exception ex)
-                {
-                    return HandleError(ex, 404);
-                }
+				}
+				catch (Exception ex)
+				{
+					return HandleError(ex, 404);
+				}
 
-            }
+			}
 
 			private bool HandleError(Exception ex, int statusCode)
 			{
 				var errorResponse = new
-                {
-                    Title = "Unexpected Error",
-                    ErrorCode = ex.GetType().Name,
-                    Description = ex.Message,
-                };
+				{
+					Title = "Unexpected Error",
+					ErrorCode = ex.GetType().Name,
+					Description = ex.Message,
+				};
 
 				this.Response.StatusCode = statusCode;
-                return this.JsonResponse(errorResponse);
-            }
-        }
+				return this.JsonResponse(errorResponse);
+			}
+		}
 
-        public class DataController : WebApiController
-        {
+		public class DataController : WebApiController
+		{
 			public DataController(IHttpContext context) : base(context)
 			{
 			}
@@ -207,28 +207,28 @@ public DataController(IHttpContext context) : base(context)
 			public bool GetData()
 			{
 				try
-                {
-                    // read the last segment of the URL to determine what data the caller wants
-                    var lastSegment = Request.Url.Segments.Last();
+				{
+					// read the last segment of the URL to determine what data the caller wants
+					var lastSegment = Request.Url.Segments.Last();
 
-                    var query = HttpUtility.ParseQueryString(Request.Url.Query);
-                    var date = query["date"];
+					var query = HttpUtility.ParseQueryString(Request.Url.Query);
+					var date = query["date"];
 					var year = query["year"];
-                    var month = query["month"];                   
-                    var draw = query["draw"];
-                    int start = Convert.ToInt32(query["start"]);
-                    int length = Convert.ToInt32(query["length"]);
-
-                    switch (lastSegment)
-                    {
-                        case "dayfile":
-                            return this.JsonResponse(Station.GetDayfile(draw,start,length));
-                        case "logfile":
-                            return this.JsonResponse(Station.GetLogfile(month,draw,start,length,false));
-                        case "extralogfile":
-                            return this.JsonResponse(Station.GetLogfile(month, draw, start, length, true));
-                        case "currentdata":
-                            return this.JsonResponse(Station.GetCurrentData());
+					var month = query["month"];
+					var draw = query["draw"];
+					int start = Convert.ToInt32(query["start"]);
+					int length = Convert.ToInt32(query["length"]);
+
+					switch (lastSegment)
+					{
+						case "dayfile":
+							return this.JsonResponse(Station.GetDayfile(draw,start,length));
+						case "logfile":
+							return this.JsonResponse(Station.GetLogfile(month,draw,start,length,false));
+						case "extralogfile":
+							return this.JsonResponse(Station.GetLogfile(month, draw, start, length, true));
+						case "currentdata":
+							return this.JsonResponse(Station.GetCurrentData());
 						case "diarydata":
 							return this.JsonResponse(Station.GetDiaryData(date));
 						case "diarysummary":
@@ -237,30 +237,29 @@ public bool GetData()
 					}
 
 					throw new KeyNotFoundException("Key Not Found: " + lastSegment);
-                }
-                catch (Exception ex)
-                {
-                    return HandleError(ex, 404);
-                }
-
-            }
+				}
+				catch (Exception ex)
+				{
+					return HandleError(ex, 404);
+				}
+			}
 
 			private bool HandleError(Exception ex, int statusCode)
 			{
 				var errorResponse = new
-                {
-                    Title = "Unexpected Error",
-                    ErrorCode = ex.GetType().Name,
-                    Description = ex.Message,
-                };
-
-                this.Response.StatusCode = statusCode;
-                return this.JsonResponse(errorResponse);
-            }
-        }
-
-        public class GraphDataController : WebApiController
-        {
+				{
+					Title = "Unexpected Error",
+					ErrorCode = ex.GetType().Name,
+					Description = ex.Message,
+				};
+
+				this.Response.StatusCode = statusCode;
+				return this.JsonResponse(errorResponse);
+			}
+		}
+
+		public class GraphDataController : WebApiController
+		{
 			public GraphDataController(IHttpContext context) : base(context)
 			{
 			}
@@ -269,77 +268,76 @@ public GraphDataController(IHttpContext context) : base(context)
 			public bool GetGraphData()
 			{
 				try
-                {
-                    // read the last segment of the URL to determine what data the caller wants
-                    var lastSegment = Request.Url.Segments.Last();
-                    
-                    switch (lastSegment)
-                    {
-                        case "tempdata.json":
-                            return this.JsonResponse(Station.GetTempGraphData());
-                        case "tempdatad3.json":
-                            return this.JsonResponse(Station.GetTempGraphDataD3());
-                        case "winddata.json":
-                            return this.JsonResponse(Station.GetWindGraphData());
-                        case "winddatad3.json":
-                            return this.JsonResponse(Station.GetWindGraphDataD3());
-                        case "raindata.json":
-                            return this.JsonResponse(Station.GetRainGraphData());
-                        case "raindatad3.json":
-                            return this.JsonResponse(Station.GetRainGraphDataD3());
-                        case "pressdata.json":
-                            return this.JsonResponse(Station.GetPressGraphData());
-                        case "pressdatad3.json":
-                            return this.JsonResponse(Station.GetPressGraphDataD3());
-                        case "wdirdata.json":
-                            return this.JsonResponse(Station.GetWindDirGraphData());
-                        case "wdirdatad3.json":
-                            return this.JsonResponse(Station.GetWindDirGraphDataD3());
-                        case "humdata.json":
-                            return this.JsonResponse(Station.GetHumGraphData());
-                        case "humdatad3.json":
-                            return this.JsonResponse(Station.GetHumGraphDataD3());
-                        case "solardata.json":
-                            return this.JsonResponse(Station.GetSolarGraphData());
-                        case "solardatad3.json":
-                            return this.JsonResponse(Station.GetSolarGraphDataD3());
-                        case "dailyrain.json":
-                            return this.JsonResponse(Station.GetDailyRainGraphData());
-                        case "sunhours.json":
-                            return this.JsonResponse(Station.GetSunHoursGraphData());
-                        case "dailytemp.json":
-                            return this.JsonResponse(Station.GetDailyTempGraphData());
-                        case "units.json":
-                            return this.JsonResponse(Station.GetUnits());
-                        case "graphconfig.json":
-                            return this.JsonResponse(Station.GetGraphConfig());
-                    }
-
-                    throw new KeyNotFoundException("Key Not Found: " + lastSegment);
-                }
-                catch (Exception ex)
-                {
-                    return HandleError(ex, 404);
-                }
-                
-            }
+				{
+					// read the last segment of the URL to determine what data the caller wants
+					var lastSegment = Request.Url.Segments.Last();
+
+					switch (lastSegment)
+					{
+						case "tempdata.json":
+							return this.JsonResponse(Station.GetTempGraphData());
+						case "tempdatad3.json":
+							return this.JsonResponse(Station.GetTempGraphDataD3());
+						case "winddata.json":
+							return this.JsonResponse(Station.GetWindGraphData());
+						case "winddatad3.json":
+							return this.JsonResponse(Station.GetWindGraphDataD3());
+						case "raindata.json":
+							return this.JsonResponse(Station.GetRainGraphData());
+						case "raindatad3.json":
+							return this.JsonResponse(Station.GetRainGraphDataD3());
+						case "pressdata.json":
+							return this.JsonResponse(Station.GetPressGraphData());
+						case "pressdatad3.json":
+							return this.JsonResponse(Station.GetPressGraphDataD3());
+						case "wdirdata.json":
+							return this.JsonResponse(Station.GetWindDirGraphData());
+						case "wdirdatad3.json":
+							return this.JsonResponse(Station.GetWindDirGraphDataD3());
+						case "humdata.json":
+							return this.JsonResponse(Station.GetHumGraphData());
+						case "humdatad3.json":
+							return this.JsonResponse(Station.GetHumGraphDataD3());
+						case "solardata.json":
+							return this.JsonResponse(Station.GetSolarGraphData());
+						case "solardatad3.json":
+							return this.JsonResponse(Station.GetSolarGraphDataD3());
+						case "dailyrain.json":
+							return this.JsonResponse(Station.GetDailyRainGraphData());
+						case "sunhours.json":
+							return this.JsonResponse(Station.GetSunHoursGraphData());
+						case "dailytemp.json":
+							return this.JsonResponse(Station.GetDailyTempGraphData());
+						case "units.json":
+							return this.JsonResponse(Station.GetUnits());
+						case "graphconfig.json":
+							return this.JsonResponse(Station.GetGraphConfig());
+					}
+
+					throw new KeyNotFoundException("Key Not Found: " + lastSegment);
+				}
+				catch (Exception ex)
+				{
+					return HandleError(ex, 404);
+				}
+			}
 
 			private bool HandleError(Exception ex, int statusCode)
 			{
 				var errorResponse = new
-                {
-                    Title = "Unexpected Error",
-                    ErrorCode = ex.GetType().Name,
-                    Description = ex.Message,
-                };
-
-                this.Response.StatusCode = statusCode;
-                return this.JsonResponse(errorResponse);
-            }
-        }
-
-        public class RecordsController : WebApiController
-        {
+				{
+					Title = "Unexpected Error",
+					ErrorCode = ex.GetType().Name,
+					Description = ex.Message,
+				};
+
+				this.Response.StatusCode = statusCode;
+				return this.JsonResponse(errorResponse);
+			}
+		}
+
+		public class RecordsController : WebApiController
+		{
 			public RecordsController(IHttpContext context) : base(context)
 			{
 			}
@@ -348,309 +346,295 @@ public RecordsController(IHttpContext context) : base(context)
 			public bool GetAlltimeData()
 			{
 				try
-                {
-                    // read the last segment of the URL to determine what data the caller wants
-                    var lastSegment = Request.Url.Segments.Last();
-
-                    switch (lastSegment)
-                    {
-                        case "temperature.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetTempRecords()));
-                        case "humidity.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetHumRecords()));
-                        case "pressure.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetPressRecords()));
-                        case "wind.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetWindRecords()));
-                        case "rain.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetRainRecords()));
-                        
-                    }
-
-                    throw new KeyNotFoundException("Key Not Found: " + lastSegment);
-                }
-                catch (Exception ex)
-                {
-                    return HandleError(ex, 404);
-                }
-
-            }
-
-            [WebApiHandler(HttpVerbs.Get, RelativePath + "records/month/*")]
+				{
+					// read the last segment of the URL to determine what data the caller wants
+					var lastSegment = Request.Url.Segments.Last();
+
+					switch (lastSegment)
+					{
+						case "temperature.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetTempRecords()));
+						case "humidity.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetHumRecords()));
+						case "pressure.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetPressRecords()));
+						case "wind.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetWindRecords()));
+						case "rain.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetRainRecords()));
+					}
+
+					throw new KeyNotFoundException("Key Not Found: " + lastSegment);
+				}
+				catch (Exception ex)
+				{
+					return HandleError(ex, 404);
+				}
+			}
+
+			[WebApiHandler(HttpVerbs.Get, RelativePath + "records/month/*")]
 			public bool GetMonthlyRecordData()
 			{
 				try
-                {
-                    // read the last segment of the URL to determine what data the caller wants
-                    var lastSegment = Request.Url.Segments.Last();
-                    // Get penultimate segment and trim off traling slash. This gives the required month
-                    int month = Convert.ToInt32(Request.Url.Segments[Request.Url.Segments.Length - 2].TrimEnd('/'));
-
-                    switch (lastSegment)
-                    {
-                        case "temperature.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetMonthlyTempRecords(month)));
-                        case "humidity.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetMonthlyHumRecords(month)));
-                        case "pressure.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetMonthlyPressRecords(month)));
-                        case "wind.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetMonthlyWindRecords(month)));
-                        case "rain.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetMonthlyRainRecords(month)));
-
-                    }
-
-                    throw new KeyNotFoundException("Key Not Found: " + lastSegment);
-                }
-                catch (Exception ex)
-                {
-                    return HandleError(ex, 404);
-                }
-
-            }
-
-            [WebApiHandler(HttpVerbs.Get, RelativePath + "records/thismonth/*")]
+				{
+					// read the last segment of the URL to determine what data the caller wants
+					var lastSegment = Request.Url.Segments.Last();
+					// Get penultimate segment and trim off traling slash. This gives the required month
+					int month = Convert.ToInt32(Request.Url.Segments[Request.Url.Segments.Length - 2].TrimEnd('/'));
+
+					switch (lastSegment)
+					{
+						case "temperature.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetMonthlyTempRecords(month)));
+						case "humidity.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetMonthlyHumRecords(month)));
+						case "pressure.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetMonthlyPressRecords(month)));
+						case "wind.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetMonthlyWindRecords(month)));
+						case "rain.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetMonthlyRainRecords(month)));
+					}
+
+					throw new KeyNotFoundException("Key Not Found: " + lastSegment);
+				}
+				catch (Exception ex)
+				{
+					return HandleError(ex, 404);
+				}
+			}
+
+			[WebApiHandler(HttpVerbs.Get, RelativePath + "records/thismonth/*")]
 			public bool GetThisMonthRecordData()
 			{
 				try
-                {
-                    // read the last segment of the URL to determine what data the caller wants
-                    var lastSegment = Request.Url.Segments.Last();
-                    
-                    switch (lastSegment)
-                    {
-                        case "temperature.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetThisMonthTempRecords()));
-                        case "humidity.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetThisMonthHumRecords()));
-                        case "pressure.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetThisMonthPressRecords()));
-                        case "wind.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetThisMonthWindRecords()));
-                        case "rain.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetThisMonthRainRecords()));
-
-                    }
-
-                    throw new KeyNotFoundException("Key Not Found: " + lastSegment);
-                }
-                catch (Exception ex)
-                {
-                    return HandleError(ex, 404);
-                }
-
-            }
-
-            [WebApiHandler(HttpVerbs.Get, RelativePath + "records/thisyear/*")]
+				{
+					// read the last segment of the URL to determine what data the caller wants
+					var lastSegment = Request.Url.Segments.Last();
+
+					switch (lastSegment)
+					{
+						case "temperature.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetThisMonthTempRecords()));
+						case "humidity.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetThisMonthHumRecords()));
+						case "pressure.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetThisMonthPressRecords()));
+						case "wind.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetThisMonthWindRecords()));
+						case "rain.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetThisMonthRainRecords()));
+					}
+
+					throw new KeyNotFoundException("Key Not Found: " + lastSegment);
+				}
+				catch (Exception ex)
+				{
+					return HandleError(ex, 404);
+				}
+			}
+
+			[WebApiHandler(HttpVerbs.Get, RelativePath + "records/thisyear/*")]
 			public bool GetThisYearRecordData()
 			{
 				try
-                {
-                    // read the last segment of the URL to determine what data the caller wants
-                    var lastSegment = Request.Url.Segments.Last();
-
-                    switch (lastSegment)
-                    {
-                        case "temperature.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetThisYearTempRecords()));
-                        case "humidity.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetThisYearHumRecords()));
-                        case "pressure.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetThisYearPressRecords()));
-                        case "wind.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetThisYearWindRecords()));
-                        case "rain.json":
-                            return this.JsonResponse(EscapeUnicode(Station.GetThisYearRainRecords()));
-
-                    }
-
-                    throw new KeyNotFoundException("Key Not Found: " + lastSegment);
-                }
-                catch (Exception ex)
-                {
-                    return HandleError(ex, 404);
-                }
-
-            }
+				{
+					// read the last segment of the URL to determine what data the caller wants
+					var lastSegment = Request.Url.Segments.Last();
+
+					switch (lastSegment)
+					{
+						case "temperature.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetThisYearTempRecords()));
+						case "humidity.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetThisYearHumRecords()));
+						case "pressure.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetThisYearPressRecords()));
+						case "wind.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetThisYearWindRecords()));
+						case "rain.json":
+							return this.JsonResponse(EscapeUnicode(Station.GetThisYearRainRecords()));
+					}
+
+					throw new KeyNotFoundException("Key Not Found: " + lastSegment);
+				}
+				catch (Exception ex)
+				{
+					return HandleError(ex, 404);
+				}
+			}
 
 			private bool HandleError(Exception ex, int statusCode)
 			{
 				var errorResponse = new
-                {
-                    Title = "Unexpected Error",
-                    ErrorCode = ex.GetType().Name,
-                    Description = ex.Message,
-                };
-
-                this.Response.StatusCode = statusCode;
-                return this.JsonResponse(errorResponse);
-            }
-        }
-
-        public class TodayYestDataController : WebApiController
-        {
+				{
+					Title = "Unexpected Error",
+					ErrorCode = ex.GetType().Name,
+					Description = ex.Message,
+				};
+
+				this.Response.StatusCode = statusCode;
+				return this.JsonResponse(errorResponse);
+			}
+		}
+
+		public class TodayYestDataController : WebApiController
+		{
 			public TodayYestDataController(IHttpContext context) : base(context) {}
 
 			[WebApiHandler(HttpVerbs.Get, RelativePath + "todayyest/*")]
 			public bool GetYesterdayData()
 			{
 				try
-                {
-                    // read the last segment of the URL to determine what data the caller wants
-                    var lastSegment = Request.Url.Segments.Last();
-
-                    switch (lastSegment)
-                    {
-                        case "temp.json":
-                            return this.JsonResponse(Station.GetTodayYestTemp());
-                        case "hum.json":
-                            return this.JsonResponse(Station.GetTodayYestHum());
-                        case "rain.json":
-                            return this.JsonResponse(Station.GetTodayYestRain());
-                        case "wind.json":
-                            return this.JsonResponse(Station.GetTodayYestWind());
-                        case "pressure.json":
-                            return this.JsonResponse(Station.GetTodayYestPressure());
-                        case "solar.json":
-                            return this.JsonResponse(Station.GetTodayYestSolar());
-
-                    }
-
-                    throw new KeyNotFoundException("Key Not Found: " + lastSegment);
-                }
-                catch (Exception ex)
-                {
-                    return HandleError(ex, 404);
-                }
-
-            }
+				{
+					// read the last segment of the URL to determine what data the caller wants
+					var lastSegment = Request.Url.Segments.Last();
+
+					switch (lastSegment)
+					{
+						case "temp.json":
+							return this.JsonResponse(Station.GetTodayYestTemp());
+						case "hum.json":
+							return this.JsonResponse(Station.GetTodayYestHum());
+						case "rain.json":
+							return this.JsonResponse(Station.GetTodayYestRain());
+						case "wind.json":
+							return this.JsonResponse(Station.GetTodayYestWind());
+						case "pressure.json":
+							return this.JsonResponse(Station.GetTodayYestPressure());
+						case "solar.json":
+							return this.JsonResponse(Station.GetTodayYestSolar());
+					}
+
+					throw new KeyNotFoundException("Key Not Found: " + lastSegment);
+				}
+				catch (Exception ex)
+				{
+					return HandleError(ex, 404);
+				}
+			}
 
 			private bool HandleError(Exception ex, int statusCode)
 			{
 				var errorResponse = new
-                {
-                    Title = "Unexpected Error",
-                    ErrorCode = ex.GetType().Name,
-                    Description = ex.Message,
-                };
-
-                this.Response.StatusCode = statusCode;
-                return this.JsonResponse(errorResponse);
-            }
-        }
-
-        public class ExtraDataController : WebApiController
-        {
+				{
+					Title = "Unexpected Error",
+					ErrorCode = ex.GetType().Name,
+					Description = ex.Message,
+				};
+
+				this.Response.StatusCode = statusCode;
+				return this.JsonResponse(errorResponse);
+			}
+		}
+
+		public class ExtraDataController : WebApiController
+		{
 			public ExtraDataController(IHttpContext context) : base(context) { }
 
 			[WebApiHandler(HttpVerbs.Get, RelativePath + "extra/*")]
 			public bool GetExtraData()
 			{
 				try
-                {
-                    // read the last segment of the URL to determine what data the caller wants
-                    var lastSegment = Request.Url.Segments.Last();
-
-                    switch (lastSegment)
-                    {
-                        case "temp.json":
-                            return this.JsonResponse(Station.GetExtraTemp());
-                        case "hum.json":
-                            return this.JsonResponse(Station.GetExtraHum());
-                        case "dew.json":
-                            return this.JsonResponse(Station.GetExtraDew());
-                        case "soiltemp.json":
-                            return this.JsonResponse(Station.GetSoilTemp());
-                        case "soilmoisture.json":
-                            return this.JsonResponse(Station.GetSoilMoisture());
-                        case "leaf.json":
-                            return this.JsonResponse(Station.GetLeaf());
-                        case "leaf4.json":
-                            return this.JsonResponse(Station.GetLeaf4());
-
-                    }
-
-                    throw new KeyNotFoundException("Key Not Found: " + lastSegment);
-                }
-                catch (Exception ex)
-                {
-                    return HandleError(ex, 404);
-                }
-
-            }
+				{
+					// read the last segment of the URL to determine what data the caller wants
+					var lastSegment = Request.Url.Segments.Last();
+
+					switch (lastSegment)
+					{
+						case "temp.json":
+							return this.JsonResponse(Station.GetExtraTemp());
+						case "hum.json":
+							return this.JsonResponse(Station.GetExtraHum());
+						case "dew.json":
+							return this.JsonResponse(Station.GetExtraDew());
+						case "soiltemp.json":
+							return this.JsonResponse(Station.GetSoilTemp());
+						case "soilmoisture.json":
+							return this.JsonResponse(Station.GetSoilMoisture());
+						case "leaf.json":
+							return this.JsonResponse(Station.GetLeaf());
+						case "leaf4.json":
+							return this.JsonResponse(Station.GetLeaf4());
+					}
+
+					throw new KeyNotFoundException("Key Not Found: " + lastSegment);
+				}
+				catch (Exception ex)
+				{
+					return HandleError(ex, 404);
+				}
+			}
 
 			private bool HandleError(Exception ex, int statusCode)
 			{
 				var errorResponse = new
-                {
-                    Title = "Unexpected Error",
-                    ErrorCode = ex.GetType().Name,
-                    Description = ex.Message,
-                };
-
-                this.Response.StatusCode = statusCode;
-                return this.JsonResponse(errorResponse);
-            }
-        }
-
-        public class SetSettingsController : WebApiController
-        {
+				{
+					Title = "Unexpected Error",
+					ErrorCode = ex.GetType().Name,
+					Description = ex.Message,
+				};
+
+				this.Response.StatusCode = statusCode;
+				return this.JsonResponse(errorResponse);
+			}
+		}
+
+		public class SetSettingsController : WebApiController
+		{
 			public SetSettingsController(IHttpContext context) : base(context) { }
 
 			[WebApiHandler(HttpVerbs.Post, RelativePath + "setsettings/*")]
 			public bool SettingsSet()
 			{
 				try
-                {
-                    // read the last segment of the URL to determine what data the caller wants
-                    var lastSegment = Request.Url.Segments.Last();
-                    
-                    switch (lastSegment)
-                    {
-                       
-                        case "updatestationconfig.json":
-                            return this.JsonResponse(stationSettings.UpdateStationConfig(this));
-                        case "updateinternetconfig.json":
-                            return this.JsonResponse(internetSettings.UpdateInternetConfig(this));
-                        case "updatecalibrationconfig.json":
-                            return this.JsonResponse(calibrationSettings.UpdateCalibrationConfig(this));
-                        case "updatenoaaconfig.json":
-                            return this.JsonResponse(noaaSettings.UpdateNoaaConfig(this));
-                        case "updateextrawebfiles.html":
-                            return this.JsonResponse(internetSettings.UpdateExtraWebFiles(this));
-                        case "updatemysqlconfig.json":
-                            return this.JsonResponse(mySqlSettings.UpdateMysqlConfig(this));
-                        case "createmonthlysql.json":
-                            return this.JsonResponse(mySqlSettings.CreateMonthlySQL(this));
-                        case "createdayfilesql.json":
-                            return this.JsonResponse(mySqlSettings.CreateDayfileSQL(this));
-                        case "createrealtimesql.json":
-                            return this.JsonResponse(mySqlSettings.CreateRealtimeSQL(this));
-                    }
-
-                    throw new KeyNotFoundException("Key Not Found: " + lastSegment);
-                }
-                catch (Exception ex)
-                {
-                    return HandleError(ex, 404);
-                }
-
-            }
+				{
+					// read the last segment of the URL to determine what data the caller wants
+					var lastSegment = Request.Url.Segments.Last();
+
+					switch (lastSegment)
+					{
+						case "updatestationconfig.json":
+							return this.JsonResponse(stationSettings.UpdateStationConfig(this));
+						case "updateinternetconfig.json":
+							return this.JsonResponse(internetSettings.UpdateInternetConfig(this));
+						case "updatecalibrationconfig.json":
+							return this.JsonResponse(calibrationSettings.UpdateCalibrationConfig(this));
+						case "updatenoaaconfig.json":
+							return this.JsonResponse(noaaSettings.UpdateNoaaConfig(this));
+						case "updateextrawebfiles.html":
+							return this.JsonResponse(internetSettings.UpdateExtraWebFiles(this));
+						case "updatemysqlconfig.json":
+							return this.JsonResponse(mySqlSettings.UpdateMysqlConfig(this));
+						case "createmonthlysql.json":
+							return this.JsonResponse(mySqlSettings.CreateMonthlySQL(this));
+						case "createdayfilesql.json":
+							return this.JsonResponse(mySqlSettings.CreateDayfileSQL(this));
+						case "createrealtimesql.json":
+							return this.JsonResponse(mySqlSettings.CreateRealtimeSQL(this));
+					}
+
+					throw new KeyNotFoundException("Key Not Found: " + lastSegment);
+				}
+				catch (Exception ex)
+				{
+					return HandleError(ex, 404);
+				}
+			}
 
 			private bool HandleError(Exception ex, int statusCode)
 			{
 				var errorResponse = new
-                {
-                    Title = "Unexpected Error",
-                    ErrorCode = ex.GetType().Name,
-                    Description = ex.Message,
-                };
+				{
+					Title = "Unexpected Error",
+					ErrorCode = ex.GetType().Name,
+					Description = ex.Message,
+				};
 
-                this.Response.StatusCode = statusCode;
-                return this.JsonResponse(errorResponse);
-            }
-        }
+				this.Response.StatusCode = statusCode;
+				return this.JsonResponse(errorResponse);
+			}
+		}
 
 		public class GetSettingsController : WebApiController
 		{
@@ -686,77 +670,76 @@ public bool SettingsGet()
 				 }*/
 
 				try
-                {
-                    // read the last segment of the URL to determine what data the caller wants
-                    var lastSegment = Request.Url.Segments.Last();
-                    
-                    switch (lastSegment)
-                    {
-                        case "stationdata.json":
-                            return this.JsonResponse(stationSettings.GetStationAlpacaFormData());
-                        case "stationoptions.json":
-                            return this.JsonResponse(stationSettings.GetStationAlpacaFormOptions());
-                        case "stationschema.json":
-                            return this.JsonResponse(stationSettings.GetStationAlpacaFormSchema());
-
-                        case "internetdata.json":
-                            return this.JsonResponse(internetSettings.GetInternetAlpacaFormData());
-                        case "internetoptions.json":
-                            return this.JsonResponse(internetSettings.GetInternetAlpacaFormOptions());
-                        case "internetschema.json":
-                            return this.JsonResponse(internetSettings.GetInternetAlpacaFormSchema());
-
-                        case "extrawebfiles.json":
-                            return this.JsonResponse(internetSettings.GetExtraWebFilesData());
-
-                        case "calibrationdata.json":
-                            return this.JsonResponse(calibrationSettings.GetCalibrationAlpacaFormData());
-                        case "calibrationoptions.json":
-                            return this.JsonResponse(calibrationSettings.GetCalibrationAlpacaFormOptions());
-                        case "calibrationschema.json":
-                            return this.JsonResponse(calibrationSettings.GetCalibrationAlpacaFormSchema());
-
-                        case "noaadata.json":
-                            return this.JsonResponse(noaaSettings.GetNoaaAlpacaFormData());
-                        case "noaaoptions.json":
-                            return this.JsonResponse(noaaSettings.GetNoaaAlpacaFormOptions());
-                        case "noaaschema.json":
-                            return this.JsonResponse(noaaSettings.GetNoaaAlpacaFormSchema());
-
-                        case "wsport.json":
-                            return this.JsonResponse(stationSettings.GetWSport());
-                        case "version.json":
-                            return this.JsonResponse(stationSettings.GetVersion());
-
-                        case "mysqldata.json":
-                            return this.JsonResponse(mySqlSettings.GetMySqlAlpacaFormData());
-                        case "mysqloptions.json":
-                            return this.JsonResponse(mySqlSettings.GetMySqAlpacaFormOptions());
-                        case "mysqlschema.json":
-                            return this.JsonResponse(mySqlSettings.GetMySqAlpacaFormSchema());
-                    }
-
-                    throw new KeyNotFoundException("Key Not Found: " + lastSegment);
-                }
-                catch (Exception ex)
-                {
-                    return HandleError(ex, 404);
-                }
-
-            }
+				{
+					// read the last segment of the URL to determine what data the caller wants
+					var lastSegment = Request.Url.Segments.Last();
+
+					switch (lastSegment)
+					{
+						case "stationdata.json":
+							return this.JsonResponse(stationSettings.GetStationAlpacaFormData());
+						case "stationoptions.json":
+							return this.JsonResponse(stationSettings.GetStationAlpacaFormOptions());
+						case "stationschema.json":
+							return this.JsonResponse(stationSettings.GetStationAlpacaFormSchema());
+
+						case "internetdata.json":
+							return this.JsonResponse(internetSettings.GetInternetAlpacaFormData());
+						case "internetoptions.json":
+							return this.JsonResponse(internetSettings.GetInternetAlpacaFormOptions());
+						case "internetschema.json":
+							return this.JsonResponse(internetSettings.GetInternetAlpacaFormSchema());
+
+						case "extrawebfiles.json":
+							return this.JsonResponse(internetSettings.GetExtraWebFilesData());
+
+						case "calibrationdata.json":
+							return this.JsonResponse(calibrationSettings.GetCalibrationAlpacaFormData());
+						case "calibrationoptions.json":
+							return this.JsonResponse(calibrationSettings.GetCalibrationAlpacaFormOptions());
+						case "calibrationschema.json":
+							return this.JsonResponse(calibrationSettings.GetCalibrationAlpacaFormSchema());
+
+						case "noaadata.json":
+							return this.JsonResponse(noaaSettings.GetNoaaAlpacaFormData());
+						case "noaaoptions.json":
+							return this.JsonResponse(noaaSettings.GetNoaaAlpacaFormOptions());
+						case "noaaschema.json":
+							return this.JsonResponse(noaaSettings.GetNoaaAlpacaFormSchema());
+
+						case "wsport.json":
+							return this.JsonResponse(stationSettings.GetWSport());
+						case "version.json":
+							return this.JsonResponse(stationSettings.GetVersion());
+
+						case "mysqldata.json":
+							return this.JsonResponse(mySqlSettings.GetMySqlAlpacaFormData());
+						case "mysqloptions.json":
+							return this.JsonResponse(mySqlSettings.GetMySqAlpacaFormOptions());
+						case "mysqlschema.json":
+							return this.JsonResponse(mySqlSettings.GetMySqAlpacaFormSchema());
+					}
+
+					throw new KeyNotFoundException("Key Not Found: " + lastSegment);
+				}
+				catch (Exception ex)
+				{
+					return HandleError(ex, 404);
+				}
+			}
 
 			private bool HandleError(Exception ex, int statusCode)
 			{
 				var errorResponse = new
-                {
-                    Title = "Unexpected Error",
-                    ErrorCode = ex.GetType().Name,
-                    Description = ex.Message,
-                };
-
-                this.Response.StatusCode = statusCode;
-                return this.JsonResponse(errorResponse);
-            }
-        }
-    }
+				{
+					Title = "Unexpected Error",
+					ErrorCode = ex.GetType().Name,
+					Description = ex.Message,
+				};
+
+				this.Response.StatusCode = statusCode;
+				return this.JsonResponse(errorResponse);
+			}
+		}
+	}
 }
diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs
index e6dec641..daaf6e1d 100644
--- a/CumulusMX/Cumulus.cs
+++ b/CumulusMX/Cumulus.cs
@@ -30,7 +30,7 @@ public class Cumulus
 	{
 		/////////////////////////////////
 		public string Version = "3.0.0";
-		public string Build = "3048beta";
+		public string Build = "3048";
 		/////////////////////////////////
 
 		private static string appGuid = "57190d2e-7e45-4efb-8c09-06a176cef3f3";
@@ -76,11 +76,11 @@ public enum rainunits
 			IN
 		}
 
-        public enum solarcalcTypes
-        {
-            RyanStolzenbach = 0,
-            Bras = 1
-        }
+		public enum solarcalcTypes
+		{
+			RyanStolzenbach = 0,
+			Bras = 1
+		}
 
 		public struct Dataunits
 		{
@@ -201,7 +201,7 @@ public struct TExtraFiles
 
 		public string WindRunUnitText;
 
-		public bool WebUpdating = false;
+		public volatile bool WebUpdating = false;
 
 		public double WindRoseAngle { get; set; }
 
@@ -355,9 +355,12 @@ public struct TExtraFiles
 		public string RainFormat;
 
 		internal int PressDPlaces = 1;
-	    internal bool DavisIncrementPressureDP;
+		internal bool DavisIncrementPressureDP;
 		public string PressFormat;
 
+		internal int SunshineDPlaces = 1;
+		public string SunFormat;
+
 		internal int UVDPlaces = 1;
 		public string UVFormat;
 
@@ -365,9 +368,9 @@ public struct TExtraFiles
 
 		public int VPrainGaugeType = -1;
 
-        public string ComportName;
-        public string DefaultComportName;
-        public int ImetBaudRate;
+		public string ComportName;
+		public string DefaultComportName;
+		public int ImetBaudRate;
 		public int DavisBaudRate;
 
 		public int VendorID;
@@ -513,7 +516,7 @@ public struct TExtraFiles
 		public bool RealtimeEnabled; // The timer is to be started
 		public bool RealtimeFTPEnabled; // The FTP connection is to be established
 		public bool RealtimeTxtFTP; // The realtime.txt file is to be uploaded
-	    public bool RealtimeGaugesTxtFTP; // The realtimegauges.txt file is to be uploaded
+		public bool RealtimeGaugesTxtFTP; // The realtimegauges.txt file is to be uploaded
 
 		// Twitter settings
 		public string Twitteruser = " ";
@@ -782,7 +785,7 @@ public struct TExtraFiles
 		public string[] APRSstationtype = { "DsVP", "DsVP", "WMR928", "WM918", "EW", "FO", "WS2300", "FOs", "WMR100", "WMR200", "Instromet" };
 
 
-        /*
+		/*
 		CryptoLicense lic = new CryptoLicense();
 
 
@@ -886,11 +889,11 @@ private void DoLicenseCheck()
 		}
 		*/
 
-        public Cumulus(int HTTPport, int WSport)
-        {
-            //DoLicenseCheck();
+		public Cumulus(int HTTPport, int WSport)
+		{
+			//DoLicenseCheck();
 
-            /*lic.ValidationKey = "AMAAMACrfxYrYEOGd+D5ypZ32bnLCvviBrTlejReXNRdvgWzSgyvdfkLvNDvDX1WuMh2JIEDAAEAAQ==";
+			/*lic.ValidationKey = "AMAAMACrfxYrYEOGd+D5ypZ32bnLCvviBrTlejReXNRdvgWzSgyvdfkLvNDvDX1WuMh2JIEDAAEAAQ==";
 
 			// Load license from the file
 			lic.StorageMode = LicenseStorageMode.ToFile;
@@ -902,10 +905,10 @@ public Cumulus(int HTTPport, int WSport)
 				throw new Exception("license validation failed");
 			*/
 
-            string serial = CalculateMD5Hash(Environment.MachineName);
-            Console.WriteLine("Serial: " + serial);
-            File.WriteAllText("serial.txt", serial);
-            /*
+			string serial = CalculateMD5Hash(Environment.MachineName);
+			Console.WriteLine("Serial: " + serial);
+			File.WriteAllText("serial.txt", serial);
+			/*
 						try
 						{
 							using (TextReader reader = File.OpenText(@"licence.lic"))
@@ -954,71 +957,71 @@ public Cumulus(int HTTPport, int WSport)
 							Environment.Exit(0);
 						}
 			*/
-            DirectorySeparator = Path.DirectorySeparatorChar;
+			DirectorySeparator = Path.DirectorySeparatorChar;
 
-            AppDir = AppDomain.CurrentDomain.BaseDirectory;
+			AppDir = AppDomain.CurrentDomain.BaseDirectory;
 
-            TwitterTxtFile = AppDir + "twitter.txt";
-            WebTagFile = AppDir + "WebTags.txt";
+			TwitterTxtFile = AppDir + "twitter.txt";
+			WebTagFile = AppDir + "WebTags.txt";
 
-            // interface port passed as param
-            HttpPort = HTTPport;
+			// interface port passed as param
+			HttpPort = HTTPport;
 
 			//b3045, use smae port for WS...  WS port = HTTPS port
 			//wsPort = WSport;
 			wsPort = HTTPport;
 
-            // Set up the diagnostic tracing
-            string loggingfile = GetLoggingFileName("MXdiags" + DirectorySeparator);
+			// Set up the diagnostic tracing
+			string loggingfile = GetLoggingFileName("MXdiags" + DirectorySeparator);
 
-            TextWriterTraceListener myTextListener = new TextWriterTraceListener(loggingfile);
+			TextWriterTraceListener myTextListener = new TextWriterTraceListener(loggingfile);
 
-            Trace.Listeners.Add(myTextListener);
-            Trace.AutoFlush = true;
+			Trace.Listeners.Add(myTextListener);
+			Trace.AutoFlush = true;
 
 
-            // Read the configuration file
+			// Read the configuration file
 
-            LogMessage(" ========================== Cumulus MX starting ==========================");
+			LogMessage(" ========================== Cumulus MX starting ==========================");
 
-            LogMessage("Command line: " + Environment.CommandLine);
+			LogMessage("Command line: " + Environment.CommandLine);
 
 
-            Assembly thisAssembly = this.GetType().Assembly;
-            //Version = thisAssembly.GetName().Version.ToString();
-            //VersionLabel.Content = "Cumulus v." + thisAssembly.GetName().Version;
-            LogMessage("Cumulus MX v." + Version + " build " + Build);
-            Console.WriteLine("Cumulus MX v." + Version + " build " + Build);
-            //Console.WriteLine("This is pre-release beta software");
+			Assembly thisAssembly = this.GetType().Assembly;
+			//Version = thisAssembly.GetName().Version.ToString();
+			//VersionLabel.Content = "Cumulus v." + thisAssembly.GetName().Version;
+			LogMessage("Cumulus MX v." + Version + " build " + Build);
+			Console.WriteLine("Cumulus MX v." + Version + " build " + Build);
+			//Console.WriteLine("This is pre-release beta software");
 
-            IsOSX = IsRunningOnMac();
+			IsOSX = IsRunningOnMac();
 
-            Platform = IsOSX ? "Mac OS X" : Environment.OSVersion.Platform.ToString();
+			Platform = IsOSX ? "Mac OS X" : Environment.OSVersion.Platform.ToString();
 
-            // Set the default comport name depending on platform
-            if (Platform.Substring(0, 3) == "Win")
-            {
-                DefaultComportName = "COM1";
-            }
-            else
-            {
-                DefaultComportName = "/dev/ttyUSB0";
-            }
+			// Set the default comport name depending on platform
+			if (Platform.Substring(0, 3) == "Win")
+			{
+				DefaultComportName = "COM1";
+			}
+			else
+			{
+				DefaultComportName = "/dev/ttyUSB0";
+			}
 
 
-            LogMessage("Platform: " + Platform);
+			LogMessage("Platform: " + Platform);
 
 			LogMessage("OS version: " + Environment.OSVersion.ToString());
 
-            Type type = Type.GetType("Mono.Runtime");
-            if (type != null)
-            {
-                MethodInfo displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
-                if (displayName != null)
-                    LogMessage("Mono version: "+displayName.Invoke(null, null));
-            }
+			Type type = Type.GetType("Mono.Runtime");
+			if (type != null)
+			{
+				MethodInfo displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
+				if (displayName != null)
+					LogMessage("Mono version: "+displayName.Invoke(null, null));
+			}
 
-            LogMessage("Current culture: " + CultureInfo.CurrentCulture.DisplayName);
+			LogMessage("Current culture: " + CultureInfo.CurrentCulture.DisplayName);
 			ListSeparator = CultureInfo.CurrentCulture.TextInfo.ListSeparator;
 
 			DecimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
@@ -1066,7 +1069,7 @@ public Cumulus(int HTTPport, int WSport)
 			ThisMonthTFile = "web" + DirectorySeparator + "thismonthT.htm";
 			ThisYearTFile = "web" + DirectorySeparator + "thisyearT.htm";
 			MonthlyRecordTFile = "web" + DirectorySeparator + "monthlyrecordT.htm";
-		    RealtimeGaugesTxtTFile = "web" + DirectorySeparator + "realtimegaugesT.txt";
+			RealtimeGaugesTxtTFile = "web" + DirectorySeparator + "realtimegaugesT.txt";
 
 			Indexfile = "web" + DirectorySeparator + "index.htm";
 			Todayfile = "web" + DirectorySeparator + "today.htm";
@@ -1077,9 +1080,9 @@ public Cumulus(int HTTPport, int WSport)
 			ThisMonthfile = "web" + DirectorySeparator + "thismonth.htm";
 			ThisYearfile = "web" + DirectorySeparator + "thisyear.htm";
 			MonthlyRecordfile = "web" + DirectorySeparator + "monthlyrecord.htm";
-            RealtimeGaugesTxtFile = "web" + DirectorySeparator + "realtimegauges.txt";
+			RealtimeGaugesTxtFile = "web" + DirectorySeparator + "realtimegauges.txt";
 
-            localwebtextfiles = new[] { Indexfile, Todayfile, Yesterfile, Recordfile, Trendsfile, Gaugesfile, ThisMonthfile, ThisYearfile, MonthlyRecordfile };
+			localwebtextfiles = new[] { Indexfile, Todayfile, Yesterfile, Recordfile, Trendsfile, Gaugesfile, ThisMonthfile, ThisYearfile, MonthlyRecordfile };
 			remotewebtextfiles = new[] { "index.htm", "today.htm", "yesterday.htm", "record.htm", "trends.htm", "gauges.htm", "thismonth.htm", "thisyear.htm", "monthlyrecord.htm" };
 
 			//localgraphdatafiles = new[] {"units.json","tempdatad3.json", "pressdatad3.json", "winddatad3.json", "wdirdatad3.json", "humdatad3.json", "raindatad3.json", "solardatad3.json"};
@@ -1101,10 +1104,10 @@ public Cumulus(int HTTPport, int WSport)
 				};
 
 			remotegraphdatafiles = new[]
-								   {
-									   "graphconfig.json", "tempdata.json", "pressdata.json", "winddata.json", "wdirdata.json", "humdata.json", "raindata.json", "solardata.json",
-									   "dailyrain.json", "sunhours.json", "dailytemp.json"
-								   };
+									{
+										"graphconfig.json", "tempdata.json", "pressdata.json", "winddata.json", "wdirdata.json", "humdata.json", "raindata.json", "solardata.json",
+										"dailyrain.json", "sunhours.json", "dailytemp.json"
+									};
 
 			LogMessage("Data path = " + Datapath);
 
@@ -1143,14 +1146,15 @@ public Cumulus(int HTTPport, int WSport)
 			LogMessage("Debug logging is " + (logging ? "enabled" : "disabled"));
 			LogMessage("Data logging is " + (DataLogging ? "enabled" : "disabled"));
 			LogMessage("Logging interval = " + logints[DataLogInterval]);
-            LogMessage("NoSensorCheck = " + (NoSensorCheck ? "1" : "0"));
+			LogMessage("NoSensorCheck = " + (NoSensorCheck ? "1" : "0"));
 
-            TempFormat = "F" + TempDPlaces;
+			TempFormat = "F" + TempDPlaces;
 			WindFormat = "F" + WindDPlaces;
 			RainFormat = "F" + RainDPlaces;
 			PressFormat = "F" + PressDPlaces;
 			HumFormat = "F" + HumDPlaces;
 			UVFormat = "F" + UVDPlaces;
+			SunFormat = "F" + SunshineDPlaces;
 			ETFormat = "F" + (RainDPlaces + 1);
 			WindRunFormat = "F" + WindRunDPlaces;
 			TempTrendFormat = "+0.0;-0.0;0";
@@ -1255,12 +1259,12 @@ public Cumulus(int HTTPport, int WSport)
 			LogMessage("RainDayThreshold=" + RainDayThreshold.ToString("F3"));
 			LogMessage("Offsets and Multipliers:");
 			LogMessage("PO=" + PressOffset.ToString("F3") + " TO=" + TempOffset.ToString("F3") + " HO=" + HumOffset + " WDO=" + WindDirOffset + " ITO=" +
-					   InTempoffset.ToString("F3") + " UVO=" + UVOffset.ToString("F3"));
+						InTempoffset.ToString("F3") + " UVO=" + UVOffset.ToString("F3"));
 			LogMessage("WSM=" + WindSpeedMult.ToString("F3") + " WGM=" + WindGustMult.ToString("F3") + " TM=" + TempMult.ToString("F3") + " TM2=" + TempMult2.ToString("F3") +
-					   " HM=" + HumMult.ToString("F3") + " HM2=" + HumMult2.ToString("F3") + " RM=" + RainMult.ToString("F3") + " UVM=" + UVMult.ToString("F3"));
+						" HM=" + HumMult.ToString("F3") + " HM2=" + HumMult2.ToString("F3") + " RM=" + RainMult.ToString("F3") + " UVM=" + UVMult.ToString("F3"));
 			LogMessage("Spike removal:");
 			LogMessage("TD=" + EWtempdiff.ToString("F3") + " GD=" + EWgustdiff.ToString("F3") + " WD=" + EWwinddiff.ToString("F3") + " HD=" + EWhumiditydiff.ToString("F3") + " PD=" +
-					   EWpressurediff.ToString("F3"));
+						EWpressurediff.ToString("F3"));
 			LogMessage("MR=" + EWmaxRainRate.ToString("F3") + " MH=" + EWmaxHourlyRain.ToString("F3"));
 
 			LogMessage("Cumulus Starting");
@@ -1460,7 +1464,7 @@ private void CustomHttpSecondsTimerTick(object sender, ElapsedEventArgs e)
 		internal void SetStartOfRealtimeInsertSQL()
 		{
 			StartOfRealtimeInsertSQL = "INSERT IGNORE INTO " + MySqlRealtimeTable +
-									   " (LogDateTime,temp,hum,dew,wspeed,wlatest,bearing,rrate,rfall,press,currentwdir,beaufortnumber,windunit,tempunitnodeg,pressunit,rainunit,windrun,presstrendval,rmonth,ryear,rfallY,intemp,inhum,wchill,temptrend,tempTH,TtempTH,tempTL,TtempTL,windTM,TwindTM,wgustTM,TwgustTM,pressTH,TpressTH,pressTL,TpressTL,version,build,wgust,heatindex,humidex,UV,ET,SolarRad,avgbearing,rhour,forecastnumber,isdaylight,SensorContactLost,wdir,cloudbasevalue,cloudbaseunit,apptemp,SunshineHours,CurrentSolarMax,IsSunny)";
+										" (LogDateTime,temp,hum,dew,wspeed,wlatest,bearing,rrate,rfall,press,currentwdir,beaufortnumber,windunit,tempunitnodeg,pressunit,rainunit,windrun,presstrendval,rmonth,ryear,rfallY,intemp,inhum,wchill,temptrend,tempTH,TtempTH,tempTL,TtempTL,windTM,TwindTM,wgustTM,TwgustTM,pressTH,TpressTH,pressTL,TpressTL,version,build,wgust,heatindex,humidex,UV,ET,SolarRad,avgbearing,rhour,forecastnumber,isdaylight,SensorContactLost,wdir,cloudbasevalue,cloudbaseunit,apptemp,SunshineHours,CurrentSolarMax,IsSunny)";
 		}
 
 		internal void SetRealtimeSqlCreateString()
@@ -1481,19 +1485,19 @@ internal void SetRealtimeSqlCreateString()
 								") NOT NULL,ET decimal(4," + RainDPlaces + ") NOT NULL,SolarRad decimal(5,1) NOT NULL,avgbearing varchar(3) NOT NULL,rhour decimal(4," + RainDPlaces +
 								") NOT NULL,forecastnumber varchar(2) NOT NULL,isdaylight varchar(1) NOT NULL,SensorContactLost varchar(1) NOT NULL,wdir varchar(3) NOT NULL,cloudbasevalue varchar(5) NOT NULL,cloudbaseunit varchar(2) NOT NULL,apptemp decimal(4," +
 								TempDPlaces +
-								") NOT NULL,SunshineHours decimal(3,1) NOT NULL,CurrentSolarMax decimal(5,1) NOT NULL,IsSunny varchar(1) NOT NULL,PRIMARY KEY (LogDateTime)) COMMENT = \"Realtime log\"";
+								") NOT NULL,SunshineHours decimal(3," + SunshineDPlaces + ") NOT NULL,CurrentSolarMax decimal(5,1) NOT NULL,IsSunny varchar(1) NOT NULL,PRIMARY KEY (LogDateTime)) COMMENT = \"Realtime log\"";
 		}
 
 		internal void SetStartOfDayfileInsertSQL()
 		{
 			StartOfDayfileInsertSQL = "INSERT IGNORE INTO " + MySqlDayfileTable +
-									  " (LogDate,HighWindGust,HWindGBear,THWindG,MinTemp,TMinTemp,MaxTemp,TMaxTemp,MinPress,TMinPress,MaxPress,TMaxPress,MaxRainRate,TMaxRR,TotRainFall,AvgTemp,TotWindRun,HighAvgWSpeed,THAvgWSpeed,LowHum,TLowHum,HighHum,THighHum,TotalEvap,HoursSun,HighHeatInd,THighHeatInd,HighAppTemp,THighAppTemp,LowAppTemp,TLowAppTemp,HighHourRain,THighHourRain,LowWindChill,TLowWindChill,HighDewPoint,THighDewPoint,LowDewPoint,TLowDewPoint,DomWindDir,HeatDegDays,CoolDegDays,HighSolarRad,THighSolarRad,HighUV,THighUV,HWindGBearSym,DomWindDirSym)";
+										" (LogDate,HighWindGust,HWindGBear,THWindG,MinTemp,TMinTemp,MaxTemp,TMaxTemp,MinPress,TMinPress,MaxPress,TMaxPress,MaxRainRate,TMaxRR,TotRainFall,AvgTemp,TotWindRun,HighAvgWSpeed,THAvgWSpeed,LowHum,TLowHum,HighHum,THighHum,TotalEvap,HoursSun,HighHeatInd,THighHeatInd,HighAppTemp,THighAppTemp,LowAppTemp,TLowAppTemp,HighHourRain,THighHourRain,LowWindChill,TLowWindChill,HighDewPoint,THighDewPoint,LowDewPoint,TLowDewPoint,DomWindDir,HeatDegDays,CoolDegDays,HighSolarRad,THighSolarRad,HighUV,THighUV,HWindGBearSym,DomWindDirSym)";
 		}
 
 		internal void SetStartOfMonthlyInsertSQL()
 		{
 			StartOfMonthlyInsertSQL = "INSERT IGNORE INTO " + MySqlMonthlyTable +
-									  " (LogDateTime,Temp,Humidity,Dewpoint,Windspeed,Windgust,Windbearing,RainRate,TodayRainSoFar,Pressure,Raincounter,InsideTemp,InsideHumidity,LatestWindGust,WindChill,HeatIndex,UVindex,SolarRad,Evapotrans,AnnualEvapTran,ApparentTemp,MaxSolarRad,HrsSunShine,CurrWindBearing,RG11rain,RainSinceMidnight,WindbearingSym,CurrWindBearingSym)";
+										" (LogDateTime,Temp,Humidity,Dewpoint,Windspeed,Windgust,Windbearing,RainRate,TodayRainSoFar,Pressure,Raincounter,InsideTemp,InsideHumidity,LatestWindGust,WindChill,HeatIndex,UVindex,SolarRad,Evapotrans,AnnualEvapTran,ApparentTemp,MaxSolarRad,HrsSunShine,CurrWindBearing,RG11rain,RainSinceMidnight,WindbearingSym,CurrWindBearingSym)";
 		}
 
 
@@ -1626,7 +1630,7 @@ private void OnDisconnect(UserContext context)
 
 		private void OnConnected(UserContext context)
 		{
-            LogDebugMessage("Connected From : " + context.ClientAddress.ToString());
+			LogDebugMessage("Connected From : " + context.ClientAddress.ToString());
 		}
 
 		private void OnConnect(UserContext context)
@@ -1639,12 +1643,12 @@ private void OnConnect(UserContext context)
 
 		private void OnSend(UserContext context)
 		{
-            LogDebugMessage("OnSend From : " + context.ClientAddress.ToString());
+			LogDebugMessage("OnSend From : " + context.ClientAddress.ToString());
 		}
 
 		private void OnReceive(UserContext context)
 		{
-            LogDebugMessage("WS receive : " + context.DataFrame.ToString());
+			LogDebugMessage("WS receive : " + context.DataFrame.ToString());
 		}
 */
 		private void InitialiseRG11()
@@ -1720,7 +1724,11 @@ private void APRSTimerTick(object sender, ElapsedEventArgs e)
 
 		private void WebTimerTick(object sender, ElapsedEventArgs e)
 		{
-			if (!WebUpdating)
+			if (WebUpdating)
+			{
+				LogMessage("Warning, previous web update is still in progress, skipping this interval");
+			}
+			else
 			{
 				WebUpdating = true;
 				ftpThread = new Thread(DoHTMLFiles);
@@ -1739,8 +1747,7 @@ internal async void UpdateTwitter()
 			LogDebugMessage("Starting Twitter update");
 			var auth = new XAuthAuthorizer
 			{
-				CredentialStore =
-							   new XAuthCredentials { ConsumerKey = twitterKey, ConsumerSecret = twitterSecret, UserName = Twitteruser, Password = TwitterPW }
+				CredentialStore = new XAuthCredentials { ConsumerKey = twitterKey, ConsumerSecret = twitterSecret, UserName = Twitteruser, Password = TwitterPW }
 			};
 
 			if (TwitterOauthToken == "unknown")
@@ -1789,7 +1796,7 @@ internal async void UpdateTwitter()
 
 				LogDebugMessage("Updating Twitter: " + status);
 
-                Status tweet;
+				Status tweet;
 
 				try
 				{
@@ -1963,60 +1970,88 @@ internal async void UpdateWCloud(DateTime timestamp)
 
 		internal void RealtimeTimerTick(object sender, ElapsedEventArgs elapsedEventArgs)
 		{
-            if (!RealtimeInProgress)
-            {
-                try
-                {
-                    RealtimeInProgress = true;
-                    if (RealtimeFTPEnabled)
-                    {
-                        if (!RealtimeFTP.IsConnected)
-                        {
-                            try
-                            {
-                                LogDebugMessage("Realtime ftp not connected - reconnecting");
-                                RealtimeFTP.Connect();
-                            }
-                            catch (Exception ex)
-                            {
-                                LogMessage("Error connecting ftp - " + ex.Message);
-                            }
-
-                            //RealtimeFTP.EnableThreadSafeDataConnections = false; // use same connection for all transfers
-                        }
-
-                        try
-                        {
-                            //LogDebugMessage("Create realtime file");
-                            CreateRealtimeFile();
-                            //LogDebugMessage("Create extra realtime files");
-                            CreateRealtimeHTMLfiles();
-                            //LogDebugMessage("Upload realtime files");
-                            RealtimeFTPUpload();
-                        }
-                        catch (Exception ex)
-                        {
-                            LogMessage("Error during realtime update: " + ex.Message);
-                        }
-                    }
-                    else
-                    {
-                        // No FTP, just process files
-                        CreateRealtimeFile();
-                        CreateRealtimeHTMLfiles();
-                    }
-
-                    if (!string.IsNullOrEmpty(RealtimeProgram))
-                    {
-                        //LogDebugMessage("Execute realtime program");
-                        ExecuteProgram(RealtimeProgram, RealtimeParams);
-                    }
-                }
-                finally
-                {
-                    RealtimeInProgress = false;
-                }
-            }
+			bool connectionFailed = false;
+			// We are not overly fussed about locking as the thread start times are sufficently far apart
+			if (RealtimeInProgress)
+			{
+				LogMessage("Warning, previous realtime ftp still in progress, skipping this period.");
+			}
+			else
+			{
+				RealtimeInProgress = true;
+				try
+				{
+					// Process any files
+					CreateRealtimeFile();
+					CreateRealtimeHTMLfiles();
+
+					if (RealtimeFTPEnabled)
+					{
+						if (RealtimeFTP.Host == null)
+						{
+							// This only happens if the user enables realtime FTP after starting Cumulus
+							RealtimeFTPLogin();
+						}
+						// Force a test of the connection, IsConnected is not always reliable
+						try
+						{
+							string pwd = RealtimeFTP.GetWorkingDirectory();
+							if (pwd.Length == 0)
+							{
+								connectionFailed = true;
+							}
+						}
+						catch (Exception ex)
+						{
+							LogDebugMessage("Test of realtime FTP connection failed: " + ex.Message);
+							connectionFailed = true;
+						}
+						if (!(RealtimeFTP.IsConnected) || connectionFailed)
+						{
+							LogDebugMessage("Realtime ftp not connected - reconnecting");
+							try
+							{
+								RealtimeFTP.Disconnect();
+							}
+							catch
+							{
+								// do nothing on any disconnect error
+							}
+							try
+							{
+								RealtimeFTP.Connect();
+							}
+							catch (Exception ex)
+							{
+								LogMessage("Error connecting ftp - " + ex.Message);
+							}
+						}
+
+						try
+						{
+							RealtimeFTPUpload();
+						}
+						catch (Exception ex)
+						{
+							LogMessage("Error during realtime FTP update: " + ex.Message);
+						}
+					}
+
+					if (!string.IsNullOrEmpty(RealtimeProgram))
+					{
+						//LogDebugMessage("Execute realtime program");
+						ExecuteProgram(RealtimeProgram, RealtimeParams);
+					}
+				}
+				catch (Exception ex)
+				{
+					LogMessage("Error during realtime update: " + ex.Message);
+				}
+				finally
+				{
+					RealtimeInProgress = false;
+				}
+			}
 		}
 
 		private void RealtimeFTPUpload()
@@ -2027,12 +2062,12 @@ private void RealtimeFTPUpload()
 			if (ftp_directory == "")
 			{
 				filepath = "realtime.txt";
-			    gaugesfilepath = "realtimegauges.txt";
+				gaugesfilepath = "realtimegauges.txt";
 			}
 			else
 			{
 				filepath = ftp_directory + "/realtime.txt";
-			    gaugesfilepath = ftp_directory + "/realtimegauges.txt";
+				gaugesfilepath = ftp_directory + "/realtimegauges.txt";
 			}
 
 			if (RealtimeTxtFTP)
@@ -2040,11 +2075,11 @@ private void RealtimeFTPUpload()
 				UploadFile(RealtimeFTP, RealtimeFile, filepath);
 			}
 
-		    if (RealtimeGaugesTxtFTP)
-		    {
-                ProcessTemplateFile(RealtimeGaugesTxtTFile,RealtimeGaugesTxtFile, realtimeTokenParser);
-		        UploadFile(RealtimeFTP, RealtimeGaugesTxtFile,gaugesfilepath);
-		    }
+			if (RealtimeGaugesTxtFTP)
+			{
+				ProcessTemplateFile(RealtimeGaugesTxtTFile, RealtimeGaugesTxtFile, realtimeTokenParser);
+				UploadFile(RealtimeFTP, RealtimeGaugesTxtFile, gaugesfilepath);
+			}
 
 			// Extra files
 			for (int i = 0; i < numextrafiles; i++)
@@ -2154,11 +2189,11 @@ private List<string> ParseParams(string line)
 			{
 				if (Char.IsWhiteSpace(line[i]))
 				{
-				  if (!insideQuotes && start != -1)
-				  {
-					parts.Add(line.Substring(start, i - start));
-					start = -1;
-				  }
+					if (!insideQuotes && start != -1)
+					{
+						parts.Add(line.Substring(start, i - start));
+						start = -1;
+					}
 				}
 				else if (line[i] == '"')
 				{
@@ -2676,7 +2711,7 @@ private void ReadIniFile()
 			DavisInitWaitTime = ini.GetValue("Station", "DavisInitWaitTime", 200);
 			DavisIPResponseTime = ini.GetValue("Station", "DavisIPResponseTime", 1000);
 			DavisReadTimeout = ini.GetValue("Station", "DavisReadTimeout", 1000);
-            DavisIncrementPressureDP = ini.GetValue("Station", "DavisIncrementPressureDP", true);
+			DavisIncrementPressureDP = ini.GetValue("Station", "DavisIncrementPressureDP", true);
 			if (StationType == StationTypes.VantagePro)
 			{
 				UseDavisLoop2 = false;
@@ -2862,7 +2897,7 @@ private void ReadIniFile()
 			LogMessage("Cumulus start date: " + RecordsBeganDate);
 
 			ImetWaitTime = ini.GetValue("Station", "ImetWaitTime", 500);
-		    ImetUpdateLogPointer = ini.GetValue("Station", "ImetUpdateLogPointer", true);
+			ImetUpdateLogPointer = ini.GetValue("Station", "ImetUpdateLogPointer", true);
 
 			UseDataLogger = ini.GetValue("Station", "UseDataLogger", true);
 			UseCumulusForecast = ini.GetValue("Station", "UseCumulusForecast", false);
@@ -2962,7 +2997,7 @@ private void ReadIniFile()
 			RealtimeEnabled = ini.GetValue("FTP site", "EnableRealtime", false);
 			RealtimeFTPEnabled = ini.GetValue("FTP site", "RealtimeFTPEnabled", false);
 			RealtimeTxtFTP = ini.GetValue("FTP site", "RealtimeTxtFTP", false);
-		    RealtimeGaugesTxtFTP = ini.GetValue("FTP site", "RealtimeGaugesTxtFTP", false);
+			RealtimeGaugesTxtFTP = ini.GetValue("FTP site", "RealtimeGaugesTxtFTP", false);
 			RealtimeInterval = ini.GetValue("FTP site", "RealtimeInterval", 30000);
 			if (RealtimeInterval < 1) { RealtimeInterval = 1; }
 			//RealtimeTimer.Change(0,RealtimeInterval);
@@ -3192,8 +3227,8 @@ private void ReadIniFile()
 			SolarMinimum = ini.GetValue("Solar", "SolarMinimum", 0);
 			LuxToWM2 = ini.GetValue("Solar", "LuxToWM2", 0.0079);
 			UseBlakeLarsen = ini.GetValue("Solar", "UseBlakeLarsen", false);
-            SolarCalc = ini.GetValue("Solar", "SolarCalc", 0);
-            BrasTurbidity = ini.GetValue("Solar", "BrasTurbidity", 2.0);
+			SolarCalc = ini.GetValue("Solar", "SolarCalc", 0);
+			BrasTurbidity = ini.GetValue("Solar", "BrasTurbidity", 2.0);
 
 			NOAAname = ini.GetValue("NOAA", "Name", " ");
 			NOAAcity = ini.GetValue("NOAA", "City", " ");
@@ -3489,7 +3524,7 @@ internal void WriteIniFile()
 			ini.SetValue("FTP site", "EnableRealtime", RealtimeEnabled);
 			ini.SetValue("FTP site", "RealtimeFTPEnabled", RealtimeFTPEnabled);
 			ini.SetValue("FTP site", "RealtimeTxtFTP", RealtimeTxtFTP);
-            ini.SetValue("FTP site", "RealtimeGaugesTxtFTP", RealtimeGaugesTxtFTP);
+			ini.SetValue("FTP site", "RealtimeGaugesTxtFTP", RealtimeGaugesTxtFTP);
 			ini.SetValue("FTP site", "RealtimeInterval", RealtimeInterval);
 			ini.SetValue("FTP site", "UpdateInterval", UpdateInterval);
 			ini.SetValue("FTP site", "IncludeSTD", IncludeStandardFiles);
@@ -3675,11 +3710,11 @@ internal void WriteIniFile()
 			ini.SetValue("Solar", "RStransfactor", RStransfactor);
 			ini.SetValue("Solar", "SolarMinimum", SolarMinimum);
 			ini.SetValue("Solar", "UseBlakeLarsen", UseBlakeLarsen);
-            ini.SetValue("Solar", "SolarCalc", SolarCalc);
-            ini.SetValue("Solar", "BrasTurbidity", BrasTurbidity);
+			ini.SetValue("Solar", "SolarCalc", SolarCalc);
+			ini.SetValue("Solar", "BrasTurbidity", BrasTurbidity);
 
 
-            ini.SetValue("NOAA", "Name", NOAAname);
+			ini.SetValue("NOAA", "Name", NOAAname);
 			ini.SetValue("NOAA", "City", NOAAcity);
 			ini.SetValue("NOAA", "State", NOAAstate);
 			ini.SetValue("NOAA", "12hourformat", NOAA12hourformat);
@@ -4121,11 +4156,11 @@ private void ReadStringsFile()
 
 		public int SunThreshold { get; set; }
 
-        public int SolarCalc { get; set; }
+		public int SolarCalc { get; set; }
 
-        public double BrasTurbidity { get; set; }
+		public double BrasTurbidity { get; set; }
 
-        public int xapPort { get; set; }
+		public int xapPort { get; set; }
 
 		public string xapUID { get; set; }
 
@@ -4263,7 +4298,7 @@ private void ReadStringsFile()
 
 		public int ImetWaitTime { get; set; }
 
-        public bool ImetUpdateLogPointer { get; set; }
+		public bool ImetUpdateLogPointer { get; set; }
 
 		public bool DavisConsoleHighGust { get; set; }
 
@@ -4457,15 +4492,15 @@ private WeatherStation Station
 		private readonly string ThisYearTFile;
 		private readonly string GaugesTFile;
 		private readonly string RealtimeFile = "realtime.txt";
-	    private readonly string RealtimeGaugesTxtTFile;
-        private readonly string RealtimeGaugesTxtFile;
-        private readonly string TwitterTxtFile;
+		private readonly string RealtimeGaugesTxtTFile;
+		private readonly string RealtimeGaugesTxtFile;
+		private readonly string TwitterTxtFile;
 		public bool IncludeStandardFiles = true;
 		public bool IncludeGraphDataFiles;
 		public bool TwitterSendLocation;
 		private const int numwebtextfiles = 9;
 		private FtpClient RealtimeFTP = new FtpClient();
-		private bool RealtimeInProgress = false;
+		private volatile bool RealtimeInProgress = false;
 		public bool SendSoilTemp1ToWund;
 		public bool SendSoilTemp2ToWund;
 		public bool SendSoilTemp3ToWund;
@@ -4682,7 +4717,7 @@ public void DoLogFile(DateTime timestamp, bool live) // Writes an entry to the n
 				file.Write(station.AnnualETTotal.ToString(ETFormat) + ListSeparator);
 				file.Write(station.ApparentTemperature.ToString(TempFormat) + ListSeparator);
 				file.Write((Math.Round(station.CurrentSolarMax)) + ListSeparator);
-				file.Write(station.SunshineHours.ToString("N1") + ListSeparator);
+				file.Write(station.SunshineHours.ToString(SunFormat) + ListSeparator);
 				file.Write(station.Bearing + ListSeparator);
 				file.Write(station.RG11RainToday.ToString(RainFormat) + ListSeparator);
 				file.WriteLine(station.RainSinceMidnight.ToString(RainFormat));
@@ -4713,7 +4748,7 @@ public void DoLogFile(DateTime timestamp, bool live) // Writes an entry to the n
 								station.HeatIndex.ToString(TempFormat, InvC) + "," + station.UV.ToString(UVFormat, InvC) + "," + station.SolarRad + "," +
 								station.ET.ToString(ETFormat, InvC) + "," + station.AnnualETTotal.ToString(ETFormat, InvC) + "," +
 								station.ApparentTemperature.ToString(TempFormat, InvC) + "," + (Math.Round(station.CurrentSolarMax)) + "," +
-								station.SunshineHours.ToString("N1", InvC) + "," + station.Bearing + "," + station.RG11RainToday.ToString(RainFormat, InvC) + "," +
+								station.SunshineHours.ToString(SunFormat, InvC) + "," + station.Bearing + "," + station.RG11RainToday.ToString(RainFormat, InvC) + "," +
 								station.RainSinceMidnight.ToString(RainFormat, InvC) + ",'" + station.CompassPoint(station.AvgBearing) + "','" +
 								station.CompassPoint(station.Bearing) + "')";
 
@@ -5381,111 +5416,116 @@ public void ExecuteProgram(string externalProgram, string externalParams)
 
 		public void DoHTMLFiles()
 		{
-			if (!RealtimeEnabled)
+			try
 			{
-				CreateRealtimeFile();
-			}
+				if (!RealtimeEnabled)
+				{
+					CreateRealtimeFile();
+				}
 
-			//LogDebugMessage("Creating standard HTML files");
-			ProcessTemplateFile(IndexTFile, Indexfile,tokenParser);
-			ProcessTemplateFile(TodayTFile, Todayfile,tokenParser);
-			ProcessTemplateFile(YesterdayTFile, Yesterfile,tokenParser);
-			ProcessTemplateFile(RecordTFile, Recordfile, tokenParser);
-			ProcessTemplateFile(MonthlyRecordTFile, MonthlyRecordfile,tokenParser);
-			ProcessTemplateFile(TrendsTFile, Trendsfile, tokenParser);
-			ProcessTemplateFile(ThisMonthTFile, ThisMonthfile, tokenParser);
-			ProcessTemplateFile(ThisYearTFile, ThisYearfile, tokenParser);
-			ProcessTemplateFile(GaugesTFile, Gaugesfile,tokenParser);
-			//LogDebugMessage("Done creating standard HTML files");
-			if (IncludeGraphDataFiles)
-			{
-				//LogDebugMessage("Creating graph data files");
-				station.CreateGraphDataFiles();
-				//LogDebugMessage("Done creating graph data files");
-			}
-			//LogDebugMessage("Creating extra files");
-			// handle any extra files
-			for (int i = 0; i < numextrafiles; i++)
-			{
-				if (!ExtraFiles[i].realtime && !ExtraFiles[i].endofday)
+				//LogDebugMessage("Creating standard HTML files");
+				ProcessTemplateFile(IndexTFile, Indexfile, tokenParser);
+				ProcessTemplateFile(TodayTFile, Todayfile, tokenParser);
+				ProcessTemplateFile(YesterdayTFile, Yesterfile, tokenParser);
+				ProcessTemplateFile(RecordTFile, Recordfile, tokenParser);
+				ProcessTemplateFile(MonthlyRecordTFile, MonthlyRecordfile, tokenParser);
+				ProcessTemplateFile(TrendsTFile, Trendsfile, tokenParser);
+				ProcessTemplateFile(ThisMonthTFile, ThisMonthfile, tokenParser);
+				ProcessTemplateFile(ThisYearTFile, ThisYearfile, tokenParser);
+				ProcessTemplateFile(GaugesTFile, Gaugesfile, tokenParser);
+				//LogDebugMessage("Done creating standard HTML files");
+				if (IncludeGraphDataFiles)
 				{
-					var uploadfile = ExtraFiles[i].local;
-					if (uploadfile == "<currentlogfile>")
+					//LogDebugMessage("Creating graph data files");
+					station.CreateGraphDataFiles();
+					//LogDebugMessage("Done creating graph data files");
+				}
+				//LogDebugMessage("Creating extra files");
+				// handle any extra files
+				for (int i = 0; i < numextrafiles; i++)
+				{
+					if (!ExtraFiles[i].realtime && !ExtraFiles[i].endofday)
 					{
-						uploadfile = GetLogFileName(DateTime.Now);
-					}
-					var remotefile = ExtraFiles[i].remote;
-					remotefile = remotefile.Replace("<currentlogfile>", Path.GetFileName(GetLogFileName(DateTime.Now)));
+						var uploadfile = ExtraFiles[i].local;
+						if (uploadfile == "<currentlogfile>")
+						{
+							uploadfile = GetLogFileName(DateTime.Now);
+						}
+						var remotefile = ExtraFiles[i].remote;
+						remotefile = remotefile.Replace("<currentlogfile>", Path.GetFileName(GetLogFileName(DateTime.Now)));
 
-					if ((uploadfile != "") && (File.Exists(uploadfile)) && (remotefile != ""))
-					{
-						if (ExtraFiles[i].process)
+						if ((uploadfile != "") && (File.Exists(uploadfile)) && (remotefile != ""))
 						{
-							//LogDebugMessage("Processing extra file "+uploadfile);
-							// process the file
-							var utf8WithoutBom = new System.Text.UTF8Encoding(false);
-							var encoding = UTF8encode ? utf8WithoutBom : System.Text.Encoding.GetEncoding("iso-8859-1");
-							tokenParser.encoding = encoding;
-							tokenParser.SourceFile = uploadfile;
-							var output = tokenParser.ToString();
-							uploadfile += "tmp";
-							try
+							if (ExtraFiles[i].process)
 							{
-								using (StreamWriter file = new StreamWriter(uploadfile, false, encoding))
+								//LogDebugMessage("Processing extra file "+uploadfile);
+								// process the file
+								var utf8WithoutBom = new System.Text.UTF8Encoding(false);
+								var encoding = UTF8encode ? utf8WithoutBom : System.Text.Encoding.GetEncoding("iso-8859-1");
+								tokenParser.encoding = encoding;
+								tokenParser.SourceFile = uploadfile;
+								var output = tokenParser.ToString();
+								uploadfile += "tmp";
+								try
 								{
-									file.Write(output);
+									using (StreamWriter file = new StreamWriter(uploadfile, false, encoding))
+									{
+										file.Write(output);
 
-									file.Close();
+										file.Close();
+									}
 								}
+								catch (Exception ex)
+								{
+									LogDebugMessage("Error writing file " + uploadfile);
+									LogDebugMessage(ex.Message);
+								}
+								//LogDebugMessage("Finished processing extra file " + uploadfile);
 							}
-							catch (Exception ex)
-							{
-								LogDebugMessage("Error writing file " + uploadfile);
-								LogDebugMessage(ex.Message);
-							}
-							//LogDebugMessage("Finished processing extra file " + uploadfile);
-						}
 
-						if (!ExtraFiles[i].FTP)
-						{
-							// just copy the file
-							//LogDebugMessage("Copying extra file " + uploadfile);
-							try
+							if (!ExtraFiles[i].FTP)
 							{
-								File.Copy(uploadfile, remotefile, true);
-							}
-							catch (Exception ex)
-							{
-								LogDebugMessage("Error copying extra file: " + ex.Message);
+								// just copy the file
+								//LogDebugMessage("Copying extra file " + uploadfile);
+								try
+								{
+									File.Copy(uploadfile, remotefile, true);
+								}
+								catch (Exception ex)
+								{
+									LogDebugMessage("Error copying extra file: " + ex.Message);
+								}
+								//LogDebugMessage("Finished copying extra file " + uploadfile);
 							}
-							//LogDebugMessage("Finished copying extra file " + uploadfile);
 						}
 					}
 				}
-			}
 
-			if (!string.IsNullOrEmpty(ExternalProgram))
-			{
-				LogDebugMessage("Executing program " + ExternalProgram + " " + ExternalParams);
-				try
+				if (!string.IsNullOrEmpty(ExternalProgram))
 				{
-					ExecuteProgram(ExternalProgram, ExternalParams);
-					LogDebugMessage("External program started");
+					LogDebugMessage("Executing program " + ExternalProgram + " " + ExternalParams);
+					try
+					{
+						ExecuteProgram(ExternalProgram, ExternalParams);
+						LogDebugMessage("External program started");
+					}
+					catch (Exception ex)
+					{
+						LogMessage("Error starting external program: " + ex.Message);
+					}
 				}
-				catch (Exception ex)
+
+				//LogDebugMessage("Done creating extra files");
+
+				if (!String.IsNullOrEmpty(ftp_host))
 				{
-					LogMessage("Error starting external program: " + ex.Message);
+					DoFTPLogin();
 				}
 			}
-
-			//LogDebugMessage("Done creating extra files");
-
-			if (!String.IsNullOrEmpty(ftp_host))
+			finally
 			{
-				DoFTPLogin();
+				WebUpdating = false;
 			}
-
-			WebUpdating = false;
 		}
 
 		void Client_ValidateCertificate(FtpClient control, FtpSslValidationEventArgs e)
@@ -5508,11 +5548,11 @@ private void DoFTPLogin()
 					conn.EncryptionMode = FtpEncryptionMode.Explicit;
 					conn.DataConnectionEncryption = true;
 					conn.ValidateCertificate += Client_ValidateCertificate;
-                    // b3045 - switch from System.Net.Ftp.Client to FluentFTP allows us to specifiy protocols
-                    conn.SslProtocols = SslProtocols.Default | SslProtocols.Tls11 | SslProtocols.Tls12;
-                }
+					// b3045 - switch from System.Net.Ftp.Client to FluentFTP allows us to specifiy protocols
+					conn.SslProtocols = SslProtocols.Default | SslProtocols.Tls11 | SslProtocols.Tls12;
+				}
 
-                if (ActiveFTPMode)
+				if (ActiveFTPMode)
 				{
 					conn.DataConnectionType = FtpDataConnectionType.PORT;
 				}
@@ -5562,6 +5602,7 @@ private void DoFTPLogin()
 
 					//LogDebugMessage("Uploading extra files");
 					// Extra files
+					FtpTrace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " Uploading Extra files");
 					for (int i = 0; i < numextrafiles; i++)
 					{
 						var uploadfile = ExtraFiles[i].local;
@@ -5647,8 +5688,8 @@ private void DoFTPLogin()
 					}
 				}
 
-                // b3045 - dispose of connection
-                conn.Disconnect();
+				// b3045 - dispose of connection
+				conn.Disconnect();
 				FtpTrace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " Disconnected from " + ftp_host);
 			}
 		}
@@ -5664,15 +5705,15 @@ private void UploadFile(FtpClient conn, string localfile, string remotefile)
 			{
 				if (DeleteBeforeUpload)
 				{
-                    // delete the existing file
-                    try
-                    {
-                        conn.DeleteFile(remotefile);
-                    }
-                    catch (Exception ex)
-                    {
-                        LogMessage("FTP error deleting " + remotefile + " : " + ex.Message);
-                    }
+					// delete the existing file
+					try
+					{
+						conn.DeleteFile(remotefile);
+					}
+					catch (Exception ex)
+					{
+						LogMessage("FTP error deleting " + remotefile + " : " + ex.Message);
+					}
 				}
 
 				using (Stream ostream = conn.OpenWrite(remotefilename))
@@ -5699,11 +5740,17 @@ private void UploadFile(FtpClient conn, string localfile, string remotefile)
 					}
 				}
 
-
 				if (FTPRename)
 				{
 					// rename the file
-					conn.Rename(remotefilename, remotefile);
+					try
+					{
+						conn.Rename(remotefilename, remotefile);
+					}
+					catch (Exception ex)
+					{
+						LogMessage("FTP error renaming " + remotefilename + " to " + remotefile + " : " + ex.Message);
+					}
 				}
 
 				FtpTrace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " Completed uploading " + localfile + " to " + remotefile);
@@ -5867,7 +5914,7 @@ 58     1          Is sunny?
 				file.Write((Convert.ToInt32(station.CloudBase)).ToString() + ' '); // 53
 				file.Write(CloudBaseInFeet ? "ft " : "m ");
 				file.Write(ReplaceCommas(station.ApparentTemperature.ToString(TempFormat)) + ' '); // 55
-				file.Write(ReplaceCommas(station.SunshineHours.ToString("F1")) + ' '); // 56
+				file.Write(ReplaceCommas(station.SunshineHours.ToString(SunFormat)) + ' '); // 56
 				file.Write((Convert.ToInt32(station.CurrentSolarMax)).ToString() + ' '); // 57
 				file.WriteLine(station.IsSunny ? "1 " : "0 ");
 
@@ -5899,7 +5946,7 @@ 58     1          Is sunny?
 								((int)station.SolarRad).ToString() + ',' + station.AvgBearing.ToString() + ',' + station.RainLastHour.ToString(RainFormat, InvC) + ',' +
 								station.Forecastnumber.ToString() + ",'" + (IsDaylight() ? "1" : "0") + "','" + (station.SensorContactLost ? "1" : "0") + "','" +
 								station.CompassPoint(station.AvgBearing) + "'," + ((int)station.CloudBase).ToString() + ",'" + (CloudBaseInFeet ? "ft" : "m") + "'," +
-								station.ApparentTemperature.ToString(TempFormat, InvC) + ',' + station.SunshineHours.ToString("F1", InvC) + ',' +
+								station.ApparentTemperature.ToString(TempFormat, InvC) + ',' + station.SunshineHours.ToString(SunFormat, InvC) + ',' +
 								((int) Math.Round(station.CurrentSolarMax)).ToString() + ",'" + (station.IsSunny ? "1" : "0") + "')";
 
 
@@ -5931,8 +5978,7 @@ 58     1          Is sunny?
 				if (!string.IsNullOrEmpty(MySqlRealtimeRetention))
 				{
 					// delete old entries
-					cmd.CommandText = "DELETE IGNORE FROM " + MySqlRealtimeTable + " WHERE LogDateTime < DATE_SUB('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "', INTERVAL " +
-									  MySqlRealtimeRetention + ")";
+					cmd.CommandText = "DELETE IGNORE FROM " + MySqlRealtimeTable + " WHERE LogDateTime < DATE_SUB('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "', INTERVAL " + MySqlRealtimeRetention + ")";
 					//LogMessage(queryString);
 
 					try
@@ -6329,9 +6375,9 @@ private void RealtimeFTPLogin()
 			RealtimeFTP.Host = ftp_host;
 			RealtimeFTP.Port = ftp_port;
 			RealtimeFTP.Credentials = new NetworkCredential(ftp_user, ftp_password);
-            // b3045 - Reduce the default polling interval to try and keep the session alive
-            RealtimeFTP.SocketKeepAlive = true;
-            //RealtimeFTP.SocketPollInterval = 2000; // 2 seconds, defaults to 15 seconds
+			// b3045 - Reduce the default polling interval to try and keep the session alive
+			RealtimeFTP.SocketKeepAlive = true;
+			//RealtimeFTP.SocketPollInterval = 2000; // 2 seconds, defaults to 15 seconds
 
 
 			if (Sslftp)
@@ -6339,10 +6385,10 @@ private void RealtimeFTPLogin()
 				RealtimeFTP.EncryptionMode = FtpEncryptionMode.Explicit;
 				RealtimeFTP.DataConnectionEncryption = true;
 				RealtimeFTP.ValidateCertificate += Client_ValidateCertificate;
-                // b3045 - switch from System.Net.Ftp.Client to FluentFTP allows us to specifiy protocols
-                RealtimeFTP.SslProtocols = SslProtocols.Default | SslProtocols.Tls11 | SslProtocols.Tls12;
+				// b3045 - switch from System.Net.Ftp.Client to FluentFTP allows us to specifiy protocols
+				RealtimeFTP.SslProtocols = SslProtocols.Default | SslProtocols.Tls11 | SslProtocols.Tls12;
 
-            }
+			}
 
 
 			if (ftp_host != "" && ftp_host != " ")
@@ -6355,6 +6401,7 @@ private void RealtimeFTPLogin()
 				catch (Exception ex)
 				{
 					LogMessage("Error connecting ftp - " + ex.Message);
+					RealtimeFTP.Disconnect();
 				}
 
 				RealtimeFTP.EnableThreadSafeDataConnections = false; // use same connection for all transfers
@@ -6466,7 +6513,7 @@ private async void WBCatchup()
 				}
 				catch (Exception ex)
 				{
-                    LogMessage("Wbug update: " + ex.Message);
+					LogMessage("Wbug update: " + ex.Message);
 				}
 			}
 
@@ -6758,33 +6805,33 @@ private void AddToWeatherbugList(DateTime timestamp)
 		public void SetMonthlySqlCreateString()
 		{
 			CreateMonthlySQL = "CREATE TABLE " + MySqlMonthlyTable + " (LogDateTime DATETIME NOT NULL,Temp decimal(4," + TempDPlaces + ") NOT NULL,Humidity decimal(4," + HumDPlaces +
-							   ") NOT NULL,Dewpoint decimal(4," + TempDPlaces + ") NOT NULL,Windspeed decimal(4," + WindDPlaces + ") NOT NULL,Windgust decimal(4," + WindDPlaces +
-							   ") NOT NULL,Windbearing VARCHAR(3) NOT NULL,RainRate decimal(4," + RainDPlaces + ") NOT NULL,TodayRainSoFar decimal(4," + RainDPlaces +
-							   ") NOT NULL,Pressure decimal(6," + PressDPlaces + ") NOT NULL,Raincounter decimal(6," + RainDPlaces + ") NOT NULL,InsideTemp decimal(4," +
-							   TempDPlaces + ") NOT NULL,InsideHumidity decimal(4," + HumDPlaces + ") NOT NULL,LatestWindGust decimal(5," + WindDPlaces +
-							   ") NOT NULL,WindChill decimal(4," + TempDPlaces + ") NOT NULL,HeatIndex decimal(4," + TempDPlaces + ") NOT NULL,UVindex decimal(4," + UVDPlaces +
-							   "),SolarRad decimal(5,1),Evapotrans decimal(4," + RainDPlaces + "),AnnualEvapTran decimal(5," + RainDPlaces + "),ApparentTemp decimal(4," +
-							   TempDPlaces + "),MaxSolarRad decimal(5,1),HrsSunShine decimal(3,1),CurrWindBearing varchar(3),RG11rain decimal(4," + RainDPlaces +
-							   "),RainSinceMidnight decimal(4," + RainDPlaces +
-							   "), WindbearingSym varchar(3),CurrWindBearingSym varchar(3),PRIMARY KEY (LogDateTime)) COMMENT = \"Monthly logs from Cumulus\"";
+								") NOT NULL,Dewpoint decimal(4," + TempDPlaces + ") NOT NULL,Windspeed decimal(4," + WindDPlaces + ") NOT NULL,Windgust decimal(4," + WindDPlaces +
+								") NOT NULL,Windbearing VARCHAR(3) NOT NULL,RainRate decimal(4," + RainDPlaces + ") NOT NULL,TodayRainSoFar decimal(4," + RainDPlaces +
+								") NOT NULL,Pressure decimal(6," + PressDPlaces + ") NOT NULL,Raincounter decimal(6," + RainDPlaces + ") NOT NULL,InsideTemp decimal(4," +
+								TempDPlaces + ") NOT NULL,InsideHumidity decimal(4," + HumDPlaces + ") NOT NULL,LatestWindGust decimal(5," + WindDPlaces +
+								") NOT NULL,WindChill decimal(4," + TempDPlaces + ") NOT NULL,HeatIndex decimal(4," + TempDPlaces + ") NOT NULL,UVindex decimal(4," + UVDPlaces +
+								"),SolarRad decimal(5,1),Evapotrans decimal(4," + RainDPlaces + "),AnnualEvapTran decimal(5," + RainDPlaces + "),ApparentTemp decimal(4," +
+								TempDPlaces + "),MaxSolarRad decimal(5,1),HrsSunShine decimal(3," + SunshineDPlaces + "),CurrWindBearing varchar(3),RG11rain decimal(4," + RainDPlaces +
+								"),RainSinceMidnight decimal(4," + RainDPlaces +
+								"), WindbearingSym varchar(3),CurrWindBearingSym varchar(3),PRIMARY KEY (LogDateTime)) COMMENT = \"Monthly logs from Cumulus\"";
 		}
 
 		internal void SetDayfileSqlCreateString()
 		{
 			CreateDayfileSQL = "CREATE TABLE " + MySqlDayfileTable + " (LogDate date NOT NULL ,HighWindGust decimal(4," + WindDPlaces +
-							   ") NOT NULL,HWindGBear varchar(3) NOT NULL,THWindG varchar(5) NOT NULL,MinTemp decimal(5," + TempDPlaces +
-							   ") NOT NULL,TMinTemp varchar(5) NOT NULL,MaxTemp decimal(5," + TempDPlaces + ") NOT NULL,TMaxTemp varchar(5) NOT NULL,MinPress decimal(6," +
-							   PressDPlaces + ") NOT NULL,TMinPress varchar(5) NOT NULL,MaxPress decimal(6," + PressDPlaces +
-							   ") NOT NULL,TMaxPress varchar(5) NOT NULL,MaxRainRate decimal(4," + RainDPlaces + ") NOT NULL,TMaxRR varchar(5) NOT NULL,TotRainFall decimal(6," +
-							   RainDPlaces + ") NOT NULL,AvgTemp decimal(4," + TempDPlaces + ") NOT NULL,TotWindRun decimal(5," + WindRunDPlaces +
-							   ") NOT NULL,HighAvgWSpeed decimal(3," + WindDPlaces + "),THAvgWSpeed varchar(5),LowHum decimal(4," + HumDPlaces +
-							   "),TLowHum varchar(5),HighHum decimal(4," + HumDPlaces + "),THighHum varchar(5),TotalEvap decimal(5," + RainDPlaces +
-							   "),HoursSun decimal(3,1),HighHeatInd decimal(4," + TempDPlaces + "),THighHeatInd varchar(5),HighAppTemp decimal(4," + TempDPlaces +
-							   "),THighAppTemp varchar(5),LowAppTemp decimal(4," + TempDPlaces + "),TLowAppTemp varchar(5),HighHourRain decimal(4," + RainDPlaces +
-							   "),THighHourRain varchar(5),LowWindChill decimal(4," + TempDPlaces + "),TLowWindChill varchar(5),HighDewPoint decimal(4," + TempDPlaces +
-							   "),THighDewPoint varchar(5),LowDewPoint decimal(4," + TempDPlaces +
-							   "),TLowDewPoint varchar(5),DomWindDir varchar(3),HeatDegDays decimal(4,1),CoolDegDays decimal(4,1),HighSolarRad decimal(5,1),THighSolarRad varchar(5),HighUV decimal(3," +
-							   UVDPlaces + "),THighUV varchar(5),HWindGBearSym varchar(3),DomWindDirSym varchar(3),PRIMARY KEY(LogDate)) COMMENT = \"Dayfile from Cumulus\"";
+								") NOT NULL,HWindGBear varchar(3) NOT NULL,THWindG varchar(5) NOT NULL,MinTemp decimal(5," + TempDPlaces +
+								") NOT NULL,TMinTemp varchar(5) NOT NULL,MaxTemp decimal(5," + TempDPlaces + ") NOT NULL,TMaxTemp varchar(5) NOT NULL,MinPress decimal(6," +
+								PressDPlaces + ") NOT NULL,TMinPress varchar(5) NOT NULL,MaxPress decimal(6," + PressDPlaces +
+								") NOT NULL,TMaxPress varchar(5) NOT NULL,MaxRainRate decimal(4," + RainDPlaces + ") NOT NULL,TMaxRR varchar(5) NOT NULL,TotRainFall decimal(6," +
+								RainDPlaces + ") NOT NULL,AvgTemp decimal(4," + TempDPlaces + ") NOT NULL,TotWindRun decimal(5," + WindRunDPlaces +
+								") NOT NULL,HighAvgWSpeed decimal(3," + WindDPlaces + "),THAvgWSpeed varchar(5),LowHum decimal(4," + HumDPlaces +
+								"),TLowHum varchar(5),HighHum decimal(4," + HumDPlaces + "),THighHum varchar(5),TotalEvap decimal(5," + RainDPlaces +
+								"),HoursSun decimal(3," + SunshineDPlaces + "),HighHeatInd decimal(4," + TempDPlaces + "),THighHeatInd varchar(5),HighAppTemp decimal(4," + TempDPlaces +
+								"),THighAppTemp varchar(5),LowAppTemp decimal(4," + TempDPlaces + "),TLowAppTemp varchar(5),HighHourRain decimal(4," + RainDPlaces +
+								"),THighHourRain varchar(5),LowWindChill decimal(4," + TempDPlaces + "),TLowWindChill varchar(5),HighDewPoint decimal(4," + TempDPlaces +
+								"),THighDewPoint varchar(5),LowDewPoint decimal(4," + TempDPlaces +
+								"),TLowDewPoint varchar(5),DomWindDir varchar(3),HeatDegDays decimal(4,1),CoolDegDays decimal(4,1),HighSolarRad decimal(5,1),THighSolarRad varchar(5),HighUV decimal(3," +
+								UVDPlaces + "),THighUV varchar(5),HWindGBearSym varchar(3),DomWindDirSym varchar(3),PRIMARY KEY(LogDate)) COMMENT = \"Dayfile from Cumulus\"";
 		}
 	}
 
diff --git a/CumulusMX/CumulusMX.csproj b/CumulusMX/CumulusMX.csproj
index cfb9c8fd..3940f926 100644
--- a/CumulusMX/CumulusMX.csproj
+++ b/CumulusMX/CumulusMX.csproj
@@ -80,8 +80,8 @@
     <Reference Include="Devart.Data.MySql">
       <HintPath>..\packages\Devart.Data.MySql.dll</HintPath>
     </Reference>
-    <Reference Include="FluentFTP, Version=19.2.3.0, Culture=neutral, PublicKeyToken=f4af092b1d8df44f, processorArchitecture=MSIL">
-      <HintPath>..\packages\FluentFTP.19.2.3\lib\net45\FluentFTP.dll</HintPath>
+    <Reference Include="FluentFTP, Version=21.0.0.0, Culture=neutral, PublicKeyToken=f4af092b1d8df44f, processorArchitecture=MSIL">
+      <HintPath>..\packages\FluentFTP.21.0.0\lib\net45\FluentFTP.dll</HintPath>
     </Reference>
     <Reference Include="HidSharp, Version=2.0.8.0, Culture=neutral, processorArchitecture=MSIL">
       <HintPath>..\packages\HidSharp.2.0.8\lib\net35\HidSharp.dll</HintPath>
diff --git a/CumulusMX/DataStruct.cs b/CumulusMX/DataStruct.cs
index 53911712..58be8938 100644
--- a/CumulusMX/DataStruct.cs
+++ b/CumulusMX/DataStruct.cs
@@ -640,7 +640,7 @@ public string PressTrendRounded
 		[DataMember(Name = "SunshineHours")]
 		public string SunshineHoursRounded
 		{
-			get { return SunshineHours.ToString("F1"); }
+			get { return SunshineHours.ToString(cumulus.SunFormat); }
 			set { }
 		}
 
diff --git a/CumulusMX/DavisStation.cs b/CumulusMX/DavisStation.cs
index b11e40d9..87706e38 100644
--- a/CumulusMX/DavisStation.cs
+++ b/CumulusMX/DavisStation.cs
@@ -22,10 +22,13 @@ internal class DavisStation : WeatherStation
 		//private int min;
 		private int previousMinuteDisconnect = 60;
 		private const int ACK = 6;
+        private const int NACK = 33;
+        private const int CANCEL = 24;
 		private bool clockSetNeeded = false;
 		private int previousMinuteSetClock = 60;
 		private const string newline = "\n";
 		private DateTime lastRecepStatsTime;
+        private int commWaitTimeMs = 200;
 
 		private TcpClient socket;
 
@@ -181,7 +184,7 @@ private string GetFirmwareVersion()
 				{
 					comport.WriteLine(commandString);
 
-					Thread.Sleep(200);
+					Thread.Sleep(commWaitTimeMs);
 
 					// Read the response
 					var bytesRead = 0;
@@ -264,7 +267,7 @@ private string GetReceptionStats()
 				{
 					comport.WriteLine(commandString);
 
-					Thread.Sleep(200);
+					Thread.Sleep(commWaitTimeMs);
 
 					// Read the response
 					var bytesRead = 0;
@@ -666,6 +669,7 @@ private void SendBarRead()
             cumulus.LogDebugMessage("Sending BARREAD");
         
             string response = "";
+            // Expected response = "\n\rOK\n\rNNNNN\n\r" - Where NNNNN = ASCII pressure, inHg * 1000
 
             if (IsSerial)
             {
@@ -674,7 +678,7 @@ private void SendBarRead()
                 {
                     comport.WriteLine(commandString);
 
-                    Thread.Sleep(200);
+                    Thread.Sleep(commWaitTimeMs);
 
                     // Read the response
                     var bytesRead = 0;
@@ -693,10 +697,10 @@ private void SendBarRead()
                     }
                     catch (Exception ex)
                     {
-                        cumulus.LogMessage(ex.Message);
+                        cumulus.LogDebugMessage("SendBarRead: Error - " + ex.Message);
                     }
 
-                    cumulus.LogDebugMessage(BitConverter.ToString(buffer));
+                    cumulus.LogDataMessage("BARREAD Recieved 0x" + BitConverter.ToString(buffer));
                 }
             }
             else
@@ -724,7 +728,7 @@ private void SendBarRead()
                             //cumulus.LogMessage("Received " + ch.ToString("X2"));
                         }
 
-						cumulus.LogDataMessage("Recieved 0x" + BitConverter.ToString(buffer));
+						cumulus.LogDataMessage("BARREAD Recieved 0x" + BitConverter.ToString(buffer));
 					}
 					catch (Exception ex)
 					{
@@ -762,19 +766,9 @@ private bool SendLoopCommand(SerialPort serialPort, string commandString)
 
 					Thread.Sleep(500);
 
-					// Wait for the VP to acknowledge the the receipt of the command - sometimes we get a '\n\r'
-					// in the buffer first or no response is given.  If all else fails, try again.
-					cumulus.LogDebugMessage("Wait for ACK");
-					while (serialPort.BytesToRead > 0 && !Found_ACK)
-					{
-						// Read the current character
-					    if (serialPort.ReadChar() == ACK)
-					    {
-					        Found_ACK = true;
-					        cumulus.LogDebugMessage("ACK received");
-					    }
-					}
-
+                    // Wait for the VP to acknowledge the the receipt of the command - sometimes we get a '\n\r'
+                    // in the buffer first or no response is given.  If all else fails, try again.
+                    Found_ACK = WaitForACK(serialPort);
 					passCount++;
 				}
 
@@ -796,7 +790,6 @@ private bool SendLoopCommand(TcpClient tcpPort, string commandString)
 		{
 			
 			bool Found_ACK = false;
-			const int ACK = 6; // ASCII 6
 			int passCount = 1;
 			const int maxPasses = 4;
 
@@ -814,22 +807,10 @@ private bool SendLoopCommand(TcpClient tcpPort, string commandString)
 					stream.Write(Encoding.ASCII.GetBytes(commandString), 0, commandString.Length);
 					Thread.Sleep(cumulus.DavisIPResponseTime);
 					cumulus.LogDebugMessage("Wait for ACK");
-					// Wait for the VP to acknowledge the the receipt of the command - sometimes we get a '\n\r'
-					// in the buffer first or no response is given.  If all else fails, try again.
-					while (stream.DataAvailable && !Found_ACK)
-					{
-
-						// Read the current character
-						int data = stream.ReadByte();
-						cumulus.LogDataMessage("Received 0x" + data.ToString("X2"));
-						if (data == ACK)
-						{
-							cumulus.LogDebugMessage("Received ACK");
-							Found_ACK = true;
-						}
-					}
-
-					passCount++;
+                    // Wait for the VP to acknowledge the the receipt of the command - sometimes we get a '\n\r'
+                    // in the buffer first or no response is given.  If all else fails, try again.
+                    Found_ACK = WaitForACK(stream);
+                    passCount++;
 				}
 			}
 			catch (Exception ex)
@@ -876,7 +857,7 @@ private void GetAndProcessLoopData(int number)
 						while (loopcount < 20 && comport.BytesToRead < loopDataLength)
 						{
 							// Wait a short period to allow more data into the buffer
-							Thread.Sleep(250);
+							Thread.Sleep(commWaitTimeMs);
 							loopcount++;
 						}
 
@@ -964,7 +945,8 @@ private void GetAndProcessLoopData(int number)
 					cumulus.LogMessage("Data " + (i + 1) + ": " + BitConverter.ToString(loopString));
 				}
 
-				if (!(loopString[0] == 'L' && loopString[1] == 'O' && loopString[2] == 'O'))
+                // Check it is a LOOP packet, starts with "LOO" and 5th byte == 0: LOOP1
+				if (!(loopString[0] == 'L' && loopString[1] == 'O' && loopString[2] == 'O' && Convert.ToByte(loopString[4]) == 0))
 				{
 					cumulus.LogDebugMessage("invalid LOOP packet");
 					// Stop the sending of LOOP packets so we can resynch
@@ -1044,8 +1026,8 @@ private void GetAndProcessLoopData(int number)
 				double wind = ConvertWindMPHToUser(loopData.CurrentWindSpeed);
 				double avgwind = ConvertWindMPHToUser(loopData.AvgWindSpeed);
 
-				// Check for sensible figures (spec says max for large cups is 175mph)
-				if (loopData.CurrentWindSpeed < 175 && loopData.AvgWindSpeed < 175)
+				// Check for sensible figures (spec says max for large cups is 175 mph, but up to 200 mph)
+				if (loopData.CurrentWindSpeed < 200 && loopData.AvgWindSpeed < 200)
 				{
 				    int winddir = loopData.WindDirection;
 
@@ -1361,7 +1343,7 @@ private void GetAndProcessLoop2Data(int number)
 						while (loopcount < 100 && comport.BytesToRead < loopDataLength)
 						{
 							// Wait a short period to allow more data into the buffer
-							Thread.Sleep(200);
+							Thread.Sleep(commWaitTimeMs);
 							loopcount++;
 						}
 
@@ -1409,7 +1391,8 @@ private void GetAndProcessLoop2Data(int number)
 					}
 				}
 
-				if (!(loopString[0] == 'L' && loopString[1] == 'O' && loopString[2] == 'O'))
+                // Check it is a LOOP packet, starts with "LOO" and 5th byte == 1: LOOP2
+                if (!(loopString[0] == 'L' && loopString[1] == 'O' && loopString[2] == 'O' && Convert.ToByte(loopString[4]) == 1))
 				{
 					cumulus.LogDebugMessage("invalid LOOP2 packet");
 					continue;
@@ -1559,8 +1542,6 @@ private void GetArchiveData()
 			cumulus.LogMessage("Date: " + vantageDateStamp);
 			cumulus.LogMessage("Time: " + vantageTimeStamp);
 
-			int currChar;
-
 			if (IsSerial)
 			{
 				comport.DiscardInBuffer();
@@ -1574,12 +1555,12 @@ private void GetArchiveData()
 				cumulus.LogMessage("Sending DMPAFT");
 				comport.WriteLine("DMPAFT");
 
-				// wait for the ACK
-				currChar = comport.ReadChar();
+                Thread.Sleep(commWaitTimeMs);
 
-				if (currChar != ACK)
+				// wait for the ACK
+				if (!WaitForACK(comport))
 				{
-					cumulus.LogMessage("No Ack in response to DMPAFT, received 0x" + currChar.ToString("X2"));
+					cumulus.LogMessage("No Ack in response to DMPAFT");
 					return;
 				}
 			}
@@ -1588,25 +1569,17 @@ private void GetArchiveData()
 				WakeVP(socket);
 				string dmpaft = "DMPAFT\n";
 				stream.Write(Encoding.ASCII.GetBytes(dmpaft), 0, dmpaft.Length);
-				Thread.Sleep(cumulus.DavisIPResponseTime);
 
-				bool Found_ACK = false;
-
-				while (stream.DataAvailable && !Found_ACK)
-				{
-					// Read the current character
-					if (stream.ReadByte() == ACK)
-						Found_ACK = true;
-				}
+				Thread.Sleep(cumulus.DavisIPResponseTime);
 
-				if (!Found_ACK)
-				{
-					cumulus.LogMessage("No Ack in response to DMPAFT");
+                if (!WaitForACK(stream))
+                {
+                    cumulus.LogMessage("No Ack in response to DMPAFT");
 					return;
 				}
 			}
 
-			cumulus.LogMessage("Received response to DMPAFT, sending date and time");
+			cumulus.LogMessage("Received response to DMPAFT, sending start date and time");
 			Trace.Flush();
 
 			// Construct date time string to send next
@@ -1622,33 +1595,28 @@ private void GetArchiveData()
 			data[4] = (byte) (crc/256);
 			data[5] = (byte) (crc%256);
 
-			cumulus.LogMessage(BitConverter.ToString(data));
+			cumulus.LogMessage("Sending: " + BitConverter.ToString(data));
 
 			if (IsSerial)
 			{
 				// send the data
 				comport.Write(data, 0, 6);
 
-				// wait for the ACK
-				cumulus.LogMessage("Wait for ACK...");
-				currChar = comport.ReadChar();
+                Thread.Sleep(commWaitTimeMs);
 
-				if (currChar != ACK)
+				// wait for the ACK
+				if (!WaitForACK(comport))
 				{
-					cumulus.LogMessage("No ACK, received: 0x" + currChar.ToString("X2"));
+					cumulus.LogMessage("No ACK in response to sending date and time");
 					return;
 				}
-				else
-				{
-					cumulus.LogMessage("ACK received");
-				}
 
 				cumulus.LogMessage("Waiting for response");
 				// wait for the response
 				while (comport.BytesToRead < 6)
 				{
 					// Wait a short period to let more data load into the buffer
-					Thread.Sleep(200);
+					Thread.Sleep(commWaitTimeMs);
 				}
 
 				// Read the response
@@ -1668,30 +1636,17 @@ private void GetArchiveData()
 
 				Thread.Sleep(cumulus.DavisIPResponseTime);
 
-				bool Found_ACK = false;
-
-				while (stream.DataAvailable && !Found_ACK)
+				if (!WaitForACK(stream))
 				{
-					// Read the current character
-					currChar = stream.ReadByte();
-					cumulus.LogMessage("Received 0x" + currChar.ToString("X2"));
-					if (currChar == ACK)
-						Found_ACK = true;
+                    cumulus.LogMessage("No ACK in response to sending date and time");
+                    return;
 				}
 
-				if (!Found_ACK)
-				{
-					cumulus.LogMessage("No ACK");
-					return;
-				}
-
-				cumulus.LogMessage("ACK received");
-
 				// Wait until the buffer is full 
 				while (socket.Available < 6)
 				{
 					// Wait a short period to let more data load into the buffer
-					Thread.Sleep(200);
+					Thread.Sleep(cumulus.DavisIPResponseTime);
 				}
 
 				// Read the response
@@ -1741,7 +1696,7 @@ private void GetArchiveData()
 						while (comport.BytesToRead < pageSize && responsePasses < 20)
 						{
 							// Wait a short period to let more data load into the buffer
-							Thread.Sleep(200);
+							Thread.Sleep(commWaitTimeMs);
 							responsePasses++;
 						}
 
@@ -2201,7 +2156,7 @@ private byte[] GetData(SerialPort serialPort, string commandString, int returnLe
 					while (serialPort.BytesToRead < loopString.Length)
 					{
 						// Wait a short period to let more data load into the buffer
-						Thread.Sleep(200);
+						Thread.Sleep(commWaitTimeMs);
 					}
 
 					// Read the first returnLength bytes of the buffer into the array
@@ -2237,7 +2192,7 @@ private byte[] GetData(SerialPort serialPort, string commandString, int returnLe
 		private bool WakeVP(SerialPort serialPort)
 		{
 			int passCount = 1, maxPasses = 4;
-
+            int currChar, newLine = 10;
 
 			try
 			{
@@ -2246,26 +2201,28 @@ private bool WakeVP(SerialPort serialPort)
 				serialPort.DiscardInBuffer();
 				serialPort.DiscardOutBuffer();
 
-				//cumulus.LogMessage("Waking VP");
+				cumulus.LogDebugMessage("Waking VP");
 				// Put a newline character ('\n') out the serial port - the Writeline method terminates with a '\n' of its own
 				serialPort.WriteLine("");
-				//Thread.Sleep(1200);
-				//serialPort.WriteLine("");
-				// Wait for 1.2 seconds 
-				//Thread.Sleep(1200);
+				// Wait for 0.2 second for a response
+				Thread.Sleep(commWaitTimeMs);
 
 				bool woken = false;
-
-				for (int i = 0; i < 5; i++)
+                int i = 0;
+                while (!woken && i < 5)
 				{
-					if (serialPort.BytesToRead != 0)
+					while (serialPort.BytesToRead != 0)
 					{
-						woken = true;
-						//cumulus.LogMessage("Woken: i="+i);
-						break;
+                        currChar = comport.ReadChar();
+                        if (currChar == newLine)
+                        {
+                            woken = true;
+                            //cumulus.LogMessage("Woken: i="+i);
+                            break;
+                        }
 					}
-
-					Thread.Sleep(100);
+                    i++;
+					Thread.Sleep(commWaitTimeMs);
 				}
 
 				// Now check and see if anything's been returned.  If nothing, try again with another newline.
@@ -2273,16 +2230,21 @@ private bool WakeVP(SerialPort serialPort)
 				{
 					//cumulus.LogMessage("No response, retry wake");
 					serialPort.WriteLine("");
-					for (int i = 0; i < 12; i++)
-					{
-						if (serialPort.BytesToRead != 0)
-						{
-							woken = true;
-							//cumulus.LogMessage("Woken: i=" + i);
-							break;
+                    i = 0;
+                    while (!woken && i < 12)
+					{
+						while (serialPort.BytesToRead != 0)
+						{
+                            currChar = comport.ReadChar();
+                            if (currChar == newLine)
+                            {
+                                woken = true;
+                                //cumulus.LogMessage("Woken: i=" + i);
+                                break;
+                            }
 						}
-
-						Thread.Sleep(100);
+                        i++;
+						Thread.Sleep(commWaitTimeMs);
 					}
 					passCount++;
 				}
@@ -2302,12 +2264,12 @@ private bool WakeVP(SerialPort serialPort)
 					//}
 					//cumulus.LogMessage(str);
 					serialPort.DiscardInBuffer();
-					//cumulus.LogMessage("Woken");
+					cumulus.LogDebugMessage("Woken");
 					return (true);
 				}
 				else
 				{
-					cumulus.LogDebugMessage("!!! Not woken");
+					cumulus.LogMessage("!!! VP2 Not woken");
 					return (false);
 				}
 			}
@@ -2457,304 +2419,295 @@ private bool WakeVP()
 			}
 		}
 
-		private DateTime getTime()
-		{
-			cumulus.LogMessage("Reading console time");
+        private bool WaitForACK(SerialPort serialPort)
+        {
+            int currChar;
 
-			if (IsSerial)
-			{
-				string commandString = "GETTIME";
-				if (WakeVP(comport))
-				{
-					comport.WriteLine(commandString);
-
-					Thread.Sleep(200);
-
-					// Read the time
-					var bytesRead = 0;
-					byte[] buffer = new byte[8];
-					while (comport.BytesToRead > 0 && bytesRead < 9)
-					{
-						// Read the current character
-						var ch = comport.ReadChar();
-						if (bytesRead > 0)
-						{
-							buffer[bytesRead - 1] = (byte) ch;
-						}
-						bytesRead++;
-						//cumulus.LogMessage("Received " + ch.ToString("X2"));
-					}
-
-					cumulus.LogDataMessage("Recieved 0x" + BitConverter.ToString(buffer));
-
-					if (bytesRead != 9)
-					{
-						cumulus.LogMessage("Expected 9 bytes, got " + bytesRead);
-					}
-					/*else if (!crcOK(buffer))
-					{
-						cumulus.LogMessage("Invalid CRC");
-					}*/
-					else
-					{
-						try
-						{
-							return new DateTime(buffer[5] + 1900, buffer[4], buffer[3], buffer[2], buffer[1], buffer[0]);
-						}
-						catch (Exception)
-						{
-							cumulus.LogMessage("Error in time format");
-						}
-					}
-				}
-			}
-			else
-			{
-				string commandString = "GETTIME\n";
-				if (WakeVP(socket))
-				{
-					try
-					{
-						NetworkStream stream = socket.GetStream();
-						stream.Write(Encoding.ASCII.GetBytes(commandString), 0, commandString.Length);
-
-						Thread.Sleep(cumulus.DavisIPResponseTime);
-
-						bool Found_ACK = false;
-						int ch;
-
-						while (stream.DataAvailable && !Found_ACK)
-						{
-							// Read the current character
-							ch = stream.ReadByte();
-							//cumulus.LogMessage("Received 0x" + ch.ToString("X2"));
-							if (ch == ACK)
-								Found_ACK = true;
-						}
-
-						if (!Found_ACK)
-						{
-							cumulus.LogMessage("No ACK - wait a little longer");
-							// wait a little longer
-							Thread.Sleep(500);
-							while (stream.DataAvailable && !Found_ACK)
-							{
-								// Read the current character
-								ch = stream.ReadByte();
-								cumulus.LogMessage("Received 0x" + ch.ToString("X2"));
-								if (ch == ACK)
-									Found_ACK = true;
-							}
-							if (!Found_ACK)
-							{
-								cumulus.LogMessage("No ACK");
-								return DateTime.MinValue;
-							}
-						}
-
-						cumulus.LogMessage("ACK received");
-
-						// Read the time
-						var bytesRead = 0;
-						byte[] buffer = new byte[8];
-						while (stream.DataAvailable && bytesRead < 8)
-						{
-							// Read the current character
-							ch = stream.ReadByte();
-							buffer[bytesRead] = (byte)ch;
-
-							bytesRead++;
-							//cumulus.LogMessage("Received " + ch.ToString("X2"));
-						}
+            // Wait for the VP to acknowledge the the receipt of the command - sometimes we get a '\n\r'
+            // in the buffer first or no response is given.  If all else fails, try again.
+            cumulus.LogDebugMessage("Wait for ACK");
+            while (serialPort.BytesToRead > 0)
+            {
+                // Read the current character
+                currChar = serialPort.ReadChar();
+                cumulus.LogDataMessage("WaitForACK received 0x" + currChar.ToString("X2"));
+                if (currChar == ACK)
+                {
+                    cumulus.LogDebugMessage("ACK received");
+                    return true;
+                }
+                else if (currChar == NACK)
+                {
+                    cumulus.LogDebugMessage("NACK received");
+                    return false;
+                } else if (currChar == CANCEL)
+                {
+                    cumulus.LogDebugMessage("CANCEL received");
+                    return false;
+                }
+            }
+            return false;
+        }
 
-						cumulus.LogDataMessage("Recieved 0x" + BitConverter.ToString(buffer));
+        private bool WaitForACK(NetworkStream stream)
+        {
+            int currChar;
 
-						if (bytesRead != 8)
-						{
-							cumulus.LogMessage("Expected 8 bytes, got " + bytesRead);
-						}
-						/*else if (!crcOK(buffer))
-						{
-							cumulus.LogMessage("Invalid CRC");
-						}*/
-						else
-						{
-							try
-							{
-								return new DateTime(buffer[5] + 1900, buffer[4], buffer[3], buffer[2], buffer[1], buffer[0]);
-							}
-							catch (Exception)
-							{
-								cumulus.LogMessage("Error in time format");
-							}
-						}
-					}
-					catch (Exception ex)
-					{
-						cumulus.LogDebugMessage("Get date time: Error - " + ex.Message);
-					}
-				}
-			}
+            // Wait for the VP to acknowledge the the receipt of the command - sometimes we get a '\n\r'
+            // in the buffer first or no response is given.  If all else fails, try again.
+            cumulus.LogDebugMessage("Wait for ACK");
+            while (stream.DataAvailable)
+            {
+                // Read the current character
+                currChar = stream.ReadByte();
+                cumulus.LogDataMessage("WaitForACK received 0x" + currChar.ToString("X2"));
+                if (currChar == ACK)
+                {
+                    cumulus.LogDebugMessage("ACK received");
+                    return true;
+                }
+                else if (currChar == NACK)
+                {
+                    cumulus.LogDebugMessage("NACK received");
+                    return false;
+                }
+                else if (currChar == CANCEL)
+                {
+                    cumulus.LogDebugMessage("CANCEL received");
+                    return false;
+                }
+            }
+            return false;
+        }
 
-			return DateTime.MinValue;
-		}
 
-		private void setTime()
+        private DateTime getTime()
 		{
-			cumulus.LogMessage("Setting console time");
+            byte[] buffer = new byte[8];
+            var bytesRead = 0;
 
-			if (IsSerial)
-			{
-				string commandString = "SETTIME";
-				if (WakeVP(comport))
-				{
-					comport.WriteLine(commandString);
-
-					//Thread.Sleep(200);
+            cumulus.LogMessage("Reading console time");
 
-					// wait for the ACK
-					cumulus.LogMessage("Wait for ACK...");
-					var ch = comport.ReadChar();
+            if (IsSerial)
+            {
+                string commandString = "GETTIME";
+                if (WakeVP(comport))
+                {
+                    comport.WriteLine(commandString);
 
-					if (ch != ACK)
-					{
-						cumulus.LogMessage("No ACK, received: 0x" + ch.ToString("X2"));
-						return;
-					}
-					else
-					{
-						cumulus.LogMessage("ACK received");
-					}
+                    Thread.Sleep(commWaitTimeMs);
 
-					DateTime now = DateTime.Now;
+                    if (!WaitForACK(comport))
+                    {
+                        cumulus.LogMessage("No ACK");
+                        return DateTime.MinValue;
+                    }
 
-					byte[] buffer = new byte[8];
+                    // Read the time
+                    while (comport.BytesToRead > 0 && bytesRead < 8)
+                    {
+                        // Read the current character
+                        var ch = comport.ReadChar();
+                        if (bytesRead > 0)
+                        {
+                            buffer[bytesRead] = (byte)ch;
+                        }
+                        bytesRead++;
+                        //cumulus.LogMessage("Received " + ch.ToString("X2"));
+                    }
+                }
+            }
+            else
+            {
+                string commandString = "GETTIME\n";
+                if (WakeVP(socket))
+                {
+                    try
+                    {
+                        NetworkStream stream = socket.GetStream();
+                        stream.Write(Encoding.ASCII.GetBytes(commandString), 0, commandString.Length);
 
-					buffer[0] = (byte) now.Second;
-					buffer[1] = (byte) now.Minute;
-					buffer[2] = (byte) now.Hour;
-					buffer[3] = (byte) now.Day;
-					buffer[4] = (byte) now.Month;
-					buffer[5] = (byte) (now.Year - 1900);
+                        Thread.Sleep(cumulus.DavisIPResponseTime);
 
-					// calculate and insert CRC
+                        int ch;
 
-					byte[] datacopy = new byte[6];
+                        if (!WaitForACK(stream))
+                        {
+                            cumulus.LogMessage("No ACK - wait a little longer");
+                            // wait a little longer
+                            Thread.Sleep(500);
+                            if (!WaitForACK(stream))
+                            {
+                                cumulus.LogMessage("No ACK");
+                                return DateTime.MinValue;
+                            }
+                        }
 
-					Array.Copy(buffer, datacopy, 6);
-					int crc = calculateCRC(datacopy);
+                        cumulus.LogMessage("ACK received");
 
-					buffer[6] = (byte) (crc/256);
-					buffer[7] = (byte) (crc%256);
+                        // Read the time
+                        while (stream.DataAvailable && bytesRead < 8)
+                        {
+                            // Read the current character
+                            ch = stream.ReadByte();
+                            buffer[bytesRead] = (byte)ch;
 
-					// send the data
-					comport.Write(buffer, 0, 8);
+                            bytesRead++;
+                            //cumulus.LogMessage("Received " + ch.ToString("X2"));
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        cumulus.LogDebugMessage("Get date time: Error - " + ex.Message);
+                    }
+                }
+            }
 
-					// wait for the ACK
-					cumulus.LogMessage("Wait for ACK...");
-					ch = comport.ReadChar();
+            cumulus.LogDataMessage("Recieved 0x" + BitConverter.ToString(buffer));
 
-					if (ch != ACK)
-					{
-						cumulus.LogMessage("No ACK, received: 0x" + ch.ToString("X2"));
-						return;
-					}
-					else
-					{
-						cumulus.LogMessage("ACK received");
-					}
-				}
+			if (bytesRead != 8)
+			{
+				cumulus.LogMessage("Expected 8 bytes, got " + bytesRead);
 			}
+            /* Always seems to return a fixed CRC :(
+			else if (!crcOK(buffer))
+			{
+				cumulus.LogMessage("Invalid CRC!");
+			}
+            */
 			else
 			{
-				string commandString = "SETTIME\n";
-				if (WakeVP(socket))
+				try
 				{
-					try
-					{
-						NetworkStream stream = socket.GetStream();
-						stream.Write(Encoding.ASCII.GetBytes(commandString), 0, commandString.Length);
+					return new DateTime(buffer[5] + 1900, buffer[4], buffer[3], buffer[2], buffer[1], buffer[0]);
+				}
+				catch (Exception)
+				{
+					cumulus.LogMessage("Error in time format");
+				}
+			}
+            return DateTime.MinValue;
+		}
 
-						Thread.Sleep(cumulus.DavisIPResponseTime);
+		private void setTime()
+		{
+            NetworkStream stream = null;
+   
+            cumulus.LogMessage("Setting console time");
 
-						bool Found_ACK = false;
-						int ch;
+            try
+            {
+                if (IsSerial)
+                {
+                    string commandString = "SETTIME";
+                    if (WakeVP(comport))
+                    {
+                        comport.WriteLine(commandString);
 
-						while (stream.DataAvailable && !Found_ACK)
-						{
-							// Read the current character
-							ch = stream.ReadByte();
-							cumulus.LogMessage("Received 0x" + ch.ToString("X2"));
-							if (ch == ACK)
-								Found_ACK = true;
-						}
+                        Thread.Sleep(commWaitTimeMs);
 
-						if (!Found_ACK)
-						{
-							cumulus.LogMessage("No ACK");
-							return;
-						}
+                        // wait for the ACK
+                        if (!WaitForACK(comport))
+                        {
+                            cumulus.LogMessage("No ACK to SETTIME - Not setting the time");
+                            return;
+                        }
+                    }
+                }
+                else
+                {
+                    string commandString = "SETTIME\n";
+                    if (WakeVP(socket))
+                    {
+                        stream = socket.GetStream();
+                        stream.Write(Encoding.ASCII.GetBytes(commandString), 0, commandString.Length);
 
-						cumulus.LogMessage("ACK received");
+                        Thread.Sleep(cumulus.DavisIPResponseTime);
 
-						DateTime now = DateTime.Now;
+                        // wait for the ACK
+                        if (!WaitForACK(stream))
+                        {
+                            cumulus.LogMessage("No ACK to SETTIME - Not setting the time");
+                            return;
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                cumulus.LogDebugMessage("setTime Error - " + ex.Message);
+                return;
+            }
 
-						byte[] buffer = new byte[8];
+            DateTime now = DateTime.Now;
 
-						buffer[0] = (byte)now.Second;
-						buffer[1] = (byte)now.Minute;
-						buffer[2] = (byte)now.Hour;
-						buffer[3] = (byte)now.Day;
-						buffer[4] = (byte)now.Month;
-						buffer[5] = (byte)(now.Year - 1900);
+            byte[] buffer = new byte[8];
 
-						// calculate and insert CRC
+            buffer[0] = (byte)now.Second;
+            buffer[1] = (byte)now.Minute;
+            buffer[2] = (byte)now.Hour;
+            buffer[3] = (byte)now.Day;
+            buffer[4] = (byte)now.Month;
+            buffer[5] = (byte)(now.Year - 1900);
 
-						byte[] datacopy = new byte[6];
+            // calculate and insert CRC
 
-						Array.Copy(buffer, datacopy, 6);
-						int crc = calculateCRC(datacopy);
+            byte[] datacopy = new byte[6];
 
-						buffer[6] = (byte)(crc / 256);
-						buffer[7] = (byte)(crc % 256);
+            Array.Copy(buffer, datacopy, 6);
+            int crc = calculateCRC(datacopy);
 
-						stream.Write(buffer, 0, buffer.Length);
+            buffer[6] = (byte)(crc / 256);
+            buffer[7] = (byte)(crc % 256);
 
-						Thread.Sleep(cumulus.DavisIPResponseTime);
+            try
+            {
+                if (IsSerial)
+                {
 
-						Found_ACK = false;
+                    // send the data
+                    comport.Write(buffer, 0, 8);
 
-						while (stream.DataAvailable && !Found_ACK)
-						{
-							// Read the current character
-							ch = stream.ReadByte();
-							cumulus.LogMessage("Received 0x" + ch.ToString("X2"));
-							if (ch == ACK)
-								Found_ACK = true;
-						}
+                    Thread.Sleep(commWaitTimeMs);
 
-						if (!Found_ACK)
-						{
-							cumulus.LogMessage("No ACK");
-							return;
-						}
+                    // wait for the ACK
+                    if (WaitForACK(comport))
+                    {
+                        cumulus.LogMessage("Console time set OK");
+                    }
+                    else
+                    {
+                        cumulus.LogMessage("Error, console time set failed");
+                    }
+			    }
+			    else if (stream != null)
+                {
+                    stream.Write(buffer, 0, buffer.Length);
 
-						cumulus.LogMessage("ACK received");
-					}
-					catch (Exception ex)
-					{
-						cumulus.LogDebugMessage("setTime Error - " + ex.Message);
-					}
-				}
-			}
-		}
+                    Thread.Sleep(cumulus.DavisIPResponseTime);
 
-		/// <summary>
-		/// Converts rain from VP standard (in) to internal standard (mm)
-		/// </summary>
-		/// <param name="VPrain">rain in inches</param>
-		/// <returns>rain in mm</returns>
-		internal double ConvertRainToInternal(double VPrain)
+                    if (WaitForACK(stream))
+                    {
+                        cumulus.LogMessage("Console time set OK");
+                    }
+                    else
+                    {
+                        cumulus.LogMessage("Error, console time set failed");
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                cumulus.LogDebugMessage("setTime Error - " + ex.Message);
+            }
+        }
+
+        /// <summary>
+        /// Converts rain from VP standard (in) to internal standard (mm)
+        /// </summary>
+        /// <param name="VPrain">rain in inches</param>
+        /// <returns>rain in mm</returns>
+        internal double ConvertRainToInternal(double VPrain)
 		{
 			if (cumulus.VPrainGaugeType == 1)
 				return VPrain*25.4F;
diff --git a/CumulusMX/EasyWeather.cs b/CumulusMX/EasyWeather.cs
index b8da9822..91aefb74 100644
--- a/CumulusMX/EasyWeather.cs
+++ b/CumulusMX/EasyWeather.cs
@@ -10,6 +10,47 @@
 
 namespace CumulusMX
 {
+    /*
+     * EasyWeather.dat file format (* indicates fields used by Cumulus)
+     * 1    Record number       int
+     * 2    Transfer Date       yyy-mmm-dd hh:mm:ss
+     * 3*   Reading Date        yyy-mmm-dd hh:mm:ss
+     * 4    Reading interval    int     (minutes since previous reading)
+     * 5*   Indoor humidity     int
+     * 6*   Indoor temp         float   Celcius
+     * 7*   Outdoor humidity    int
+     * 8*   Outdoor temp        float   Celcius
+     * 9*   Dew point           float   Celcius
+     * 10*  Wind chill          float   Celcius
+     * 11   Absolute press      float   mB/hPa
+     * 12*  Relative press      float   mB/hPa
+     * 13*  Wind average        float   m/s
+     * 14   Wind average        int     Beaufort
+     * 15*  Wind gust           float   m/s
+     * 16   Wind gust           int     Beaufort
+     * 17   Wind direction      int     0 - 15. 0 = North, 1 = NNE etc
+     * 18*  Wind direction      str     Oddly ENE appears as NEE, and ESE appears as SEE
+     * 19   Rain ticks          int
+     * 20   Rain total          float   mm
+     * 21   Rain since last     float   mm
+     * 22*  Rain in last hour   float   mm
+     * 23   Rain in last 24h    float   mm
+     * 24   Rain in last 7d     float   mm
+     * 25   Rain in last 30d    float   mm
+     * 26*  Rain total          float   mm
+     * 27*  Light reading       int     Lux
+     * 28*  UV Index            int
+     * 29   Status bit #0       int     0|1
+     * 30   Status bit #1       int     0|1
+     * 31   Status bit #2       int     0|1
+     * 32   Status bit #3       int     0|1
+     * 33   Status bit #4       int     0|1
+     * 34   Status bit #5       int     0|1
+     * 35   Status bit #6       int     0|1 - Outdoor readings invalid = 1
+     * 36   Status bit #7       int     0|1
+     * 37   Data address        6 digit hex
+     * 38   Raw data            16x 2-digit hex
+    */
     internal class EasyWeather : WeatherStation
     {
         private Timer tmrDataRead;
diff --git a/CumulusMX/ImetStation.cs b/CumulusMX/ImetStation.cs
index 477c3ba0..dfe5cf9e 100644
--- a/CumulusMX/ImetStation.cs
+++ b/CumulusMX/ImetStation.cs
@@ -31,6 +31,12 @@ public ImetStation(Cumulus cumulus) : base(cumulus)
 
 			calculaterainrate = true;
 
+			// Change the default dps for rain and sunshine from 1 to 2 for IMet stations
+			cumulus.RainDPlaces = cumulus.SunshineDPlaces = 2;
+			cumulus.RainDPlace[0] = 2;  // mm
+			cumulus.RainDPlace[1] = 3;  // in
+			cumulus.RainFormat = cumulus.SunFormat = "F2";
+
 			comport = new SerialPort(cumulus.ComportName, cumulus.ImetBaudRate, Parity.None, 8, StopBits.One) {Handshake = Handshake.None, RtsEnable = true, DtrEnable = true};
 
 			try
diff --git a/CumulusMX/Properties/AssemblyInfo.cs b/CumulusMX/Properties/AssemblyInfo.cs
index 41cf47f8..c19500c2 100644
--- a/CumulusMX/Properties/AssemblyInfo.cs
+++ b/CumulusMX/Properties/AssemblyInfo.cs
@@ -5,11 +5,11 @@
 // 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("CumulusMX - beta")]
+[assembly: AssemblyTitle("CumulusMX")]
 [assembly: AssemblyDescription("")]
 [assembly: AssemblyConfiguration("")]
 [assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("CumulusMX - beta")]
+[assembly: AssemblyProduct("CumulusMX")]
 [assembly: AssemblyCopyright("Copyright ©  2015")]
 [assembly: AssemblyTrademark("")]
 [assembly: AssemblyCulture("")]
diff --git a/CumulusMX/TokenParser.cs b/CumulusMX/TokenParser.cs
index b5e20fec..8966189e 100644
--- a/CumulusMX/TokenParser.cs
+++ b/CumulusMX/TokenParser.cs
@@ -237,7 +237,8 @@ private String Parse3()
 						Trace.WriteLine(e.ToString());
 						//cumulus.LogMessage(InputText);
 						Console.WriteLine("*** web tag error - see MXdiags file ***");
-						outText += e.Message;
+                        //outText += e.Message;
+                        outText += "**Web tag error, tag starting: #" + token.Substring(0, token.Length > 40 ? 39 : token.Length - 1) + "**";
 					}
 					i = match.Index + match.Length;
 				}
diff --git a/CumulusMX/WeatherLink.cs b/CumulusMX/WeatherLink.cs
index ce74e904..b5a0d508 100644
--- a/CumulusMX/WeatherLink.cs
+++ b/CumulusMX/WeatherLink.cs
@@ -23,8 +23,7 @@ namespace CumulusMX
     //                                                    80 = ASCII "P" = Rev A firmware, no trend info is available. 
     //                                                    Any other value means that the Vantage does not have the 3 hours of bar data needed 
     //                                                        to determine the bar trend. 
-    //    Packet Type                     4       1       Has the value zero. In the future we may define new LOOP packet formats and assign a different 
-    //                                                        value to this field. 
+    //    Packet Type                     4       1       Has the value zero. LOOP2 packets are set to 1.
     //    Next Record                     5       2       Location in the archive memory where the next data packet will be written. This can be 
     //                                                        monitored to detect when a new record is created. 
     //    Pressure                        7       2       Current Pressure. Units are (in Hg / 1000). The barometric value should be between 20 inches 
@@ -255,7 +254,14 @@ public void Load(Byte[] byteArray)
                 var srMonth = (stormRainStart & 0xF000) >> 12;
                 var srDay = (stormRainStart & 0x0F80) >> 7;
                 var srYear = (stormRainStart & 0x007F) + 2000;
-                StormRainStart = new DateTime(srYear, srMonth, srDay);
+                if (srMonth < 13 && srDay < 32)  // Exception suppression!
+                {
+                    StormRainStart = new DateTime(srYear, srMonth, srDay);
+                }
+                else
+                {
+                    StormRainStart = DateTime.MinValue;
+                }
             }
             catch (Exception)
             {
diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs
index 1bbcc808..bdf53964 100644
--- a/CumulusMX/WeatherStation.cs
+++ b/CumulusMX/WeatherStation.cs
@@ -1220,11 +1220,12 @@ public void UpdateDegreeDays(int interval)
 		public double SunshineHours { get; set; }
 
 		public double YestSunshineHours { get; set; }
+
 		public double SunshineToMidnight { get; set; }
-		public double SunHourCounter { get; set; }
 
-		public double StartOfDaySunHourCounter { get; set; }
+        public double SunHourCounter { get; set; }
 
+		public double StartOfDaySunHourCounter { get; set; }
 
 		public double CurrentSolarMax { get; set; }
 
@@ -1309,11 +1310,11 @@ public void SecondTimer(object sender, ElapsedEventArgs e)
 				// send current data to websocket every 3 seconds for now
 				try
 				{
-					String windRoseData = (windcounts[0] * cumulus.WindGustMult).ToString(cumulus.WindFormat).Replace(",", ".");
+					String windRoseData = (windcounts[0] * cumulus.WindGustMult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture);
 
 					for (var i = 1; i < cumulus.NumWindRosePoints; i++)
 					{
-						windRoseData = windRoseData + "," + (windcounts[i] * cumulus.WindGustMult).ToString(cumulus.WindFormat).Replace(",", ".");
+						windRoseData = windRoseData + "," + (windcounts[i] * cumulus.WindGustMult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture);
 					}
 
 					string stormRainStart = StartOfStorm == DateTime.MinValue ? "-----" : StartOfStorm.ToString("d");
@@ -4823,12 +4824,12 @@ private void DoDayfile(DateTime timestamp)
 			if (cumulus.RolloverHour == 0)
 			{
 				// use existing current sunshinehour count
-				str += SunshineHours.ToString("F1") + cumulus.ListSeparator;
+				str += SunshineHours.ToString(cumulus.SunFormat) + cumulus.ListSeparator;
 			}
 			else
 			{
 				// for non-midnight rollover, use new item
-				str += SunshineToMidnight.ToString("F1") + cumulus.ListSeparator;
+				str += SunshineToMidnight.ToString(cumulus.SunFormat) + cumulus.ListSeparator;
 			}
 			str += HighHeatIndexToday.ToString(cumulus.TempFormat) + cumulus.ListSeparator;
 			str += highheatindextodaytime.ToString("HH:mm") + cumulus.ListSeparator;
@@ -4899,12 +4900,12 @@ private void DoDayfile(DateTime timestamp)
 						if (cumulus.RolloverHour == 0)
 						{
 							// use existing current sunshinehour count to minimise risk
-							file.Write(SunshineHours.ToString("F1") + cumulus.ListSeparator);
+							file.Write(SunshineHours.ToString(cumulus.SunFormat) + cumulus.ListSeparator);
 						}
 						else
 						{
 							// for non-midnight rollover, use new item
-							file.Write(SunshineToMidnight.ToString("F1") + cumulus.ListSeparator);
+							file.Write(SunshineToMidnight.ToString(cumulus.SunFormat) + cumulus.ListSeparator);
 						}
 						file.Write(HighHeatIndexToday.ToString(cumulus.TempFormat) + cumulus.ListSeparator);
 						file.Write(highheatindextodaytime.ToString("HH:mm") + cumulus.ListSeparator);
@@ -4972,7 +4973,7 @@ private void DoDayfile(DateTime timestamp)
 				highhumiditytoday + "," +
 				highhumiditytodaytime.ToString("\\'HH:mm\\'") + "," +
 				ET.ToString(cumulus.ETFormat, InvC) + "," +
-				(cumulus.RolloverHour == 0 ? SunshineHours.ToString("F1", InvC) : SunshineToMidnight.ToString("F1", InvC)) + "," +
+				(cumulus.RolloverHour == 0 ? SunshineHours.ToString(cumulus.SunFormat, InvC) : SunshineToMidnight.ToString(cumulus.SunFormat, InvC)) + "," +
 				HighHeatIndexToday.ToString(cumulus.TempFormat, InvC) + "," +
 				highheatindextodaytime.ToString("\\'HH:mm\\'") + "," +
 				HighAppTempToday.ToString(cumulus.TempFormat, InvC) + "," +
@@ -8537,7 +8538,7 @@ public string GetTodayYestSolar()
 
 			json += "[\"" + "High Solar Radiation" + "\",\"" + HighSolarToday.ToString("F0") + "&nbsp" + "W/m2" + "\",\"" + highsolartodaytime.ToShortTimeString() + "\",\"" +
 					HighSolarYesterday.ToString("F0") + "&nbsp;" + "W/m2" + "\",\"" + highsolaryesterdaytime.ToShortTimeString() + "\"],";
-			json += "[\"" + "Hours of Sunshine" + "\",\"" + SunshineHours.ToString("F1") + "&nbsp;hrs" + "\",\"" + "&nbsp;" + "\",\"" + YestSunshineHours.ToString("F1") +
+			json += "[\"" + "Hours of Sunshine" + "\",\"" + SunshineHours.ToString(cumulus.SunFormat) + "&nbsp;hrs" + "\",\"" + "&nbsp;" + "\",\"" + YestSunshineHours.ToString(cumulus.SunFormat) +
 					"&nbsp;hrs" + "\",\"" + "&nbsp;" + "\"]";
 
 			json += "]}";
@@ -8757,7 +8758,7 @@ public string GetSunHoursGraphData()
 
 			for (var i = 0; i < RecentDailyDataList.Count; i++)
 			{
-				sb.Append("[" + DateTimeToUnix(RecentDailyDataList[i].timestamp) * 1000 + "," + RecentDailyDataList[i].sunhours.ToString("F1", InvC) + "]");
+				sb.Append("[" + DateTimeToUnix(RecentDailyDataList[i].timestamp) * 1000 + "," + RecentDailyDataList[i].sunhours.ToString(cumulus.SunFormat, InvC) + "]");
 
 				if (i < RecentDailyDataList.Count - 1)
 					sb.Append(",");
@@ -8801,11 +8802,11 @@ public string GetDailyTempGraphData()
 
 		internal string GetCurrentData()
 		{
-			String windRoseData = (windcounts[0] * cumulus.WindGustMult).ToString(cumulus.WindFormat).Replace(",", ".");
+			String windRoseData = (windcounts[0] * cumulus.WindGustMult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture);
 
 			for (var i = 1; i < cumulus.NumWindRosePoints; i++)
 			{
-				windRoseData = windRoseData + "," + (windcounts[i] * cumulus.WindGustMult).ToString(cumulus.WindFormat).Replace(",", ".");
+				windRoseData = windRoseData + "," + (windcounts[i] * cumulus.WindGustMult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture);
 			}
 
 			string stormRainStart = StartOfStorm == DateTime.MinValue ? "-----" : StartOfStorm.ToString("d");
diff --git a/CumulusMX/packages.config b/CumulusMX/packages.config
index ee0384f7..71491240 100644
--- a/CumulusMX/packages.config
+++ b/CumulusMX/packages.config
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="EmbedIO" version="2.1.1" targetFramework="net452" />
-  <package id="FluentFTP" version="19.2.3" targetFramework="net452" />
+  <package id="FluentFTP" version="21.0.0" targetFramework="net452" />
   <package id="HidSharp" version="2.0.8" targetFramework="net452" />
   <package id="linqtotwitter" version="3.1.1" targetFramework="net45" />
   <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net452" />
diff --git a/CumulusMX/webtags.cs b/CumulusMX/webtags.cs
index b9b35501..c639e54c 100644
--- a/CumulusMX/webtags.cs
+++ b/CumulusMX/webtags.cs
@@ -836,10 +836,10 @@ private string Tagwdirdata(Dictionary<string,string> TagParams)
         private string Tagwspddata(Dictionary<string,string> TagParams)
         {
             //String s = (station.windspeeds[0]*cumulus.WindGustMult).ToString(cumulus.WindFormat).Replace(",", ".");
-            var sb = new StringBuilder((station.windspeeds[0]*cumulus.WindGustMult).ToString(cumulus.WindFormat).Replace(",", "."));
+            var sb = new StringBuilder((station.windspeeds[0]*cumulus.WindGustMult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture));
             for (var i = 1; i < station.numwindvalues; i++)
             {
-                sb.Append("," + (station.windspeeds[i]*cumulus.WindGustMult).ToString(cumulus.WindFormat).Replace(",", "."));
+                sb.Append("," + (station.windspeeds[i]*cumulus.WindGustMult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture));
                 //s = s + "," + (station.windspeeds[i]*cumulus.WindGustMult).ToString(cumulus.WindFormat).Replace(",", ".");
             }
 
@@ -859,11 +859,11 @@ private string Tagnextwindindex(Dictionary<string,string> TagParams)
 
         private string TagWindRoseData(Dictionary<string,string> TagParams)
         {
-            String s = (station.windcounts[0]*cumulus.WindGustMult).ToString(cumulus.WindFormat).Replace(",", ".");
+            String s = (station.windcounts[0]*cumulus.WindGustMult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture);
 
             for (var i = 1; i < cumulus.NumWindRosePoints; i++)
             {
-                s = s + "," + (station.windcounts[i]*cumulus.WindGustMult).ToString(cumulus.WindFormat).Replace(",", ".");
+                s = s + "," + (station.windcounts[i]*cumulus.WindGustMult).ToString(cumulus.WindFormat, CultureInfo.InvariantCulture);
             }
 
             return s;
@@ -2684,7 +2684,7 @@ private string TagCurrentSolarMax(Dictionary<string,string> TagParams)
 
         private string TagSunshineHours(Dictionary<string,string> TagParams)
         {
-            return station.SunshineHours.ToString("F1");
+            return station.SunshineHours.ToString(cumulus.SunFormat);
         }
 
         private string TagTHWIndex(Dictionary<string,string> TagParams)
@@ -2704,7 +2704,7 @@ private string TagChillHours(Dictionary<string,string> TagParams)
 
         private string TagYSunshineHours(Dictionary<string,string> TagParams)
         {
-            return station.YestSunshineHours.ToString("F1");
+            return station.YestSunshineHours.ToString(cumulus.SunFormat);
         }
 
         private string TagIsSunny(Dictionary<string,string> TagParams)
diff --git a/Updates.txt b/Updates.txt
index 0613e926..f094caa4 100644
--- a/Updates.txt
+++ b/Updates.txt
@@ -1,6 +1,24 @@
-3048beta
-========
-- Fix web tag <#YearLowDailyTempRangeD>
+3048
+====
+- You can now first time enable/disable Realtime FTP without having to restart CMX
+
+- Instromet stations now record and report rainfall (mm) and sunshine hours to 2 decimal places
+
+- Improved realtime FTP error handling
+
+- Improved Davis protocol handling
+
+- Fix Davis protocol mixing up LOOP1 and LOOP2 packets and consequently providing invalid rain and wind data.
+
+- Fix web tag <#YearLowDailyTempRangeD> broken in b3047
+
+- Bug fixes to FTP Component, and internal changes to FTP transfer mechanism
+
+- Updated files
+	\CumulusMX.exe
+	\CumulusMX.exe.pdb
+	\FluentFTP.dll
+
 
 
 3047