using System; using System.Collections.Generic; using System.ComponentModel; using System.Data.Odbc; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json.Linq; using NightScout.Interfaces; using NightscoutLibrary.Configuration; using Quobject.EngineIoClientDotNet.Client.Transports; using Quobject.SocketIoClientDotNet.Client; namespace NightScout { public class NightscoutDataSource : INightscoutDataSource { public Socket Socket { get; set; } public Manager Manager { get; set; } public log4net.ILog Logger { get; set; } public IRaspberryPiButtonSource ButtonSource { get; set; } public INightscoutAlertConfiguration NightscoutAlertConfiguration { get; set; } public event Action Connected; public event Action DataUpdate; public event Action SocketError; public event Action Alarm; public event Action UrgentAlarm; public event Action ClearAlarm; public event Action StaleDataAlarm; private DateTime lastDataUpdate = DateTime.Now; private DateTime lastSgvReading = DateTime.Now; public NightscoutDataSource(INightscoutAlertConfiguration configuration, log4net.ILog logger) { Logger = logger; NightscoutAlertConfiguration = configuration; ResetManagerAndConnect(); PrepareSocketEvents(); } public NightscoutDataSource(INightscoutAlertConfiguration configuration, log4net.ILog logger, IRaspberryPiButtonSource buttonSource) : this(configuration, logger) { ButtonSource = buttonSource; ButtonSource.ButtonStateChange += ButtonSource_ButtonStateChange; } public void Emit(String eventName, String data) { Socket.Emit(eventName, data); } public void Emit(String eventName, JObject data) { Socket.Emit(eventName,data); } public virtual void Authorize() { JObject authRequest = new JObject {{"client", "web"}}; Socket.Emit("authorize", authRequest); } public virtual void ClearAlerts() { Logger.Info("Sending Clear Alerts Message"); Socket.Emit("ack", 2, "default", 45 * 60 * 1000); Logger.Debug("Sent ack"); } protected virtual void OnConnect() { Logger.Info("Connected to Socket.IO endpoint"); if (Connected != null) Connected(); } protected virtual void OnDataUpdate(object data) { Logger.DebugFormat("Data Update: {0}", data); if (DataUpdate != null) DataUpdate(data); //Update lastUpdated JObject dataUpdate = JObject.Parse(data.ToString()); if (dataUpdate["lastUpdated"] != null) { //lastUpdated is in ms Int32 lastUpdatedUnixTime = (Int32)(dataUpdate["lastUpdated"].ToObject() / 1000); lastDataUpdate = UnixTimeStampToDateTime(lastUpdatedUnixTime); Logger.InfoFormat("Last data update reset to {0}", lastDataUpdate); } if (dataUpdate["sgvs"] != null) { var sgvs = dataUpdate["sgvs"].Children(); var newestSSgv = sgvs.Where(sgv => sgv["mills"] != null).OrderByDescending(sgv => sgv["mills"].Value()).First(); Int64 sgvReadTime = newestSSgv["mills"].Value(); DateTime reading = UnixTimeStampToDateTime(sgvReadTime / 1000);; if (reading > lastSgvReading) lastSgvReading = reading; if(newestSSgv["mgdl"] != null) Logger.InfoFormat("New sgv read of {0} mg/dl on {1}", newestSSgv["mgdl"], lastSgvReading); } } protected virtual void OnSocketError(object error) { //Socket.Close(); Logger.InfoFormat("Socket Error: {0}", error); if (SocketError != null) SocketError(error); } protected virtual void OnAlarm(object alarm) { Logger.InfoFormat("Alarm: {0}", alarm); if (Alarm != null) Alarm(alarm); } protected virtual void OnUrgentAlarm(object alarm) { Logger.InfoFormat("Urgent Alarm: {0}", alarm); if (UrgentAlarm != null) UrgentAlarm(alarm); } protected virtual void OnClearAlarm(object alarm) { Logger.InfoFormat("Clear Alarm: {0}", alarm); if (ClearAlarm != null) ClearAlarm(alarm); } protected virtual void OnStaleDataAlarm() { if (StaleDataAlarm != null) StaleDataAlarm(); } protected void ResetManagerAndConnect() { //, DisablePing = true, Transports = new List() { "polling" } Options options; options = new IO.Options(); Manager = new Manager(new Uri(NightscoutAlertConfiguration.NightscoutBaseURL), options); Socket = Manager.Socket("/"); //IO.Socket(configuration.NightscoutBaseURL, options); } private void HeartbeatThread() { while (true) { //30 minutes - (currentTime - lastDataUpdate) const double millisecondsToSleep = (5*60*1000); //(30*60*1000) - (DateTime.Now - lastDataUpdate).TotalMilliseconds; Thread.Sleep((Int32)millisecondsToSleep); if ((DateTime.Now - lastDataUpdate).TotalMinutes >= 20) { //forcefully reset data connection Logger.Info("Data update timeout, closing socket"); Manager.EngineSocket.Close(); lastDataUpdate = DateTime.Now; } if ((DateTime.Now - lastSgvReading).TotalMinutes >= 30) { //Stale data alarm OnStaleDataAlarm(); Logger.Info("Data stale alarm"); lastSgvReading = DateTime.Now; } } } private void PrepareSocketEvents() { Socket.On(Socket.EVENT_CONNECT, OnConnect); Socket.On(Socket.EVENT_ERROR, OnSocketError); Socket.On("dataUpdate", OnDataUpdate); Socket.On("alarm", OnAlarm); Socket.On("urgent_alarm", OnUrgentAlarm); Socket.On("clear_alarm", OnClearAlarm); Manager.On(Manager.EVENT_CONNECT_ERROR, (err) => Logger.InfoFormat("EVENT_CONNECT_ERROR: {0}",err)); Manager.On(Manager.EVENT_ERROR, (err) => Logger.InfoFormat("EVENT_ERROR: {0}", err)); Manager.On(Manager.EVENT_RECONNECT_ERROR, (err) => Logger.InfoFormat("EVENT_RECONNECT_ERROR: {0}", err)); Manager.On(Manager.EVENT_RECONNECT_FAILED, () => Logger.Info("EVENT_RECONNECT_FAILED")); Manager.On(Manager.EVENT_CLOSE, (reason) => Logger.InfoFormat("EVENT_CLOSE: {0}", reason)); Task.Factory.StartNew(HeartbeatThread,TaskCreationOptions.LongRunning); } private void ButtonSource_ButtonStateChange(bool obj) { if (obj) { //Button pressed, STOP active alerts ClearAlerts(); } } private static DateTime UnixTimeStampToDateTime(double unixTimeStamp) { // Unix timestamp is seconds past epoch System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc); dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime(); return dtDateTime; } } }