// server private IRemoteAcuriteData HttpSendAsync(Options options) { var http = new HttpAcuriteData(options.Port, options.Protocol, listenLocal: false); AcuriteData current = new AcuriteData(); // get the latest data OnPolled += (data) => { current = data; }; // serialize upon request http.OnSend += () => { // reset the stopwatch and restart LastNetQuery.Reset(); LastNetQuery.Start(); // serialize the most current data var json = System.Text.Json.JsonSerializer.Serialize <AcuriteData>(current); return(json); }; // start listening http.SendAsync(); return(http); }
private static void AcuriteDataTest() { var a = new AcuriteData(); a.utcDate = new DateTime(2020, 1, 1); a.pressure = 30; var b = new AcuriteData(); b.utcDate = new DateTime(2021, 1, 1); b.pressure = 31; var c = a + b; Console.WriteLine($"{c.pressure} {(c.pressureTrend == null ? "null" : c.pressureTrend[0]+"")}"); var d = new AcuriteData(); d.utcDate = new DateTime(2021, 2, 1); d.pressure = 32; var e = c + d; Console.WriteLine($"{e.pressure} {(e.pressureTrend == null ? "null" : e.pressureTrend[0] + "")}"); }
public AcuriteData Read(int vendorid, int productid) { var result = new AcuriteData() { utcDate = DateTime.UtcNow }; IUsbDevice wholeUsbDevice; // find the device var device = Context.Find((usb) => usb.ProductId == productid && usb.VendorId == vendorid); // check if we are unable to return a result if (device == null) { return(result); } try { // open the device for communication device.Open(); // on Linux we need to ask the kernel to detach the interface first if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { var key = ((long)productid << 32) | ((long)vendorid); if (!HasConfigured.Contains(key)) { if (device.DeviceHandle == null) { throw new Exception("failed to properly open device"); } var ec = DetachKernelDriver(device.DeviceHandle, 0); if (ec != Error.Success) { Console.WriteLine($"failed to detach kernel from device : {ec}"); } HasConfigured.Add(key); } } // If this is a "whole" usb device (libusb-win32, linux libusb-1.0) // it exposes an IUsbDevice interface. If not (WinUSB) the // 'wholeUsbDevice' variable will be null indicating this is // an interface of a device; it does not require or support // configuration and interface selection. wholeUsbDevice = device as IUsbDevice; if (!ReferenceEquals(wholeUsbDevice, null)) { // This is a "whole" USB device. Before it can be used, // the desired configuration and interface must be selected. // Select config #1 wholeUsbDevice.SetConfiguration(1); // Claim interface #0. wholeUsbDevice.ClaimInterface(0); } // read reports if (!ReadReport1(device, ref result)) { Console.WriteLine($"{DateTime.Now:o}: Failed to access report 1"); } if (!ReadReport2(device, ref result)) { Console.WriteLine($"{DateTime.Now:o}: Failed to access report 2"); } } finally { if (device.IsOpen) { // If this is a "whole" usb device (libusb-win32, linux libusb-1.0) // it exposes an IUsbDevice interface. If not (WinUSB) the // 'wholeUsbDevice' variable will be null indicating this is // an interface of a device; it does not require or support // configuration and interface selection. wholeUsbDevice = device as IUsbDevice; if (!ReferenceEquals(wholeUsbDevice, null)) { // Release interface #0. wholeUsbDevice.ReleaseInterface(0); } device.Close(); } } return(result); }
private bool ReadReport2(IUsbDevice device, ref AcuriteData result) { var buffer = new byte[25]; if (!Read(device, report: 2, buffer)) { Console.WriteLine($"{DateTime.Now:o}: Failed to read report 2"); return(false); } // ensure valid if (buffer.Length != 25 || buffer[0] != 0x02) { Console.WriteLine($"{DateTime.Now:o}: Invalid data in report 2"); return(false); } // constants var c1 = ((buffer[3] << 8) + buffer[4]); var c2 = ((buffer[5] << 8) + buffer[6]); var c3 = ((buffer[7] << 8) + buffer[8]); var c4 = ((buffer[9] << 8) + buffer[10]); var c5 = ((buffer[11] << 8) + buffer[12]); var c6 = ((buffer[13] << 8) + buffer[14]); var c7 = ((buffer[15] << 8) + buffer[16]); var a = buffer[17]; var b = buffer[18]; var c = buffer[19]; var d = buffer[20]; if (c1 == 0x8000 && c2 == c3 && c3 == 0x0 && c4 == 0x0400 && c5 == 0x1000 && c6 == 0x0 && c7 == 0x0960 && a == b && b == c && c == d && d == 0x1) { // MS5607 sensor, typical in 02032 consoles var d2 = (float)(((buffer[21] & 0x0f) << 8) + buffer[22]); if (d2 >= 0x0800) { d2 -= 0x1000; } var d1 = (float)((buffer[23] << 8) + buffer[24]); // decode // pressure result.pressure = (d1 / 16.0f) - (208f); // mbar result.pressure *= 0.029529983071f; // inHg result.pressure += 0.08106f; // hack (the hub seems this much off this conversion) // inside temperature result.inTemperature = 25.0f + (0.05f * d2); // celsius result.inTemperature = (result.inTemperature * 1.8f) + 32f; // fahrenheit } else { Console.WriteLine($"{DateTime.Now:o}: Failed to receive the right constants in report 2"); return(false); } return(true); }
private bool ReadReport1(IUsbDevice device, ref AcuriteData result) { var buffer = new byte[10]; if (!Read(device, report: 1, buffer)) { Console.WriteLine($"{DateTime.Now:o}: Failed to read report 1"); return(false); } // ensure valid if (buffer.Length != 10 || buffer[0] != 0x01) { Console.WriteLine($"{DateTime.Now:o}: Invalid data from hub"); return(false); } // check for errors if ((buffer[1] & 0x0f) == 0x0f && buffer[3] == 0xff) { // no sensor data Console.WriteLine($"{DateTime.Now:o}: No sensor data"); return(false); } else if ((buffer[3] & 0x0f) != 1 && (buffer[3] & 0x0f) != 8) { // bogus message flavor Console.WriteLine($"{DateTime.Now:o}: Bogus message flavor"); return(false); } else if (buffer[9] != 0xff && buffer[9] != 0x0) { // bogus final byte Console.WriteLine($"{DateTime.Now:o}: Bogus final byte"); return(false); } else if ((buffer[8] & 0x0f) < 0 || (buffer[8] & 0x0f) > 3) { // bogus signal strength Console.WriteLine($"{DateTime.Now:o}: Bogus signal strength"); return(false); } // decode // channel (todo A B C) result.channel = (ChannelName)(buffer[1] & 0xf0); // sensor id result.sensorId = ((buffer[1] & 0x0f) << 8) | buffer[2]; // signal strength result.signal = (SignalStrength)(buffer[8] & 0x0f); // stale data if (result.signal == SignalStrength.None) { Console.WriteLine($"{DateTime.Now:o}: Invalid signal strength"); } // low battery result.lowBattery = ((buffer[3] & 0xf0) >> 4) != 0x7; // wind speed result.windSpeed = (float)(((buffer[4] & 0x1f) << 3) | ((buffer[5] & 0x70) >> 4)); if (result.windSpeed > 0) { result.windSpeed = (result.windSpeed * 0.8278f) + 1.0f; // kph } result.windSpeed *= 0.62137f; // mph if ((buffer[3] & 0xf) == 1) { // wind direction var dir = buffer[5] & 0x0f; result.windDirection = WindDirection[(dir >= 0 && dir < WindDirection.Length) ? dir : 0]; // compass degrees // rain total result.rainTotal = (float)(((buffer[6] & 0x3f) << 7) | (buffer[7] & 0x7f)) * 0.01f; // inch } else { // outside temperature result.outTemperature = (float)(((buffer[5] & 0x0f) << 7) | (buffer[6] & 0x7f)) / 18.0f - 40.0f; // celsius result.outTemperature = (result.outTemperature * 1.8f) + 32f; // fahrenheit // outside humidity result.outHumidity = buffer[7] & 0x7f; // percentage } return(true); }
private async void FrameUpdate(object state) { if (Canvas == null) { throw new Exception("must have a valid canvas to draw too"); } // the timer is reentrant, so only allow one instance to run if (System.Threading.Interlocked.CompareExchange(ref FrameLock, 1, 0) != 0) { return; } // grab predictions var weather = await Prediction.CurrentWeather(); var weatherStation = await Prediction.CurrentWeatherStation(); var rowheight = 26f * Ratio; var fontsize = 18f * Ratio; var fontname = "Courier New"; var point = new Point() { X = 0f, Y = 0f }; var avoiddups = new HashSet <string>(); try { await Canvas.SuspendLayout(); // clear Canvas.Clear(RGBA.Black); // current weather point.Y = (rowheight * 0); Canvas.Text(RGBA.White, point, "Local weather", fontsize, fontname); foreach (var w in weather.OrderByDescending(wr => wr.Date)) { if (avoiddups.Contains(w.Type)) { continue; } switch (w.Type) { case "temperature": point.X = 0f; point.Y = (rowheight * 1); Canvas.Text(RGBA.White, point, $"current: {w.Value}°", fontsize, fontname); break; case "temperaturetrend": point.X = (rowheight * 6); point.Y = (rowheight * 1); Canvas.Text(RGBA.White, point, $"[{w.StrValue.ToLower()}]", fontsize, fontname); break; case "temperaturelow": point.X = 0f; point.Y = (rowheight * 2); Canvas.Text(RGBA.White, point, $"low: {w.Value}°", fontsize, fontname); break; case "temperaturehigh": point.X = 0f; point.Y = (rowheight * 3); Canvas.Text(RGBA.White, point, $"high: {w.Value}°", fontsize, fontname); break; case "winddirection": point.X = 0f; point.Y = (rowheight * 4); Canvas.Text(RGBA.White, point, $"wind: {w.StrValue}", fontsize, fontname); break; case "windspeed": point.X = (rowheight * 6); point.Y = (rowheight * 4); Canvas.Text(RGBA.White, point, $"{w.StrValue.ToLower()}", fontsize, fontname); break; case "shortforecast": point.X = 0f; point.Y = (rowheight * 5); var description = SplitString(w.StrValue, maxlength: 23); Canvas.Text(RGBA.White, point, $"{description.Item1}", fontsize, fontname); point.Y = (rowheight * 6); Canvas.Text(RGBA.White, point, $"{description.Item2}", fontsize, fontname); // debug if (true) { point.X = 0f; point.Y = (rowheight * 7); Canvas.Text(RGBA.White, point, $"{w.Date.ToLocalTime():yyyy/MM/dd hh:mm}", fontsize * 0.5f, fontname); } break; } avoiddups.Add(w.Type); } // weather station if (weatherStation.Count > 0) { var padding = (rowheight * 10); point.Y = (rowheight * 0); point.X = padding; Canvas.Text(RGBA.White, point, $"Weather Station", fontsize, fontname); // capture all the values var data = new AcuriteData(); foreach (var w in weatherStation.OrderByDescending(s => s.Date)) { if (data.utcDate == default(DateTime)) { data.utcDate = w.Date; } switch (w.Type) { case "outtemperature": if (!data.outTemperature.HasValue) { data.outTemperature = w.Value; } break; case "outhumidity": if (!data.outHumidity.HasValue) { data.outHumidity = w.Value; } break; case "pressure": if (!data.pressure.HasValue) { data.pressure = w.Value; } break; case "pressuretrend": if (data.pressureTrend == null) { data.pressureTrend = w.Values; } break; case "winddirection": if (!data.windDirection.HasValue) { data.windDirection = w.Value; } break; case "windspeed": if (!data.windSpeed.HasValue) { data.windSpeed = w.Value; } break; case "raintotal": if (!data.rainTotal.HasValue) { data.rainTotal = w.Value; } break; case "raintotaltrend": if (data.rainTotalTrend == null) { data.rainTotalTrend = w.Values; } break; } } // display the data if (data.outTemperature.HasValue) { point.X = padding; point.Y = (rowheight * 1); Canvas.Text(RGBA.White, point, $"current: {data.outTemperature:f0}°", fontsize, fontname); } if (data.outHumidity.HasValue) { point.X = padding; point.Y = (rowheight * 2); Canvas.Text(RGBA.White, point, $"humidity: {data.outHumidity:f0}%", fontsize, fontname); } if (data.pressure.HasValue) { point.X = padding; point.Y = (rowheight * 3); Canvas.Text(RGBA.White, point, $"pressure: {data.pressure:f2} inHg", fontsize, fontname); if (data.pressureTrend != null) { point.X = padding; point.Y = (rowheight * 4); var trend = ReadPressureTrend(data.pressure.Value, data.pressureTrend); var change = trend.Item2 == PressureChange.Failing ? "↓" : (trend.Item2 == PressureChange.Rising ? "↑" : "-"); Canvas.Text(RGBA.White, point, $" {trend.Item3} {change}", fontsize, fontname); } } if (data.windDirection.HasValue) { point.X = padding; point.Y = (rowheight * 5); Canvas.Text(RGBA.White, point, $"wind: {DegreesToCompass(data.windDirection.Value)}", fontsize, fontname); } if (data.windSpeed.HasValue) { point.X = padding + (rowheight * 6); point.Y = (rowheight * 5); Canvas.Text(RGBA.White, point, $"{data.windSpeed:f0} mph", fontsize, fontname); } if (data.rainTotal.HasValue) { point.X = padding; point.Y = (rowheight * 6); var rainTotal = data.rainTotal; if (data.rainTotalTrend != null && data.rainTotalTrend.Length >= 1) { rainTotal -= data.rainTotalTrend[data.rainTotalTrend.Length - 1]; } Canvas.Text(RGBA.White, point, $"rain: {rainTotal:f2} in", fontsize, fontname); // debug if (true) { point.X = padding; point.Y = (rowheight * 7); Canvas.Text(RGBA.White, point, $"{data.utcDate.ToLocalTime():yyyy/MM/dd hh:mm}", fontsize * 0.5f, fontname); } } } } finally { await Canvas.ResumeLayout(); } // fire that the frame is done if (OnRendered != null) { OnRendered(); } // set state back to not running System.Threading.Volatile.Write(ref FrameLock, 0); }
// // Common // private void PollStation(Options options) { // setup var station = new AcuriteStation(); var combined = new AcuriteData(); // poll the station until asked to stop var failCount = 0; while (true) { // check if the poll timeout has occured (in which case, skip this station read) if (LastNetQuery.ElapsedMilliseconds < options.SleepPolling) { // read data AcuriteData current; if (options.VendorId.HasValue && options.ProductId.HasValue) { current = station.Read(options.VendorId.Value, options.ProductId.Value); } else { current = station.Read(); } // send if there is data if (!current.HasValue()) { failCount++; Console.WriteLine($"{DateTime.Now:o}: failed to get a station reading"); if (failCount >= options.MaxPollFailures) { throw new Exception($"Failed to get station data {failCount} in a row"); } } else { // reset fail count (if set) failCount = 0; // combine the data into a view of the latest combined = combined + current; // display var data = combined; if (options.RawData) { data = current; } Console.WriteLine($"{DateTime.Now:o}:{(options.RawData ? " [raw]" : "")} {data.channel},{data.sensorId},{data.signal},{data.lowBattery},{data.windSpeed},{data.windDirection},{data.rainTotal},{data.outTemperature},{data.outHumidity},{data.pressure},{data.inTemperature},{(data.pressureTrend != null ? data.pressureTrend.Length : 0)},{(data.rainTotalTrend != null ? data.rainTotalTrend.Length : 0)}"); // notifiy of data if (OnPolled != null) { OnPolled(data); } } } System.Threading.Thread.Sleep(options.Interval); } }