private void ReceiveLoop() { StateObject state = new StateObject(); WFLogging.Info(" Starting receive loop"); state.workSocket = client; while (!finished) { // Start receive data from server while (client.Client.Available == 0) { Thread.Sleep(100); } state.offset = 0; client.Client.BeginReceive(state.buffer, 0, StateObject.bufsize, 0, new AsyncCallback(ReceiveCallback), state); receiveDone.WaitOne(); } // Wait for server to close connection WFLogging.Info(" Waiting for server close."); CloseDone.WaitOne(); client.Close(); Thread.Sleep(500); Started = false; }
internal void StopListenRapid(string device_id) { string message = "{ \"type\":\"listen_rapid_stop\", \"device_id\":"; message += device_id; message += ", \"id\":\"random-id-23456\" }"; WFLogging.Info(" Stopping listen for device " + device_id); SendMessage(client, message, 0x01); }
internal void StartListen(string device_id) { string message = "{ \"type\":\"listen_start\", \"device_id\":"; message += device_id; message += ", \"id\":\"random-id-23456\" }"; WFLogging.Info(" Starting listen for device " + device_id); SendMessage(client, message, 0x01); started.Add(device_id, true); }
internal void Server() { Thread handleClient; TcpListener server = new TcpListener(IPAddress.Parse(Address), Port); server.Start(); WFLogging.Log("Logging server has started on port " + Port.ToString()); // Add a new log client to get new events and send over // the websocket. WFLogging.AddListener(WSLog); while (true) { WFLogging.Info("Waiting for log client to connect"); client = server.AcceptTcpClient(); handleClient = new Thread(() => ClientHandler(client)); handleClient.Start(); } }
internal void Stop() { string message = "close"; WFLogging.Info(" Stop all listening."); foreach (string key in started.Keys) { StopListen(key); } started.Clear(); foreach (string key in started_rapid.Keys) { StopListenRapid(key); } started_rapid.Clear(); SendMessage(client, message, 0x08); finished = true; }
// handle a client connection private void ClientHandler(TcpClient client) { Byte[] bytes; NetworkStream stream = client.GetStream(); WFLogging.Info("Log client connected."); // Wait for data to be available while (!stream.DataAvailable) { ; } bytes = new Byte[client.Available]; stream.Read(bytes, 0, bytes.Length); //translate bytes of request to string String data = Encoding.UTF8.GetString(bytes); byte[] response = null; const string eol = "\r\n"; // HTTP/1.1 defines the sequence CR LF as the end-of-line marker WFLogging.Debug("GOT:" + bytes.Length.ToString() + ": " + data); if (new System.Text.RegularExpressions.Regex("^GET").IsMatch(data)) { } string protocol = new System.Text.RegularExpressions.Regex("Sec-WebSocket-Protocol: (.*)").Match(data).Groups[1].Value.Trim(); Console.WriteLine("Protocol: " + protocol); int l = bytes.Length; if (bytes[l - 1] == '\n' && bytes[l - 2] == '\r' && bytes[l - 3] == '\n') { response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + eol + "Upgrade: websocket" + eol + "Connection: Upgrade" + eol + "Sec-WebSocket-Protocol: " + protocol + eol + "Sec-WebSocket-Accept: " + Convert.ToBase64String( System.Security.Cryptography.SHA1.Create().ComputeHash( Encoding.UTF8.GetBytes( new System.Text.RegularExpressions.Regex("Sec-WebSocket-Key: (.*)").Match(data).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ) ) ) + eol + eol); stream.Write(response, 0, response.Length); stream.Flush(); WFLogging.Debug(Encoding.ASCII.GetString(response)); } else { // What do we do here if we don't get a proper header (or complete header)? WFLogging.Info("Didn't get a proper header, closing connection"); client.Close(); return; } // Start sending the log. // Only send the most recent 500 lines of data because sending more // here makes the interface unresponse for too long while it processes // it all. int start = (WFLogging.EventLogCount > 500) ? (WFLogging.EventLogCount - 500) : 0; for (int i = start; i < WFLogging.EventLogCount; i++) { string[] e = WFLogging.GetEvent(i); try { SendMessage(client, e[0] + "\t" + e[1], 0x01); } catch { // Sending to client failed for some reason. So abort. client.Close(); return; } } // Push the client to the client list clients.Add(client); // Handle data comming in over the connection. Mainly we want to // check for a close connection frame. If we get a close frame // then close the connection. while (stream.DataAvailable) { bytes = new Byte[client.Available]; stream.Read(bytes, 0, bytes.Length); // Data from client that needs to be decoded? WFLogging.Debug("Got " + bytes.Length.ToString() + " bytes from client to decode"); if ((bytes[0] & 0x80) == 0x80) { int payload_type = bytes[0] & 0x0f; int payload_size = bytes[1] & 0x7f; int payload_masking = bytes[1] & 0x80; WFLogging.Debug("type = " + payload_type.ToString() + " mask = " + payload_masking.ToString() + " len = " + payload_size.ToString()); if (payload_size < 126) { if (payload_masking == 0x80) { byte[] mask = new byte[4]; mask[0] = bytes[2]; mask[1] = bytes[3]; mask[2] = bytes[4]; mask[3] = bytes[5]; for (int i = 0; i < payload_size; i++) { bytes[6 + i] = (byte)(bytes[6 + i] ^ mask[i % 4]); } WFLogging.Debug("Payload: " + Encoding.ASCII.GetString(bytes, 6, payload_size)); } else { //for (int i = 0; i < payload_size; i++) // state.buffer[2+i] = (byte)(state.buffer[2+i] ^ 0x10); WFLogging.Debug("Payload: " + Encoding.ASCII.GetString(bytes, 2, payload_size)); } } else { WFLogging.Debug("Extended size: " + payload_size.ToString()); } switch (payload_type) { case 0x01: // text payload Console.WriteLine("Got a text payload"); break; case 0x02: // binary payload Console.WriteLine("Got a binary payload"); break; case 0x0A: // Pong break; case 0x09: // Ping // Send a pong message back Console.WriteLine("Received ping frame, should send a pong"); break; case 0x08: // close connection Console.WriteLine("Received close frame, closing connection"); clients.Remove(client); client.Close(); return; } } else { WFLogging.Debug("Non Frame: " + Encoding.ASCII.GetString(bytes, 0, bytes.Length)); } } }
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 static void ReceiveCallback(IAsyncResult ar) { StateObject state = (StateObject)ar.AsyncState; TcpClient client = state.workSocket; int bytes_read = client.Client.EndReceive(ar); if (bytes_read > 0) { int bi = 0; byte[] mask = new byte[4]; if ((state.buffer[0] & 0x80) == 0x80) { int payload_type = state.buffer[0] & 0x0f; int payload_size = state.buffer[1] & 0x7f; int payload_masking = state.buffer[1] & 0x80; bi += 2; //Console.WriteLine("type = " + payload_type.ToString() + " mask = " + payload_masking.ToString() + " len = " + payload_size.ToString()); if (payload_size == 126) { payload_size = (state.buffer[bi++] << 8) + state.buffer[bi++]; //Console.WriteLine("extended size = " + payload_size.ToString()); } if (payload_masking == 0x80) { mask[0] = state.buffer[bi++]; mask[1] = state.buffer[bi++]; mask[2] = state.buffer[bi++]; mask[3] = state.buffer[bi++]; } if (bytes_read > payload_size) { for (int i = 0; i < payload_size; i++) { if (payload_masking == 0x80) { state.buffer[bi + i] = (byte)(state.buffer[bi + i] ^ mask[i % 4]); } } if (payload_type == 0x01) { // TODO: Text type payload so send it somewhere //Console.WriteLine("Payload: " + Encoding.ASCII.GetString(state.buffer, bi, payload_size)); //Program.RaiseEvent(new WSEventArgs(Encoding.ASCII.GetString(state.buffer, bi, payload_size))); ProcessWSData(Encoding.ASCII.GetString(state.buffer, bi, payload_size)); } else if (payload_type == 0x02) { // Binary payload } else if (payload_type == 0x00) { WFLogging.Error("Got a continuation opcode, currently not supported."); } else if (payload_type == 0x08) { // Close frame finished = true; SendMessage(client, Encoding.ASCII.GetString(state.buffer, bi, payload_size), 0x08); } else if (payload_type == 0x09) { // Ping so we need to pong SendMessage(client, Encoding.ASCII.GetString(state.buffer, bi, payload_size), 0x0A); } receiveDone.Set(); return; } else { // We need to read more data WFLogging.Info("Need more data to complete frame"); client.Client.BeginReceive(state.buffer, 0, StateObject.bufsize, 0, new AsyncCallback(ReceiveCallback), state); return; } } } receiveDone.Set(); }
private void UpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { string report; string prefix = "ns/" + WF_Config.Profile.ToString() + "/nodes/"; DateTime start = DateTime.Now; if (++interval >= 10) { WFLogging.Info("ISY Request Rate: " + WeatherFlowNS.NS.Rest.stats.Rate + " requests/minute"); interval = 0; } foreach (string address in WeatherFlowNS.NS.NodeList.Keys) { if (WeatherFlowNS.NS.NodeList[address] == "WF_Hub") { if (!HeartBeat.ContainsKey(address)) { HeartBeat.Add(address, true); } if (HeartBeat[address]) { report = prefix + address + "/report/status/GV0/1/0"; } else { report = prefix + address + "/report/status/GV0/-1/0"; } HeartBeat[address] = !HeartBeat[address]; WeatherFlowNS.NS.Rest.REST(report); // CHECKME: Should we have a last update value for the hub? } else if (WeatherFlowNS.NS.NodeList[address] == "WF_AirD") { } else if (WeatherFlowNS.NS.NodeList[address] == "WF_SkyD") { } else if (WeatherFlowNS.NS.NodeList[address] == "WF_Lightning") { } else if (WeatherFlowNS.NS.NodeList[address] == "WF_RapidWind") { } else if (WeatherFlowNS.NS.NodeList[address] == "WF_LightningUS") { } else if (WeatherFlowNS.NS.NodeList[address] == "WF_RapidWindUS") { } else if (WeatherFlowNS.NS.NodeList[address] == "WF_LightningUK") { } else if (WeatherFlowNS.NS.NodeList[address] == "WF_RapidWindUK") { } else { // this should only sky & air nodes lock (_locker) { TimeSpan t = DateTime.UtcNow - new DateTime(1970, 1, 1); if (SecondsSinceUpdate.ContainsKey(address)) { int elapsed = (int)t.TotalSeconds - SecondsSinceUpdate[address].now; report = prefix + address + "/report/status/GV0/" + elapsed.ToString() + "/58"; WeatherFlowNS.NS.Rest.REST(report); } else { UDPTime utime; utime.timestamp = (int)t.TotalSeconds; // On startup, we don't know when the last message was sent utime.now = (int)t.TotalSeconds; SecondsSinceUpdate[address] = utime; } } } } WFLogging.Info("HandleHeartbeat " + DateTime.Now.Subtract(start).TotalMilliseconds.ToString("#.00") + " ms"); }
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 Process(HttpListenerContext context) { string filename = context.Request.Url.AbsolutePath; //Console.WriteLine(" request = " + filename); if (filename.Contains("config") || filename == "/") { // Handle configuration ConfigPage(context); return; } if (filename.Contains("/log")) { WFWebLog.LogPage(context); return; } if (filename == "/wflog") { WFWebLog.LogText(context); return; } if (filename == "/nodeinfo") { WFNodeInfo.NodeDocPage(context); return; } // WeatherFlow/install - install // Weatherflow/nodes/<address>/query - Query the node and report status // WeatherFlow/nodes/<address>/status - report current status // WeatherFlow/add/nodes - Add all nodes // These could have a ?requestid=<requestid> at the end // that means we need to send a message after completing the task // WeatherFlow/nodes/<address>/report/add // WeatherFlow/nodes/<address>/report/remove // WeatherFlow/nodes/<address>/report/rename // WeatherFlow/nodes/<address>/report/enable // WeatherFlow/nodes/<address>/report/disable // TODO: Parse out the request id if present if (filename.Contains("install")) { WFLogging.Info("Recieved request to install the profile files."); } else if (filename.Contains("query")) { string[] parts; parts = filename.Split('/'); WFLogging.Info("Query node " + parts[3]); } else if (filename.Contains("status")) { string[] parts; parts = filename.Split('/'); WFLogging.Info("Get status of node " + parts[3]); NodeStatus(); } else if (filename.Contains("add")) { WFLogging.Info("Add our node. How is this different from report/Add?"); AddNodes(); // the report API is not yet implemented on the ISY so we'll // never get anything of these until it is. } else if (filename.Contains("report/add")) { WFLogging.Info("Report that a node was added?"); } else if (filename.Contains("report/rename")) { } else if (filename.Contains("report/remove")) { } else if (filename.Contains("report/enable")) { } else if (filename.Contains("report/disable")) { } else if (filename.Contains("favicon.ico")) { } else { WFLogging.Error("Unknown Request: " + filename); } context.Response.ContentType = "text/plain"; context.Response.ContentLength64 = 0; context.Response.AddHeader("Date", DateTime.Now.ToString("r")); context.Response.StatusCode = (int)HttpStatusCode.OK; context.Response.OutputStream.Flush(); context.Response.OutputStream.Close(); context.Response.Close(); }