internal void Updated(string address, int timestamp, WeatherFlow_UDP.DataType t) { // Check for missing data here. UDPTime utime; TimeSpan epoch = DateTime.UtcNow - new DateTime(1970, 1, 1); utime.timestamp = timestamp; utime.now = (int)epoch.TotalSeconds; if (!SecondsSinceUpdate.ContainsKey(address)) { SecondsSinceUpdate[address] = utime; } // TODO: The diff should be 2x the interval. Is there some way to get that // information here? int diff = timestamp - SecondsSinceUpdate[address].timestamp; switch (t) { case WeatherFlow_UDP.DataType.AIR: case WeatherFlow_UDP.DataType.SKY: // Expect data every 60 seconds if (diff >= 120) { WFLogging.Warning("Possible Missing data for " + address + ": " + diff.ToString() + " seconds"); } break; case WeatherFlow_UDP.DataType.DEVICE: // Expect data every 60 seconds if (diff >= 90) { WFLogging.Warning("Possible Missing data for " + address + ": " + diff.ToString() + " seconds"); } break; case WeatherFlow_UDP.DataType.HUB: // Expect data every 10 seconds if (diff >= 20) { WFLogging.Warning("Possible Missing data for " + address + ": " + diff.ToString() + " seconds"); } break; case WeatherFlow_UDP.DataType.WIND: // Expect data every 3 seconds if (diff >= 6) { WFLogging.Warning("Possible Missing data for " + address + ": " + diff.ToString() + " seconds"); } break; } SecondsSinceUpdate[address] = utime; }
// TODO: // How do we want to handle the device status? There should // be at least 2 devices present (Air & Sky). Do we create // a HS record for each and use that as a place to store the // device specific data? Or do we just create an internal // list of devivces? // // Whatever we do here we should probably do for the hub // as well (treat it as a third device). private void DeviceStatus(string json) { JavaScriptSerializer serializer = new JavaScriptSerializer(); try { DeviceObj = serializer.Deserialize <DeviceData>(json); // Add event to update this info. try { WeatherFlowNS.NS.RaiseDeviceEvent(this, new WFNodeServer.DeviceEventArgs(DeviceObj)); } catch { WFLogging.Warning("Failed to process device event."); } WeatherFlowNS.NS.RaiseUpdateEvent(this, new UpdateEventArgs((int)DeviceObj.timestamp, DeviceObj.serial_number + "_d", DataType.DEVICE)); //Console.WriteLine("Serial Number: " + DeviceObj.serial_number); //Console.WriteLine("Device Type: " + DeviceObj.type); //Console.WriteLine("Hub Serial Number: " + DeviceObj.hub_sn); //Console.WriteLine("timestamp: " + DeviceObj.timestamp.ToString()); //Console.WriteLine("uptime: " + DeviceObj.uptime.ToString()); //Console.WriteLine("Voltage: " + DeviceObj.voltage.ToString()); //Console.WriteLine("Firmware: " + DeviceObj.firmware_revision.ToString()); //Console.WriteLine("RSSI: " + DeviceObj.rssi.ToString()); //Console.WriteLine("Sensor status: " + DeviceObj.sensor_status.ToString()); if (Sensors.ContainsKey(DeviceObj.serial_number)) { Sensors[DeviceObj.serial_number] = DeviceObj; } else { Sensors.Add(DeviceObj.serial_number, DeviceObj); } } catch (Exception ex) { WFLogging.Error("Deserialization of device status failed: " + ex.Message); } }
private void SkyObservations(string json) { JavaScriptSerializer serializer = new JavaScriptSerializer(); try { SkyObj = serializer.Deserialize <SkyData>(json); SkyObj.valid = true; WFNodeServer.SkyEventArgs evnt = new SkyEventArgs(SkyObj); evnt.SetDaily = CalcDailyPrecipitation(); evnt.Raw = json; try { WeatherFlowNS.NS.RaiseSkyEvent(this, evnt); } catch (Exception ex) { WFLogging.Warning("Failed to process Sky event. " + ex.Message); } WeatherFlowNS.NS.RaiseUpdateEvent(this, new UpdateEventArgs((int)SkyObj.obs[0][0].GetValueOrDefault(), SkyObj.serial_number, DataType.SKY)); } catch (Exception ex) { WFLogging.Error("Deserialization failed for sky data: " + ex.Message); WFLogging.Verbose(json); return; } }
internal void Start() { byte[] buf = new byte[512]; int len; bool header = false; while (Started) { int retries = 0; WFLogging.Warning("Attempt to start already active WebSocket connection " + retries.ToString()); Thread.Sleep(1000); if (retries++ > 10) { WFLogging.Error("Giving up after 10 attempts."); return; } } client = new TcpClient(Host, Port); if (!client.Connected) { WFLogging.Error("Client not connected to " + Port); } WFLogging.Log("Starting communication with websocket server."); finished = false; Started = true; // Send header var seckeybytes = Encoding.UTF8.GetBytes(seckey); client.Client.Send(Encoding.ASCII.GetBytes("GET " + Path + " HTTP/1.1\r\n")); client.Client.Send(Encoding.ASCII.GetBytes("Host: " + Host + ":" + Port.ToString() + "\r\n")); client.Client.Send(Encoding.ASCII.GetBytes("Upgrade: websocket\r\n")); client.Client.Send(Encoding.ASCII.GetBytes("Connection: Upgrade\r\n")); client.Client.Send(Encoding.ASCII.GetBytes("Pragma: no-cache\r\n")); client.Client.Send(Encoding.ASCII.GetBytes("Origin: http://" + Host + "\r\n")); client.Client.Send(Encoding.ASCII.GetBytes("Cache-Control: no-cache\r\n")); client.Client.Send(Encoding.ASCII.GetBytes("Sec-WebSocket-Key: " + System.Convert.ToBase64String(seckeybytes) + "\r\n")); client.Client.Send(Encoding.ASCII.GetBytes("Sec-WebSocket-Version: 13\r\n")); client.Client.Send(Encoding.ASCII.GetBytes("\r\n")); WFLogging.Info(" Waiting for handshake"); Thread.Sleep(100); // Receive handshake while (!header) { if (client.Client.Available > 0) { len = client.Client.Receive(buf, (client.Client.Available - 1), SocketFlags.None); if (len > 0) { string strbuf = Encoding.ASCII.GetString(buf, 0, len); // Just look for the a websock type response, ignore the rest of the headers if (strbuf.Contains("HTTP/1.1 101")) { header = true; } } } Thread.Sleep(500); } receive_thread = new Thread(new ThreadStart(ReceiveLoop)); receive_thread.IsBackground = true; receive_thread.Start(); }
private void ConfigPage(HttpListenerContext context) { string cfg_page; byte[] page; byte[] post = new byte[1024]; Thread do_something; //Console.WriteLine("content length = " + context.Request.ContentLength64.ToString()); if (context.Request.ContentLength64 > 0) { string[] list; string[] pair; int len = (int)context.Request.ContentLength64; bool initISY = false; bool saveCfg = false; context.Request.InputStream.Read(post, 0, len); string resp = Encoding.Default.GetString(post); resp = resp.Substring(0, len); //Console.WriteLine("Response = " + resp); cfg_file_status = ""; if (resp.Contains("stationcfg")) { station_form a_form = GetStationInfo(resp); switch (a_form.action) { case "Delete": WFLogging.Info("Delete station " + a_form.station_id.ToString()); WeatherFlowNS.NS.DeleteStation(a_form.station_id); saveCfg = true; break; case "++Add++": bool exists = false; WFLogging.Info("Add station " + a_form.station_id.ToString()); // Check if station already exists foreach (StationInfo s in WF_Config.WFStationInfo) { if (s.station_id == a_form.station_id) { cfg_file_status = "Station " + a_form.station_id.ToString() + " already exists."; exists = true; break; } } if (!exists) { saveCfg = AddStation(a_form); } break; case "Update": WFLogging.Info("Update station " + a_form.station_id.ToString()); saveCfg = AddStation(a_form); break; default: WFLogging.Warning("Unknown action [" + a_form.action + "]"); break; } } else if (resp.Contains("upload")) { // Upload profile files TODO: Should this be done in a thread? do_something = new Thread(WeatherFlowNS.NS.UpdateProfileFiles); do_something.IsBackground = true; do_something.Start(); } else { list = resp.Split('&'); foreach (string item in list) { int v = 0; pair = item.Split('='); pair[1] = HttpUtility.UrlDecode(pair[1]); switch (pair[0]) { case "sAddress": if (pair[1] != WF_Config.ISY) { WF_Config.ISY = pair[1]; initISY = true; saveCfg = true; } break; case "sUsername": if (pair[1] != WF_Config.Username) { WF_Config.Username = pair[1]; initISY = true; saveCfg = true; } break; case "sPassword": if (pair[1] != WF_Config.Password) { WF_Config.Password = pair[1]; initISY = true; saveCfg = true; } break; case "sProfile": int.TryParse(pair[1], out v); if (v != WF_Config.Profile) { WF_Config.Profile = v; initISY = true; saveCfg = true; } break; case "webPort": int.TryParse(pair[1], out v); if (v != WF_Config.Port) { WF_Config.Port = v; saveCfg = true; } break; case "sSI": bool imperial = (pair[1] == "1"); if (imperial != WF_Config.SI) { WF_Config.SI = (pair[1] == "1"); saveCfg = true; } break; case "sHub": bool hub = (pair[1] == "1"); if (hub != WF_Config.Hub) { WF_Config.Hub = (pair[1] == "1"); saveCfg = true; } break; case "sDevice": bool device = (pair[1] == "1"); if (device != WF_Config.Device) { WF_Config.Device = (pair[1] == "1"); saveCfg = true; } break; case "sUnits": int.TryParse(pair[1], out v); if (v != WF_Config.Units) { WF_Config.Units = v; saveCfg = true; } break; case "sLogLevel": int.TryParse(pair[1], out v); if (v != WF_Config.LogLevel) { WF_Config.LogLevel = v; WFLogging.Level = (LOG_LEVELS)v; saveCfg = true; } break; case "serverctl": if (pair[1].Contains("Restart")) { WeatherFlowNS.NS.udp_client.Start(); WeatherFlowNS.NS.heartbeat.Start(); cfg_file_status = "Server Started"; Thread.Sleep(400); } else if (pair[1].Contains("Pause")) { WeatherFlowNS.NS.heartbeat.Stop(); WeatherFlowNS.NS.udp_client.Stop(); cfg_file_status = "Server Paused"; } break; case "websocket": if (pair[1].Contains("Start")) { try { WeatherFlowNS.NS.StartWebSocket(); } catch (Exception ex) { WFLogging.Error("Starting websocket client failed: " + ex.Message); } cfg_file_status = "Websocket Client Started"; Thread.Sleep(400); } else if (pair[1].Contains("Stop")) { WeatherFlowNS.NS.wsi.Stop(); cfg_file_status = "Websocket Client Stopped"; Thread.Sleep(800); } break; default: break; } } } if (saveCfg) { cfg_file_status += WeatherFlowNS.SaveConfiguration(); } if (initISY) { WeatherFlowNS.NS.InitializeISY(); } } try { cfg_page = MakeConfigPage(); } catch (Exception ex) { WFLogging.Error("Failed to make configuration web page."); WFLogging.Error(ex.Message); context.Response.Close(); return; } // How can we substitute values into the page? May need to dynamically // generate the page instead of storing it as a resource. That would // be a bit of a pain. page = ASCIIEncoding.ASCII.GetBytes(cfg_page); context.Response.ContentType = "text/html"; context.Response.ContentLength64 = page.Length; context.Response.AddHeader("Date", DateTime.Now.ToString("r")); context.Response.StatusCode = (int)HttpStatusCode.OK; context.Response.OutputStream.Write(page, 0, page.Length); context.Response.OutputStream.Flush(); context.Response.OutputStream.Close(); context.Response.Close(); }
private void AirObservations(string json) { JavaScriptSerializer serializer = new JavaScriptSerializer(); // obs[0][0] = time (seconds) // obs[0][1] = station pressure (MB) // obs[0][2] = air temp (c) // obs[0][3] = humidity (%) // obs[0][4] = lightning count // obs[0][5] = avg lightning dist (km) // obs[0][6] = battery // obs[0][7] = interval (minutes) try { double elevation = 0; AirObj = serializer.Deserialize <AirData>(json); AirObj.valid = true; // Look up elevation StationInfo si = wf_station.FindStationAir(AirObj.serial_number); if (si != null) { elevation = si.elevation; } // Do we just want to raise an event with the data object? AirEventArgs evnt = new AirEventArgs(AirObj); evnt.SetDewpoint = 0; evnt.SetApparentTemp = 0; evnt.SetTrend = 1; evnt.SetSeaLevel = SeaLevelPressure(AirObj.obs[0][(int)AirIndex.PRESSURE].GetValueOrDefault(), elevation); evnt.Raw = json; if (SkyObj.valid) { try { evnt.SetDewpoint = CalcDewPoint(); evnt.SetApparentTemp = FeelsLike(AirObj.obs[0][(int)AirIndex.TEMPURATURE].GetValueOrDefault(), AirObj.obs[0][(int)AirIndex.HUMIDITY].GetValueOrDefault(), SkyObj.obs[0][(int)SkyIndex.WIND_SPEED].GetValueOrDefault()); // Trend is -1, 0, 1 while event wants 0, 1, 2 evnt.SetTrend = PressureTrend() + 1; // Heat index & Windchill ?? } catch { } } else { } try { WeatherFlowNS.NS.RaiseAirEvent(this, evnt); } catch (Exception ex) { WFLogging.Warning("Failed to process Air event. " + ex.Message); } WeatherFlowNS.NS.RaiseUpdateEvent(this, new UpdateEventArgs((int)AirObj.obs[0][0].GetValueOrDefault(), AirObj.serial_number, DataType.AIR)); } catch (Exception ex) { WFLogging.Error("Deserialization failed for air data: " + ex.Message); WFLogging.Verbose(json); } }