diff --git a/CumulusMX/Cumulus.cs b/CumulusMX/Cumulus.cs index 027c5698..0ecec5e8 100644 --- a/CumulusMX/Cumulus.cs +++ b/CumulusMX/Cumulus.cs @@ -34,8 +34,8 @@ namespace CumulusMX public class Cumulus { ///////////////////////////////// - public string Version = "3.8.3"; - public string Build = "3093"; + public string Version = "3.8.4"; + public string Build = "3094"; ///////////////////////////////// public static SemaphoreSlim syncInit = new SemaphoreSlim(1); @@ -3583,7 +3583,7 @@ private void ReadIniFile() WllExtraHumTx[i - 1] = ini.GetValue("WLL", "ExtraHumOnTxId" + i, false); } - // GW1000 setiings + // GW1000 settings Gw1000IpAddress = ini.GetValue("GW1000", "IPAddress", "0.0.0.0"); Gw1000AutoUpdateIpAddress = ini.GetValue("GW1000", "AutoUpdateIpAddress", true); @@ -5543,8 +5543,8 @@ public void DoLogFile(DateTime timestamp, bool live) // Writes an entry to the n // 28 Humidex { // make sure solar max is calculated for those stations without a solar sensor - LogMessage("Writing log entry for " + timestamp); - LogDebugMessage("max gust: " + station.RecentMaxGust.ToString(WindFormat)); + LogMessage("DoLogFile: Writing log entry for " + timestamp); + LogDebugMessage("DoLogFile: max gust: " + station.RecentMaxGust.ToString(WindFormat)); station.CurrentSolarMax = AstroLib.SolarMax(timestamp, Longitude, Latitude, station.AltitudeM(Altitude), out station.SolarElevation, RStransfactor, BrasTurbidity, SolarCalc); var filename = GetLogFileName(timestamp); @@ -5584,7 +5584,7 @@ public void DoLogFile(DateTime timestamp, bool live) // Writes an entry to the n } LastUpdateTime = timestamp; - LogMessage("Written log entry for " + timestamp); + LogMessage("DoLogFile: Written log entry for " + timestamp); station.WriteTodayFile(timestamp, true); if (StartOfDayBackupNeeded) @@ -5642,7 +5642,7 @@ public void DoLogFile(DateTime timestamp, bool live) // Writes an entry to the n { cmd.CommandText = queryString; cmd.Connection = MonthlyMySqlConn; - LogDebugMessage(queryString); + LogDebugMessage("DoLogFile: MySQL - " + queryString); MonthlyMySqlConn.Open(); int aff = cmd.ExecuteNonQuery(); LogMessage("MySQL: Table " + MySqlMonthlyTable + " " + aff + " rows were affected."); @@ -5650,12 +5650,16 @@ public void DoLogFile(DateTime timestamp, bool live) // Writes an entry to the n } catch (Exception ex) { - LogMessage("Error encountered during Monthly MySQL operation."); + LogMessage("DoLogFile: Error encountered during Monthly MySQL operation."); LogMessage(ex.Message); } finally { - MonthlyMySqlConn.Close(); + try + { + MonthlyMySqlConn.Close(); + } + catch { } } } else @@ -7254,12 +7258,12 @@ 59 8.4 Feels Like temperature // do the update using (MySqlCommand cmd = new MySqlCommand()) { - cmd.CommandText = queryString; - cmd.Connection = RealtimeSqlConn; - LogDebugMessage($"Realtime[{cycle}]: Running SQL command: {queryString}"); - try { + cmd.CommandText = queryString; + cmd.Connection = RealtimeSqlConn; + LogDebugMessage($"Realtime[{cycle}]: Running SQL command: {queryString}"); + RealtimeSqlConn.Open(); int aff1 = cmd.ExecuteNonQuery(); LogDebugMessage($"Realtime[{cycle}]: {aff1} rows were affected."); @@ -7295,10 +7299,7 @@ 59 8.4 Feels Like temperature { RealtimeSqlConn.Close(); } - catch - { - // do nothing - } + catch {} } } } @@ -7519,7 +7520,11 @@ private void CustomMysqlSecondsTimerTick(object sender, ElapsedEventArgs e) } finally { - CustomMysqlSecondsConn.Close(); + try + { + CustomMysqlSecondsConn.Close(); + } + catch {} customMySqlSecondsUpdateInProgress = false; } } @@ -7547,7 +7552,11 @@ internal void CustomMysqlMinutesTimerTick() } finally { - CustomMysqlMinutesConn.Close(); + try + { + CustomMysqlMinutesConn.Close(); + } + catch {} customMySqlMinutesUpdateInProgress = false; } } @@ -7576,7 +7585,11 @@ internal void CustomMysqlRolloverTimerTick() } finally { - CustomMysqlRolloverConn.Close(); + try + { + CustomMysqlRolloverConn.Close(); + } + catch {} customMySqlRolloverUpdateInProgress = false; } }); @@ -7681,32 +7694,46 @@ private void MySqlCatchup() mySqlConn.Password = MySqlPass; mySqlConn.Database = MySqlDatabase; - mySqlConn.Open(); - - for (int i = 0; i < MySqlList.Count; i++) + try { - LogMessage("MySQL Archive: Uploading archive #" + (i + 1)); - try + mySqlConn.Open(); + + for (int i = 0; i < MySqlList.Count; i++) { - using (MySqlCommand cmd = new MySqlCommand()) + LogMessage("MySQL Archive: Uploading archive #" + (i + 1)); + try { - cmd.CommandText = MySqlList[i]; - cmd.Connection = mySqlConn; - LogDebugMessage(MySqlList[i]); + using (MySqlCommand cmd = new MySqlCommand()) + { + cmd.CommandText = MySqlList[i]; + cmd.Connection = mySqlConn; + LogDebugMessage(MySqlList[i]); - int aff = cmd.ExecuteNonQuery(); - LogMessage($"MySQL Archive: Table {MySqlMonthlyTable} - {aff} rows were affected."); + int aff = cmd.ExecuteNonQuery(); + LogMessage($"MySQL Archive: Table {MySqlMonthlyTable} - {aff} rows were affected."); + } + } + catch (Exception ex) + { + LogMessage("MySQL Archive: Error encountered during catchup MySQL operation."); + LogMessage(ex.Message); } } - catch (Exception ex) + } + catch (Exception ex) + { + LogMessage("MySQL Archive: Error encountered during catchup MySQL operation."); + LogMessage(ex.Message); + } + finally + { + try { - LogMessage("MySQL Archive: Error encountered during catchup MySQL operation."); - LogMessage(ex.Message); + mySqlConn.Close(); } + catch {} } - mySqlConn.Close(); - LogMessage("MySQL Archive: End of MySQL archive upload"); MySqlList.Clear(); } @@ -8014,13 +8041,16 @@ public async void CustomHttpSecondsUpdate() { customHttpSecondsTokenParser.InputText = CustomHttpSecondsString; var processedString = customHttpSecondsTokenParser.ToStringFromString(); - await customHttpSecondsClient.GetAsync(processedString); - //var responseBodyAsText = await response.Content.ReadAsStringAsync(); - //LogDebugMessage("Custom HTTP seconds response: " + response.StatusCode + ": " + responseBodyAsText); + LogDebugMessage("CustomHttpSeconds: Querying - " + processedString); + var response = await customHttpSecondsClient.GetAsync(processedString); + response.EnsureSuccessStatusCode(); + var responseBodyAsText = await response.Content.ReadAsStringAsync(); + LogDebugMessage("CustomHttpSeconds: Response - " + response.StatusCode); + LogDataMessage("CustomHttpSeconds: Response Text - " + responseBodyAsText); } catch (Exception ex) { - LogDebugMessage("Custom HTTP seconds update: " + ex.Message); + LogDebugMessage("CustomHttpSeconds: " + ex.Message); } finally { @@ -8035,19 +8065,19 @@ public async void CustomHttpMinutesUpdate() { updatingCustomHttpMinutes = true; - LogDebugMessage("Custom HTTP Minutes update"); - try { customHttpMinutesTokenParser.InputText = CustomHttpMinutesString; var processedString = customHttpMinutesTokenParser.ToStringFromString(); + LogDebugMessage("CustomHttpMinutes: Querying - " + processedString); var response = await customHttpMinutesClient.GetAsync(processedString); var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogDebugMessage("Custom HTTP Minutes response: " + response.StatusCode + ": " + responseBodyAsText); + LogDebugMessage("CustomHttpMinutes: Response code - " + response.StatusCode); + LogDataMessage("CustomHttpMinutes: Response text - " + responseBodyAsText); } catch (Exception ex) { - LogDebugMessage("Custom HTTP Minutes update: " + ex.Message); + LogDebugMessage("CustomHttpMinutes: " + ex.Message); } finally { @@ -8062,19 +8092,19 @@ public async void CustomHttpRolloverUpdate() { updatingCustomHttpRollover = true; - LogDebugMessage("Custom HTTP Rollover update"); - try { customHttpRolloverTokenParser.InputText = CustomHttpRolloverString; var processedString = customHttpRolloverTokenParser.ToStringFromString(); + LogDebugMessage("CustomHttpRollover: Querying - " + processedString); var response = await customHttpRolloverClient.GetAsync(processedString); var responseBodyAsText = await response.Content.ReadAsStringAsync(); - LogDebugMessage("Custom HTTP Rollover response: " + response.StatusCode + ": " + responseBodyAsText); + LogDebugMessage("CustomHttpRollover: Response code - " + response.StatusCode); + LogDataMessage("CustomHttpRollover: Response text - " + responseBodyAsText); } catch (Exception ex) { - LogDebugMessage("Custom HTTP Rollover update: " + ex.Message); + LogDebugMessage("CustomHttpRollover: " + ex.Message); } finally { diff --git a/CumulusMX/CumulusService.cs b/CumulusMX/CumulusService.cs index 18e20ce7..0a174c68 100644 --- a/CumulusMX/CumulusService.cs +++ b/CumulusMX/CumulusService.cs @@ -77,17 +77,40 @@ protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus) { switch (powerStatus) { + case PowerBroadcastStatus.BatteryLow: + Program.cumulus.LogMessage("POWER: Detected system BATTERY LOW"); + break; + case PowerBroadcastStatus.OemEvent: + Program.cumulus.LogMessage("POWER: Detected system OEM EVENT"); + break; + case PowerBroadcastStatus.PowerStatusChange: + Program.cumulus.LogMessage("POWER: Detected system POWER STATUS CHANGE"); + break; + case PowerBroadcastStatus.QuerySuspend: + Program.cumulus.LogMessage("POWER: Detected system QUERY SUSPEND"); + break; + case PowerBroadcastStatus.QuerySuspendFailed: + Program.cumulus.LogMessage("POWER: Detected system QUERY SUSPEND FAILED"); + break; + case PowerBroadcastStatus.ResumeAutomatic: + Program.cumulus.LogMessage("POWER: Detected system RESUME AUTOMATIC"); + break; + case PowerBroadcastStatus.ResumeCritical: + Program.cumulus.LogMessage("POWER: Detected system RESUME CRITICAL, stopping service"); + Program.cumulus.LogConsoleMessage("Detected system RESUME CRITICAL, stopping service"); + // A critical suspend will not have shutdown Cumulus, so do it now + Stop(); + break; case PowerBroadcastStatus.ResumeSuspend: - Program.cumulus.LogMessage("Detected system RESUMING FROM STANDBY"); - Program.cumulus.LogConsoleMessage("Detected system RESUMING FROM STANDBY"); + Program.cumulus.LogMessage("POWER: Detected system RESUMING FROM STANDBY"); break; case PowerBroadcastStatus.Suspend: - Program.cumulus.LogMessage("Detected system GOING TO STANDBY"); - Program.cumulus.LogConsoleMessage("Detected system GOING TO STANDBY"); + Program.cumulus.LogMessage("POWER: Detected system GOING TO STANDBY, stopping service"); + Program.cumulus.LogConsoleMessage("Detected system GOING TO STANDBY, stopping service"); + Stop(); break; } - Stop(); return true; } } diff --git a/CumulusMX/DavisStation.cs b/CumulusMX/DavisStation.cs index 0476163e..726493bf 100644 --- a/CumulusMX/DavisStation.cs +++ b/CumulusMX/DavisStation.cs @@ -120,7 +120,6 @@ public DavisStation(Cumulus cumulus) : base(cumulus) if (cumulus.DavisReadReceptionStats) { var recepStats = GetReceptionStats(); - cumulus.LogMessage("Reception stats string: " + recepStats); DecodeReceptionStats(recepStats); } @@ -219,7 +218,7 @@ private string GetFirmwareVersion() cumulus.LogMessage("Reading firmware version"); string response = ""; string data = ""; - int crCount = 0; + int ch; // expected response - OK1.73 @@ -233,20 +232,19 @@ private string GetFirmwareVersion() comport.DiscardInBuffer(); comport.WriteLine(commandString); - // Read the response - do + if (WaitForOK(comport)) { - // Read the current character - var ch = comport.ReadChar(); - response += Convert.ToChar(ch); - data += ch.ToString("X2") + "-"; - if (ch == 13) + // Read the response + do { - crCount++; - } - } while (crCount < 3); + // Read the current character + ch = comport.ReadChar(); + response += Convert.ToChar(ch); + data += ch.ToString("X2") + "-"; + } while (ch != CR); - data = data.Remove(data.Length - 1); + data = data.Remove(data.Length - 1); + } } catch (TimeoutException) { @@ -274,21 +272,18 @@ private string GetFirmwareVersion() stream.Write(Encoding.ASCII.GetBytes(commandString), 0, commandString.Length); - Thread.Sleep(cumulus.DavisIPResponseTime); - - do + if (WaitForOK(stream)) { - // Read the current character - var ch = stream.ReadByte(); - response += Convert.ToChar(ch); - data += ch.ToString("X2") + "-"; - if (ch == 13) + do { - crCount++; - } - } while (crCount < 3); + // Read the current character + ch = stream.ReadByte(); + response += Convert.ToChar(ch); + data += ch.ToString("X2") + "-"; + } while (ch != CR); - data = data.Remove(data.Length - 1); + data = data.Remove(data.Length - 1); + } } catch (System.IO.IOException ex) { @@ -313,11 +308,9 @@ private string GetFirmwareVersion() cumulus.LogDataMessage("GetFirmwareVersion: Received - " + data); - var okIndex = response.IndexOf("OK"); - - if ((okIndex > -1) && (response.Length >= okIndex + 8)) + if (response.Length >= 5) { - return response.Substring(okIndex + 4, 4); + return response.Substring(0, response.Length - 2); } return "???"; @@ -436,7 +429,7 @@ private string GetReceptionStats() string response = ""; var bytesRead = 0; byte[] buffer = new byte[40]; - int crCount = 0; + int ch; if (IsSerial) { @@ -447,19 +440,18 @@ private string GetReceptionStats() { comport.WriteLine(commandString); - // Read the response - do + if (WaitForOK(comport)) { - // Read the current character - var ch = comport.ReadChar(); - response += Convert.ToChar(ch); - buffer[bytesRead] = (byte)ch; - bytesRead++; - if (ch == 13) + // Read the response - 21629 15 0 3204 128 + do { - crCount++; - } - } while (crCount < 3); + // Read the current character + ch = comport.ReadChar(); + response += Convert.ToChar(ch); + buffer[bytesRead] = (byte)ch; + bytesRead++; + } while (ch != CR); + } } catch (TimeoutException) { @@ -487,20 +479,18 @@ private string GetReceptionStats() stream.Write(Encoding.ASCII.GetBytes(commandString), 0, commandString.Length); - Thread.Sleep(cumulus.DavisIPResponseTime); - - do + if (WaitForOK(stream)) { - // Read the current character - var ch = stream.ReadByte(); - response += Convert.ToChar(ch); - buffer[bytesRead] = (byte) ch; - bytesRead++; - if (ch == 13) + // Read the response - 21629 15 0 3204 128 + do { - crCount++; - } - } while (crCount < 3); + // Read the current character + ch = stream.ReadByte(); + response += Convert.ToChar(ch); + buffer[bytesRead] = (byte)ch; + bytesRead++; + } while (ch != CR); + } } catch (System.IO.IOException ex) { @@ -528,15 +518,16 @@ private string GetReceptionStats() cumulus.LogDataMessage("GetReceptionStats: Received - " + BitConverter.ToString(buffer.Take(bytesRead).ToArray())); - var lastLF = response.LastIndexOf('\n'); - - if (lastLF > 15) + if (response.Length > 10) { - var len = lastLF - 6; - return response.Substring(6, len); + response = response.Substring(0, response.Length - 2); } - - return "0 0 0 0 0"; + else + { + response = "0 0 0 0 0"; + } + cumulus.LogDebugMessage($"GetReceptionStats: {response}"); + return response; } // Open a TCP socket. @@ -869,7 +860,7 @@ public override void Start() } else { - cumulus.LogMessage($"Console clock: Accurate to < 60 seconds, no need to set it (diff={(int)nowTime.Subtract(consoleclock).TotalSeconds}s)"); + cumulus.LogMessage($"Console clock: Accurate to +/- 30 seconds, no need to set it (diff={(int)nowTime.Subtract(consoleclock).TotalSeconds}s)"); } clockSetNeeded = false; @@ -953,7 +944,6 @@ public override void Start() if (cumulus.DavisReadReceptionStats && lastRecepStatsTime.AddMinutes(15) < DateTime.Now && !stop) { var recepStats = GetReceptionStats(); - cumulus.LogDebugMessage(recepStats); DecodeReceptionStats(recepStats); } } @@ -1003,16 +993,19 @@ private void SendBarRead() { comport.WriteLine(commandString); - // Read the response - do + if (WaitForOK(comport)) { - // Read the current character - var ch = comport.ReadChar(); - response += Convert.ToChar(ch); - buffer[bytesRead] = (byte)ch; - bytesRead++; + // Read the response + do + { + // Read the current character + var ch = comport.ReadChar(); + response += Convert.ToChar(ch); + buffer[bytesRead] = (byte)ch; + bytesRead++; - } while (bytesRead < 12); + } while (bytesRead < 7); + } } catch (TimeoutException) { @@ -1038,17 +1031,18 @@ private void SendBarRead() stream.Write(Encoding.ASCII.GetBytes(commandString), 0, commandString.Length); - Thread.Sleep(cumulus.DavisIPResponseTime); - - do + if (WaitForOK(stream)) { - // Read the current character - var ch = stream.ReadByte(); - response += Convert.ToChar(ch); - buffer[bytesRead] = (byte)ch; - bytesRead++; - //cumulus.LogMessage("Received " + ch.ToString("X2")); - } while (stream.DataAvailable) ; + do + { + // Read the current character + var ch = stream.ReadByte(); + response += Convert.ToChar(ch); + buffer[bytesRead] = (byte)ch; + bytesRead++; + //cumulus.LogMessage("Received " + ch.ToString("X2")); + } while (stream.DataAvailable); + } } catch (System.IO.IOException ex) { @@ -1075,6 +1069,10 @@ private void SendBarRead() } cumulus.LogDataMessage("BARREAD Received - " + BitConverter.ToString(buffer.Take(bytesRead).ToArray())); + if (response.Length > 2) + { + cumulus.LogDebugMessage("BARREAD Received - " + response.Substring(0, response.Length - 2)); + } } private bool SendLoopCommand(SerialPort serialPort, string commandString) @@ -3191,6 +3189,86 @@ private void InitTCP() } + private bool WaitForOK(SerialPort serialPort) + { + // Waits for OK + var buffer = new StringBuilder(); + + cumulus.LogDebugMessage("WaitForOK: Wait for OK"); + do + { + try + { + // Read the current character + buffer.Append((char)serialPort.ReadChar()); + } + catch (TimeoutException) + { + cumulus.LogDebugMessage($"WaitForOK: Timed out"); + return false; + } + catch (Exception ex) + { + cumulus.LogDebugMessage($"WaitForOK: Error - {ex.Message}"); + cumulus.LogDebugMessage("WaitForOK: Attempting to reconnect to logger"); + InitSerial(); + cumulus.LogDebugMessage("WaitForOK: Reconnected to logger"); + return false; + } + + } while (buffer.ToString().IndexOf("OK\n\r") == -1); + cumulus.LogDebugMessage("WaitForOK: Found OK"); + return true; + } + + private bool WaitForOK(NetworkStream stream) + { + // Waits for OK + var buffer = new StringBuilder(); + + cumulus.LogDebugMessage("WaitForOK: Wait for OK"); + Thread.Sleep(cumulus.DavisIPResponseTime); + + do + { + try + { + // Read the current character + buffer.Append((char)stream.ReadByte()); + } + catch (System.IO.IOException ex) + { + if (ex.Message.Contains("did not properly respond after a period")) + { + cumulus.LogDebugMessage("WaitForOK: Timed out"); + cumulus.LogDataMessage($"WaitForOK: Received - {BitConverter.ToString(Encoding.UTF8.GetBytes(buffer.ToString()))}"); + return false; + } + else + { + cumulus.LogDebugMessage($"WaitForOK: Error - {ex.Message}"); + cumulus.LogDataMessage($"WaitForOK: Received - {BitConverter.ToString(Encoding.UTF8.GetBytes(buffer.ToString()))}"); + cumulus.LogDebugMessage("WaitForOK: Attempting to reconnect to logger"); + InitTCP(); + cumulus.LogDebugMessage("WaitForOK: Reconnected to logger"); + return false; + } + } + catch (Exception ex) + { + cumulus.LogDebugMessage($"WaitForOK: Error - {ex.Message}"); + cumulus.LogDebugMessage("WaitForOK: Attempting to reconnect to logger"); + InitTCP(); + cumulus.LogDebugMessage("WaitForOK: Reconnected to logger"); + return false; + } + + } while (buffer.ToString().IndexOf("OK\n\r") == -1); + cumulus.LogDebugMessage("WaitForOK: Found OK"); + return true; + } + + private bool WaitForACK(SerialPort serialPort, int timeoutMs = -1) { int currChar; diff --git a/CumulusMX/DavisWllStation.cs b/CumulusMX/DavisWllStation.cs index 9ca54d7a..ca36b892 100644 --- a/CumulusMX/DavisWllStation.cs +++ b/CumulusMX/DavisWllStation.cs @@ -119,7 +119,7 @@ public DavisWllStation(Cumulus cumulus) : base(cumulus) } else { - // Read the data from the logger + // Read the data from the WL APIv2 startReadingHistoryData(); } } diff --git a/CumulusMX/FOStation.cs b/CumulusMX/FOStation.cs index ae1eb6aa..da0b6288 100644 --- a/CumulusMX/FOStation.cs +++ b/CumulusMX/FOStation.cs @@ -468,9 +468,13 @@ private void processHistoryData() else { if (historydata.interval > 0) - rainrate = ConvertRainMMToUser((raindiff*0.3)*(60.0/historydata.interval)); + { + rainrate = ConvertRainMMToUser((raindiff * 0.3) * (60.0 / historydata.interval)); + } else + { rainrate = 0; + } } DoRain(ConvertRainMMToUser(historydata.rainCounter*0.3), rainrate, timestamp); diff --git a/CumulusMX/GW1000Station.cs b/CumulusMX/GW1000Station.cs index 363af6a2..28541e18 100644 --- a/CumulusMX/GW1000Station.cs +++ b/CumulusMX/GW1000Station.cs @@ -42,7 +42,9 @@ private enum Commands : byte { CMD_WRITE_CUSTOMIZED = 0x2B, // write back customized sever setting CMD_WRITE_UPDATE = 0x43,// update firmware CMD_READ_FIRMWARE_VERSION = 0x50,// read back firmware version - // the following command is only valid for GW1000 and WH2650: + CMD_READ_USER_PATH = 0x51, + CMD_WRITE_USER_PATH = 0x52, + // the following commands are only valid for GW1000 and WH2650: CMD_GW1000_LIVEDATA = 0x27, // read current,return size is 2 Byte CMD_GET_SOILHUMIAD = 0x28,// read Soilmoisture Sensor calibration parameter CMD_SET_SOILHUMIAD = 0x29, // write back Soilmoisture Sensor calibration parameter @@ -60,8 +62,11 @@ private enum Commands : byte { CMD_WRITE_CALIBRATION = 0x39,// write back multiple parameter offset CMD_READ_SENSOR_ID = 0x3A,// read Sensors ID CMD_WRITE_SENSOR_ID = 0x3B, // write back Sensors ID + CMD_READ_SENSOR_ID_NEW = 0x3C, CMD_WRITE_REBOOT = 0x40,// system rebset CMD_WRITE_RESET = 0x41,// system default setting reset + CMD_GET_CO2_OFFSET = 0x53, + CMD_SET_CO2_OFFSET = 0x54 } private enum CommandRespSize : int @@ -81,7 +86,9 @@ private enum CommandRespSize : int CMD_WRITE_CUSTOMIZED = 1, CMD_WRITE_UPDATE = 1, CMD_READ_FIRMWARE_VERSION = 1, - // the following command is only valid for GW1000 and WH2650: + CMD_READ_USER_PATH = 1, + CMD_WRITE_USER_PATH = 1, + // the following commands are only valid for GW1000 and WH2650: CMD_GW1000_LIVEDATA = 2, CMD_GET_SOILHUMIAD = 1, CMD_SET_SOILHUMIAD = 1, @@ -202,7 +209,8 @@ private enum SensorIds WH34_CH5, // 35 WH34_CH6, // 36 WH34_CH7, // 37 - WH34_CH8 // 38 + WH34_CH8, // 38 + WH45 // 39 }; public GW1000Station(Cumulus cumulus) : base(cumulus) @@ -848,6 +856,10 @@ public void GetLiveData() } idx += 8; break; + case 0x70: // WH45 CO₂ + batteryLow = batteryLow || DoCO2Decode(data, idx); + idx += 16; + break; default: cumulus.LogDebugMessage($"Error: Unknown sensor id found = {data[idx - 1]}, at position = {idx - 1}"); @@ -1003,6 +1015,47 @@ private byte[] DoCommand(byte command) } } + private bool DoCO2Decode(byte[] data, int index) + { + bool batteryLow = false; + int idx = index; + cumulus.LogDebugMessage("WH45 CO₂: Decoding..."); + //CO2Data co2Data = (CO2Data)RawDeserialize(data, index, typeof(CO2Data)); + + var temp = ConvertBigEndianInt16(data, idx) / 10; + idx += 2; + int hum = data[idx++]; + int pm10 = ConvertBigEndianUInt16(data, idx) / 10; + idx += 2; + int pm10_24h = ConvertBigEndianUInt16(data, idx) / 10; + idx += 2; + int pm2p5 = ConvertBigEndianUInt16(data, idx) / 10; + idx += 2; + int pm2p5_24h = ConvertBigEndianUInt16(data, idx) / 10; + idx += 2; + CO2 = ConvertBigEndianUInt16(data, idx); + idx += 2; + CO2_24h = ConvertBigEndianUInt16(data, idx); + idx += 2; + var batt = TestBattery3(data[idx]); + var msg = $"WH45 CO₂: temp={temp.ToString(cumulus.TempFormat)}, hum={hum}, pm10={pm10:F1}, pm10_24h={pm10_24h:F1}, pm2.5={pm2p5:F1}, pm2.5_24h={pm2p5_24h:F1}, CO₂={CO2}, CO₂_24h={CO2_24h}"; + if (tenMinuteChanged) + { + if (batt == "Low") + { + batteryLow = true; + msg += $", Battery={batt}"; + } + else + { + msg += $", Battery={batt}"; + } + } + cumulus.LogDebugMessage(msg); + + return batteryLow; + } + private bool DoBatteryStatus(byte[] data, int index) { bool batteryLow = false; @@ -1087,7 +1140,16 @@ private bool DoBatteryStatus(byte[] data, int index) else cumulus.LogDebugMessage(str); - str= "wh80> " + TestBattery4S(status.wh80) + " - " + TestBattery4V(status.wh80) + "V"; + str = "wh80> " + TestBattery4S(status.wh80) + " - " + TestBattery4V(status.wh80) + "V"; + if (str.Contains("Low")) + { + batteryLow = true; + cumulus.LogMessage(str); + } + else + cumulus.LogDebugMessage(str); + + str = "wh45> " + TestBattery4S(status.wh45) + " - " + TestBattery4V(status.wh45) + "V"; if (str.Contains("Low")) { batteryLow = true; @@ -1096,6 +1158,8 @@ private bool DoBatteryStatus(byte[] data, int index) else cumulus.LogDebugMessage(str); + + str = "wh55>" + " ch1=" + TestBattery3(status.wh55_ch1) + " ch2=" + TestBattery3(status.wh55_ch2) + @@ -1228,7 +1292,7 @@ private struct BatteryStatus public byte wh57; public byte wh68; public byte wh80; - private readonly byte unused1; + public byte wh45; public UInt16 wh41; public byte wh55_ch1; public byte wh55_ch2; @@ -1279,6 +1343,19 @@ public Sensors() { } } + + private struct CO2Data + { + public Int16 temp; // °C x10 + public byte hum; // % + public UInt16 pm10; // ug/m3 x10 + public UInt16 pm10_24hr; // ug/m3 x10 + public UInt16 pm2p5; // ug/m3 x10 + public UInt16 pm2p5_24hr; // ug/m3 x10 + public UInt16 co2; // ppm + public UInt16 co2_24hr; // ppm + public byte batt; // 0-5 + } */ private bool ChecksumOK(byte[] data, int lengthBytes) diff --git a/CumulusMX/MysqlSettings.cs b/CumulusMX/MysqlSettings.cs index 87496911..e0be1cf7 100644 --- a/CumulusMX/MysqlSettings.cs +++ b/CumulusMX/MysqlSettings.cs @@ -234,7 +234,11 @@ private string CreateMySQLTable(string createSQL) } finally { - mySqlConn.Close(); + try + { + mySqlConn.Close(); + } + catch {} } return res; diff --git a/CumulusMX/Program.cs b/CumulusMX/Program.cs index 46102956..92d7354e 100644 --- a/CumulusMX/Program.cs +++ b/CumulusMX/Program.cs @@ -17,9 +17,11 @@ internal class Program public static TextWriterTraceListener svcTextListener; const string appGuid = "57190d2e-7e45-4efb-8c09-06a176cef3f3"; public static Mutex appMutex; + public static DateTime StartTime; private static void Main(string[] args) { + StartTime = DateTime.Now; var Windows = Type.GetType("Mono.Runtime") == null; //var ci = new CultureInfo("en-GB"); //System.Threading.Thread.CurrentThread.CurrentCulture = ci; diff --git a/CumulusMX/Properties/AssemblyInfo.cs b/CumulusMX/Properties/AssemblyInfo.cs index 895f28d1..dac39bfc 100644 --- a/CumulusMX/Properties/AssemblyInfo.cs +++ b/CumulusMX/Properties/AssemblyInfo.cs @@ -6,7 +6,7 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("CumulusMX")] -[assembly: AssemblyDescription("Build 3093")] +[assembly: AssemblyDescription("Build 3094")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CumulusMX")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.8.3.3093")] -[assembly: AssemblyFileVersion("3.8.3.3093")] +[assembly: AssemblyVersion("3.8.4.3094")] +[assembly: AssemblyFileVersion("3.8.4.3094")] diff --git a/CumulusMX/WeatherStation.cs b/CumulusMX/WeatherStation.cs index 95139c37..d3845f8e 100644 --- a/CumulusMX/WeatherStation.cs +++ b/CumulusMX/WeatherStation.cs @@ -1336,6 +1336,9 @@ public void UpdateDegreeDays(int interval) public double AirQualityAvg3 { get; set; } public double AirQualityAvg4 { get; set; } + public int CO2 { get; set; } + public int CO2_24h { get; set; } + public int LeakSensor1 { get; set; } public int LeakSensor2 { get; set; } public int LeakSensor3 { get; set; } @@ -3340,8 +3343,10 @@ public void DoRain(double total, double rate, DateTime timestamp) } if (total - Raincounter > raintipthreshold) + { // rain has occurred LastRainTip = timestamp.ToString("yyyy-MM-dd HH:mm"); + } Raincounter = total; @@ -5411,13 +5416,13 @@ private void DoDayfile(DateTime timestamp) // run the query async so we do not block the main EOD processing Task.Run(() => { - MySqlCommand cmd = new MySqlCommand(); - cmd.CommandText = queryString.ToString(); - cmd.Connection = mySqlConn; - cumulus.LogMessage($"MySQL Dayfile: {cmd.CommandText}"); - try { + MySqlCommand cmd = new MySqlCommand(); + cmd.CommandText = queryString.ToString(); + cmd.Connection = mySqlConn; + cumulus.LogMessage($"MySQL Dayfile: {cmd.CommandText}"); + mySqlConn.Open(); int aff = cmd.ExecuteNonQuery(); cumulus.LogMessage($"MySQL Dayfile: Table {cumulus.MySqlDayfileTable} - {aff} rows were affected."); @@ -5429,7 +5434,11 @@ private void DoDayfile(DateTime timestamp) } finally { - mySqlConn.Close(); + try + { + mySqlConn.Close(); + } + catch {} } }); } @@ -6086,7 +6095,8 @@ public void DoTrendValues(DateTime ts) //cumulus.LogMessage("first value = " + fiveminutedata.First().raincounter + " last value = " + fiveminutedata.Last().raincounter); //cumulus.LogMessage("raindiff = " + raindiff); - var tempRainRate = (double)(raindiff / timediffhours); + // Scale the counter values + var tempRainRate = (double)(raindiff / timediffhours) * cumulus.RainMult; if (tempRainRate < 0) { diff --git a/CumulusMX/webtags.cs b/CumulusMX/webtags.cs index ad7b9c8a..05b75271 100644 --- a/CumulusMX/webtags.cs +++ b/CumulusMX/webtags.cs @@ -3425,6 +3425,16 @@ private string TagAirQualityAvg4(Dictionary TagParams) return CheckRC(station.AirQualityAvg4.ToString(cumulus.TempFormat), TagParams); } + private string TagCO2(Dictionary TagParams) + { + return station.CO2.ToString(); + } + + private string TagCO2_24h(Dictionary TagParams) + { + return station.CO2_24h.ToString(); + } + private string TagLeafTemp1(Dictionary TagParams) { return CheckRC(station.LeafTemp1.ToString(cumulus.TempFormat), TagParams); @@ -4548,10 +4558,18 @@ private string TagSystemUpTime(Dictionary TagParams) private string TagProgramUpTime(Dictionary TagParams) { - TimeSpan ts = DateTime.Now - Process.GetCurrentProcess().StartTime; + // Bug in Mono Process.StartTime - wraps after 24 days + TimeSpan ts = DateTime.Now - Program.StartTime; return String.Format("{0} days {1} hours", ts.Days, ts.Hours); } + private string TagProgramUpTimeMs(Dictionary TagParams) + { + // Bug in Mono Process.StartTime - wraps after 24 days + TimeSpan ts = DateTime.Now - Program.StartTime; + return ts.TotalMilliseconds.ToString(); + } + private string TagCpuName(Dictionary TagParams) { return "n/a"; @@ -5424,6 +5442,8 @@ public void InitialiseWebtags() { "AirQualityAvg2", TagAirQualityAvg2 }, { "AirQualityAvg3", TagAirQualityAvg3 }, { "AirQualityAvg4", TagAirQualityAvg4 }, + { "CO2", TagCO2 }, + { "CO2-24h", TagCO2_24h }, { "LeakSensor1", TagLeakSensor1 }, { "LeakSensor2", TagLeakSensor2 }, { "LeakSensor3", TagLeakSensor3 }, @@ -5621,6 +5641,7 @@ public void InitialiseWebtags() { "OsLanguage", TagOsLanguage }, { "SystemUpTime", TagSystemUpTime }, { "ProgramUpTime", TagProgramUpTime }, + { "ProgramUpTimeMs", TagProgramUpTimeMs }, { "CpuName", TagCpuName }, { "CpuCount", TagCpuCount }, { "MemoryStatus", TagMemoryStatus }, diff --git a/Updates.txt b/Updates.txt index 4e03654d..e0c89a13 100644 --- a/Updates.txt +++ b/Updates.txt @@ -1,11 +1,48 @@ +3.8.4 - b3094 +============= +- Fix: Unhandled exception in MySQL during the start-up catch-up process +- Fix: Graph buttons not wrapping on default web site trends page +- Fix: Calculating the trend rain rate when a rainfall multiplier is used +- Fix: Program Uptime when running under Mono and uptime > 24 days +- Fix: When running as a Windows service, the service was stopped unintentionally on non-critical system power events +- Fix: Davis VP receptions stats - ReceptionStats, FirmwareVersion and BaroRead functions rewritten +- Adds Ecowitt GW1000 decoding of WH34 battery status +- Adds Ecowitt GW1000 support for CO₂ sensor, no logging yet, but the decoding is there for when these sensors are released. + - New web tags <#CO2>, <#CO2-24h> +- Adds new Linux cumulusmx.service configuration file (thanks to freddie), this allows you run Cumulus MX as a service under Linux using the + newer systemd control rather than the original init.d script supplied with v3.8.0 + - Edit the cumulusmx.service configuration file and change the path to match your Cumulus MX installation folder + - Copy cumulusmx.service to your /etc/systemd/system/ folder + - Cumulus can then be started and stopped with the following commands: + > systemctl start cumulusmx + > systemctl stop cumulusmx + - Status and restart are available too: + > systemctl restart cumulusmx + > systemctl status cumulusmx + - If you make a change to the unit file, you need to run the following command so that systemd re-reads your file: + > systemctl daemon-reload + - To have Cumulus start on reboot, issue the following command: + > systemctl enable cumulusmx + - Finally, to stop the service running on reboot, use the following: + > systemctl disable cumulusmx +- Adds a new web tag <#ProgramUpTimeMs> that returns the Cumulus run time in milliseconds + +- Updated files + \CumulusMX.exe + \web\trendsT.htm + +- New files + \MXutils\linux\cumulusmx.service + + 3.8.3 - b3093 ============= - Fix: The dew point threshold limit (default value 40C) was incorrectly being applied to installations configured to use Fahrenheit as if the value were in Fahrenheit. -- Fix: Davis VP/VP2 force 1 minute baromter updates no longer working correctly after v3.8.2 protocol changes. +- Fix: Davis VP/VP2 force 1 minute barometer updates no longer working correctly after v3.8.2 protocol changes. Note: this setting should only be used for VP stations, or VP2 stations with very old firmware. - Fix: Last rain date not updating for Davis stations (fixes the mixed unit/bucket size update in 3.8.2) -- Adds two new Cumulus.ini file settings to control the numer of decimal places used for wind speeds. These override the built-in defaults. +- Adds two new Cumulus.ini file settings to control the number of decimal places used for wind speeds. These override the built-in defaults. These settings are read-only, they can to be manually added ONLY if you need to use them. Add either or both settings as required. [Station] WindSpeedDecimals=[0|1|2] @@ -23,9 +60,9 @@ - MXdiags log files are now rotated if they exceed 20MB in size, 15 log files are now retained - For Davis VP2 stations, setting the console clock now checks the console time and only sets it if it differs from the current time by greater than 60 seconds -- Davis VP2 stations protocols have been extensively rewritten to make them more efficent and less timing sensitive +- Davis VP2 stations protocols have been extensively rewritten to make them more efficient and less timing sensitive Third party IP loggers such as meteobridge and WiFi logger should now work more reliably -- Enables PSK authentication when using SFTP. Options are now Password, PSK, PSK with Password fallback +- Enables PSK authentication when using SFTP. Options are now Password, PSK, PSK with Password fall-back New Cumulus.ini entries... [FTP site] SshFtpAuthentication=password [password (default), psk, password_psk] @@ -91,7 +128,7 @@ - New feature: - When running on Windows as a service, the service will stop if the computer goes to standby/hibernate mode. A script to set up a Scheduled Task that will restart the service on resume from standby/hibernate is provided in the \MXutils\windows folder. - The script MUST be run with Administrator priveldges. + The script MUST be run with Administrator privileges. - Script name: \MXutils\windows\CreateCmxResumeFromStandbyTask.ps1 - Adds a new web tags for the Davis WLL device. <#ConsoleSupplyV> - The WLL external supply voltage @@ -99,7 +136,7 @@ <#MulticastGoodCnt> - Count of good multicast packets received <#MulticastGoodPct> - Percentage of good multicast packets received - On new installs of Cumulus MX, changes the default value of "WarnMultiple" from false to true -- Adds a download history limit of 50 bad packets for WMR200 station types. Prevously it could get stuck in a loop downloading bad data. +- Adds a download history limit of 50 bad packets for WMR200 station types. Previously it could get stuck in a loop downloading bad data. - Adds Show UV-I to the graph hide/show options - Updates ExportMySQL to include the new Humidex values from b3089 - AWEKAS uploads updated to API v4 - now can include indoor data, soil temp/moisture 1-4, and leaf wetness 1-2. And it supports rapid