├── FloatingGlucose ├── installer │ ├── Output │ │ └── .gitignore │ ├── floatingglucose.iss │ └── floatingglucosenosign.iss ├── bin │ └── .gitignore ├── obj │ └── .gitignore ├── Resources │ ├── alarm.mp3 │ ├── alarm2.mp3 │ ├── browse-file.ico │ └── noun_335372_cc_v2.ico ├── noun_335372_cc_v2.ico ├── Classes │ ├── Utils │ │ ├── GlucoseAlarmStatusEnum.cs │ │ ├── BGReading.cs │ │ ├── Validators.cs │ │ ├── DataProtectorConverter.cs │ │ ├── DataProtector.cs │ │ └── GlucoseMath.cs │ ├── DataSources │ │ ├── Exceptions │ │ │ ├── NoSuchPluginException.cs │ │ │ ├── NoPluginChosenException.cs │ │ │ ├── MissingDataException.cs │ │ │ ├── InvalidJsonDataException.cs │ │ │ └── ConfigValidationException.cs │ │ ├── Plugins │ │ │ ├── DexcomShare │ │ │ │ ├── DexcomShareNonUS.cs │ │ │ │ └── DexcomShareUS.cs │ │ │ ├── NightscoutPebbleEndpoint │ │ │ │ ├── GeneratedNsData.cs │ │ │ │ └── NightscoutPebbleEndpoint.cs │ │ │ ├── NightscoutPebbleFileEndpoint.cs │ │ │ ├── GlimpFileEndpoint.cs │ │ │ └── YrWeatherService │ │ │ │ ├── YrWeatherServiceEndpoint.cs │ │ │ │ └── YrHourByHour.cs │ │ ├── DataSourceInfo.cs │ │ ├── IDataSourcePlugin.cs │ │ └── PluginLoader.cs │ ├── Extensions │ │ ├── ColorExtensions.cs │ │ ├── FormExtensions.cs │ │ ├── DatetimeExtensions.cs │ │ └── IDataSourcePluginExtensions.cs │ ├── AppShared.cs │ └── Audio │ │ ├── LoopStream.cs │ │ └── SoundAlarm.cs ├── Properties │ ├── FormSettings.settings │ ├── AssemblyInfo.cs │ ├── FormSettings.Designer.cs │ ├── Settings.settings │ ├── Resources.Designer.cs │ ├── Resources.resx │ └── Settings.Designer.cs ├── README_attributions.txt ├── Program.cs ├── Settings.cs ├── FormWebbrowser.Designer.cs ├── FormWebbrowser.cs ├── buildscripts │ └── create-zip-file.ps1 ├── app.manifest ├── FloatingGlucose.csproj ├── App.config ├── FormWebbrowser.resx ├── FormGlucoseSettings.resx ├── .editorconfig ├── FloatingGlucose.resx └── FloatingGlucose.Designer.cs ├── floatingglucose.png ├── .upgrade-assistant ├── .gitmodules ├── .github └── workflows │ └── FloatingGlucose.yaml ├── README.md ├── FloatingGlucose.sln ├── .gitignore └── log.txt /FloatingGlucose/installer/Output/.gitignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /floatingglucose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/FloatingGlucose/master/floatingglucose.png -------------------------------------------------------------------------------- /FloatingGlucose/bin/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/FloatingGlucose/master/FloatingGlucose/bin/.gitignore -------------------------------------------------------------------------------- /FloatingGlucose/obj/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/FloatingGlucose/master/FloatingGlucose/obj/.gitignore -------------------------------------------------------------------------------- /FloatingGlucose/Resources/alarm.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/FloatingGlucose/master/FloatingGlucose/Resources/alarm.mp3 -------------------------------------------------------------------------------- /FloatingGlucose/Resources/alarm2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/FloatingGlucose/master/FloatingGlucose/Resources/alarm2.mp3 -------------------------------------------------------------------------------- /FloatingGlucose/noun_335372_cc_v2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/FloatingGlucose/master/FloatingGlucose/noun_335372_cc_v2.ico -------------------------------------------------------------------------------- /.upgrade-assistant: -------------------------------------------------------------------------------- 1 | {"Build":"0.2.226201\u002B4598a23e60b7571063570ee21e868f0f446b5b0f","CurrentProject":"","EntryPoints":[],"Properties":{}} -------------------------------------------------------------------------------- /FloatingGlucose/Resources/browse-file.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/FloatingGlucose/master/FloatingGlucose/Resources/browse-file.ico -------------------------------------------------------------------------------- /FloatingGlucose/Resources/noun_335372_cc_v2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/FloatingGlucose/master/FloatingGlucose/Resources/noun_335372_cc_v2.ico -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ShareClientDotNet"] 2 | path = ShareClientDotNet 3 | url = https://github.com/shanselman/ShareClientDotNet.git 4 | [submodule "winforms-datavisualization-net5"] 5 | path = winforms-datavisualization-net5 6 | url = https://github.com/AngeloCresta/winforms-datavisualization-net5.git 7 | -------------------------------------------------------------------------------- /FloatingGlucose/Classes/Utils/GlucoseAlarmStatusEnum.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Text; 3 | 4 | namespace FloatingGlucose.Classes.Utils 5 | { 6 | public enum GlucoseAlarmStatusEnum 7 | { 8 | UrgentLow, 9 | Low, 10 | Normal, 11 | High, 12 | UrgentHigh, 13 | Unknown 14 | } 15 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/Exceptions/NoSuchPluginException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FloatingGlucose.Classes.DataSources 4 | { 5 | internal class NoSuchPluginException : Exception 6 | { 7 | public NoSuchPluginException() 8 | { 9 | } 10 | 11 | public NoSuchPluginException(string message) : base(message) 12 | { 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/Exceptions/NoPluginChosenException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FloatingGlucose.Classes.DataSources 4 | { 5 | internal class NoPluginChosenException : Exception 6 | { 7 | public NoPluginChosenException() 8 | { 9 | } 10 | 11 | public NoPluginChosenException(string message) : base(message) 12 | { 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /FloatingGlucose/Properties/FormSettings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0, 0, 0, 0 7 | 8 | 9 | -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/Exceptions/MissingDataException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace FloatingGlucose.Classes.DataSources 6 | { 7 | internal class MissingDataException : Exception 8 | { 9 | public MissingDataException(string message) : base(message) 10 | { 11 | } 12 | 13 | public MissingDataException() 14 | { 15 | } 16 | 17 | public MissingDataException(string message, Exception inner) : base(message, inner) 18 | { 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/Exceptions/InvalidJsonDataException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace FloatingGlucose.Classes.DataSources 6 | { 7 | internal class InvalidJsonDataException : Exception 8 | { 9 | public InvalidJsonDataException(string message) : base(message) 10 | { 11 | } 12 | 13 | public InvalidJsonDataException() 14 | { 15 | } 16 | 17 | public InvalidJsonDataException(string message, Exception inner) : base(message, inner) 18 | { 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/Plugins/DexcomShare/DexcomShareNonUS.cs: -------------------------------------------------------------------------------- 1 | using ShareClientDotNet; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace FloatingGlucose.Classes.DataSources.Plugins 6 | { 7 | internal class DexcomShareNonUSEndpoint : DexcomShareEndpoint 8 | { 9 | //protected new ShareClient shareClient = new ShareClient(ShareServer.ServerNonUS); 10 | public override string DataSourceShortName => "Dexcom Share (Non-US)"; 11 | 12 | public DexcomShareNonUSEndpoint() 13 | { 14 | this.shareClient.SetShareServer(ShareServer.ServerNonUS); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /FloatingGlucose/README_attributions.txt: -------------------------------------------------------------------------------- 1 | Attributions: 2 | 3 | Application icon: 4 | This application's icon has kindly been provided by Marie Van den Broeck and modified by me. 5 | She maintains the copyright of the icon. 6 | https://creativecommons.org/licenses/by/3.0/us/ 7 | The icon is under the creative commons license https://creativecommons.org/licenses/by/3.0/us/ 8 | Please visit her site here: https://thenounproject.com/marie49/collection/health/?q=diabetes&i=335372s 9 | 10 | Alarm mp3s: 11 | This application uses alarms as defined by the nightscout foundation's remote-cgm-monitor project, 12 | please see https://github.com/nightscout/cgm-remote-monitor/tree/master/static/audio -------------------------------------------------------------------------------- /FloatingGlucose/Classes/Extensions/ColorExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace FloatingGlucose.Classes.Extensions 7 | { 8 | internal static class ColorExtensions 9 | { 10 | public static string ToHexString(this Color color) 11 | { 12 | return color.ToArgb().ToString("X"); 13 | } 14 | 15 | public static Color FromHexStringToColor(this String hexColor) 16 | { 17 | var argb = Convert.ToInt32(hexColor, 16); 18 | var color = Color.FromArgb(argb);//FF000000 - black with no transparency 19 | return color; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/Extensions/FormExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace FloatingGlucose.Classes.Extensions 6 | { 7 | internal static class FormExtensions 8 | { 9 | public static void ShowDialogIfNonVisible(this FormGlucoseSettings form) 10 | { 11 | if (form.Visible) 12 | { 13 | return; 14 | } 15 | Debug.WriteLine("Stopping refresh timer as we are showing settings"); 16 | AppShared.refreshGlucoseTimer?.Stop(); 17 | 18 | AppShared.IsShowingSettings = true; 19 | form.ShowDialog(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/Utils/BGReading.cs: -------------------------------------------------------------------------------- 1 | using FloatingGlucose.Classes.Extensions; 2 | using System; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace FloatingGlucose.Classes.Utils 7 | { 8 | internal class BgReading 9 | { 10 | public double _glucose; 11 | public DateTime DateReading; 12 | 13 | public double Timestamp => this.DateReading.ToUnixTimeStampMilliseconds(); 14 | public double GlucoseMgdl => this._glucose; 15 | public double GlucoseMmol => this.GlucoseMgdl / 18.01559; 16 | 17 | public string GetRelativeGlucoseDirection(BgReading otherReading) 18 | { 19 | return GlucoseMath.GetGlucoseDirection(this, otherReading); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/Exceptions/ConfigValidationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace FloatingGlucose.Classes.DataSources 5 | { 6 | [Serializable] 7 | internal class ConfigValidationException : Exception 8 | { 9 | public ConfigValidationException() 10 | { 11 | } 12 | 13 | public ConfigValidationException(string message) : base(message) 14 | { 15 | } 16 | 17 | public ConfigValidationException(string message, Exception innerException) : base(message, innerException) 18 | { 19 | } 20 | 21 | protected ConfigValidationException(SerializationInfo info, StreamingContext context) : base(info, context) 22 | { 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /FloatingGlucose/Program.cs: -------------------------------------------------------------------------------- 1 | using FloatingGlucose.Classes; 2 | using System; 3 | using System.Linq; 4 | using System.Windows.Forms; 5 | 6 | namespace FloatingGlucose 7 | { 8 | internal static class Program 9 | { 10 | /// 11 | /// The main entry point for the application. 12 | /// 13 | [STAThread] 14 | private static void Main() 15 | { 16 | Application.EnableVisualStyles(); 17 | Application.SetCompatibleTextRenderingDefault(false); 18 | //Application.Run(new FormGlucoseSettings()); 19 | //Application.Run(new FormAudioTester()); 20 | //Application.Run(new AutoPositionedForm()); 21 | //return; 22 | try 23 | { 24 | Application.Run(new FloatingGlucose()); 25 | var manager = SoundAlarm.Instance; 26 | manager.StopAlarm(); 27 | } 28 | catch (ObjectDisposedException) 29 | { 30 | // this happens when application.exit() is called when the form has a modal dialog open 31 | // ignore for now 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/DataSourceInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace FloatingGlucose.Classes.DataSources 6 | { 7 | public class DataSourceInfo 8 | { 9 | public Type Type { get; set; } 10 | public string DataSourceShortName { get; set; } 11 | public string FullName { get; set; } 12 | public IDataSourcePlugin Instance { get; set; } 13 | 14 | public DataSourceInfo(Type plugin) 15 | { 16 | this.Type = plugin; 17 | this.Instance = (IDataSourcePlugin)Activator.CreateInstance(plugin); 18 | this.DataSourceShortName = this.Instance.DataSourceShortName; 19 | this.FullName = plugin.FullName; 20 | } 21 | 22 | public DataSourceInfo(IDataSourcePlugin plugin) 23 | { 24 | this.Type = plugin.GetType(); 25 | this.Instance = plugin; 26 | this.DataSourceShortName = this.Instance.DataSourceShortName; 27 | this.FullName = this.Type.FullName; 28 | } 29 | 30 | public override string ToString() 31 | { 32 | return this.DataSourceShortName; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/Plugins/NightscoutPebbleEndpoint/GeneratedNsData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace FloatingGlucose.Classes.DataSources 6 | { 7 | //json2csharp.com 8 | public class GeneratedNsData 9 | { 10 | public List status { get; set; } 11 | public List bgs { get; set; } 12 | public List cals { get; set; } 13 | } 14 | 15 | public class Status 16 | { 17 | public long now { get; set; } 18 | } 19 | 20 | public class Bg 21 | { 22 | public string sgv { get; set; } 23 | public int trend { get; set; } 24 | public string direction { get; set; } 25 | public long datetime { get; set; } 26 | public double filtered { get; set; } 27 | public double unfiltered { get; set; } 28 | public double noise { get; set; } 29 | public string bgdelta { get; set; } 30 | public string battery { get; set; } 31 | } 32 | 33 | public class Cal 34 | { 35 | public double slope { get; set; } 36 | public double intercept { get; set; } 37 | public double scale { get; set; } 38 | } 39 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/Utils/Validators.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace FloatingGlucose.Classes.Utils 7 | { 8 | internal class Validators 9 | { 10 | public static bool IsUrl(string url) 11 | { 12 | var isWellFormed = url != null && Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute); 13 | if (!isWellFormed) 14 | { 15 | return false; 16 | } 17 | System.Uri uriResult; 18 | return Uri.TryCreate(url, UriKind.Absolute, out uriResult) && 19 | ( 20 | (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps) 21 | ); 22 | } 23 | 24 | public static bool IsReadableFile(string path) 25 | { 26 | var isFile = File.Exists(path); 27 | if (!isFile) 28 | { 29 | return false; 30 | } 31 | 32 | try 33 | { 34 | return File.ReadAllBytes(path) != null; 35 | } 36 | catch (Exception) 37 | { 38 | return false; 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /FloatingGlucose/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("FloatingGlucose")] 8 | [assembly: AssemblyDescription("A windows app to display your glucose data from windows")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Bjorns_opensource_utils")] 11 | [assembly: AssemblyProduct("FloatingGlucose")] 12 | [assembly: AssemblyCopyright("Copyright code.bjorninge.no© 2016")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("9006c00a-1ee6-407c-b236-b810ddea27bd")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.7")] 35 | [assembly: AssemblyFileVersion("1.0.0.8")] -------------------------------------------------------------------------------- /.github/workflows/FloatingGlucose.yaml: -------------------------------------------------------------------------------- 1 | name: "CI Build" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - '**/*.md' 9 | - '**/*.gitignore' 10 | - '**/*.gitattributes' 11 | workflow_dispatch: 12 | branches: 13 | - master 14 | paths-ignore: 15 | - '**/*.md' 16 | - '**/*.gitignore' 17 | - '**/*.gitattributes' 18 | 19 | jobs: 20 | build: 21 | name: Build 22 | runs-on: windows-latest 23 | env: 24 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 25 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 26 | DOTNET_NOLOGO: true 27 | DOTNET_GENERATE_ASPNET_CERTIFICATE: false 28 | DOTNET_ADD_GLOBAL_TOOLS_TO_PATH: false 29 | DOTNET_MULTILEVEL_LOOKUP: 0 30 | 31 | steps: 32 | - uses: actions/checkout@v2 33 | with: 34 | submodules: recursive 35 | 36 | - name: Setup .NET Core SDK 37 | uses: actions/setup-dotnet@v1.8.0 38 | with: 39 | dotnet-version: 5.0.x 40 | 41 | - name: Restore 42 | run: dotnet restore 43 | 44 | - name: Build 45 | run: dotnet build --configuration Release --no-restore 46 | 47 | - name: Test 48 | run: dotnet test 49 | 50 | - name: Publish 51 | run: dotnet publish --configuration Release --runtime=win10-x64 --output ./publish -p:PublishReadyToRun=true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true --self-contained=true -p:DebugType=None .\FloatingGlucose\FloatingGlucose.csproj 52 | 53 | - name: Upload a Build Artifact 54 | uses: actions/upload-artifact@v2.2.3 55 | with: 56 | name: self-contained-build 57 | path: ./publish 58 | -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/IDataSourcePlugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FloatingGlucose.Classes.DataSources 9 | { 10 | public interface IDataSourcePlugin 11 | { 12 | string Acknowledgment { get; } 13 | string AcknowledgmentUrl { get; } 14 | string Author { get; } 15 | bool RequiresUserNameAndPassword { get; } 16 | bool RequiresDataSource { get; } 17 | int SortOrder { get; } 18 | bool PluginHandlesFormatting { get; } 19 | 20 | List HandleFormatting(); 21 | 22 | DateTime Date { get; } 23 | 24 | double Glucose { get; } 25 | double PreviousGlucose { get; } 26 | double Delta { get; } 27 | double RawDelta { get; } 28 | 29 | //this is a text describing in which direction the current glucose trend is heading 30 | string Direction { get; } 31 | 32 | double RawGlucose { get; } 33 | double PreviousRawGlucose { get; } 34 | 35 | double RoundedDelta(); 36 | 37 | double RoundedRawDelta(); 38 | 39 | DateTime LocalDate { get; } 40 | 41 | string DataSourceShortName { get; } 42 | 43 | Task GetDataSourceDataAsync(NameValueCollection datapath); 44 | 45 | bool VerifyConfig(Properties.Settings settings); 46 | 47 | //GUI related 48 | void OnPluginSelected(FormGlucoseSettings settingsForm); 49 | 50 | bool RequiresBrowseButton { get; } 51 | string BrowseDialogFileFilter { get; } 52 | bool PluginDisabled { get; } 53 | } 54 | } -------------------------------------------------------------------------------- /FloatingGlucose/Properties/FormSettings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace FloatingGlucose.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] 16 | internal sealed partial class FormSettings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static FormSettings defaultInstance = ((FormSettings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new FormSettings()))); 19 | 20 | public static FormSettings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] 29 | public global::System.Drawing.Rectangle WindowPosition { 30 | get { 31 | return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); 32 | } 33 | set { 34 | this["WindowPosition"] = value; 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /FloatingGlucose/Settings.cs: -------------------------------------------------------------------------------- 1 | namespace FloatingGlucose.Properties 2 | { 3 | // This class allows you to handle specific events on the settings class: 4 | // The SettingChanging event is raised before a setting's value is changed. 5 | // The PropertyChanged event is raised after a setting's value is changed. 6 | // The SettingsLoaded event is raised after the setting values are loaded. 7 | // The SettingsSaving event is raised before the setting values are saved. 8 | public sealed partial class Settings 9 | { 10 | public Settings() 11 | { 12 | // // To add event handlers for saving and changing settings, uncomment the lines below: 13 | // 14 | // this.SettingChanging += this.SettingChangingEventHandler; 15 | // 16 | // this.SettingsSaving += this.SettingsSavingEventHandler; 17 | // 18 | this.SettingsLoaded += this.SettingsLoadedEventHandler; 19 | } 20 | 21 | private void SettingsLoadedEventHandler(object sender, System.Configuration.SettingsLoadedEventArgs e) 22 | { 23 | // Add code to handle the SettingChangingEvent event here. 24 | //Debug.WriteLine("Got external settings loaded event"); 25 | //AppShared.NotifyFormSettingsHaveChanged(); 26 | } 27 | 28 | private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) 29 | { 30 | // Add code to handle the SettingChangingEvent event here. 31 | } 32 | 33 | private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) 34 | { 35 | // Add code to handle the SettingsSaving event here. 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/AppShared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Windows.Forms; 6 | 7 | namespace FloatingGlucose.Classes 8 | { 9 | internal class AppShared 10 | { 11 | #if DEBUG 12 | public static bool isDebuggingBuild => true; 13 | #else 14 | public static bool isDebuggingBuild => false; 15 | #endif 16 | 17 | public static bool IsWorkStationLocked = false; 18 | public static bool IsShowingSettings = false; 19 | public static readonly string AppName = typeof(Program).Assembly.GetName().Name; 20 | 21 | public static string AppVersion 22 | { 23 | get 24 | { 25 | try 26 | { 27 | using (var reader = File.OpenText(Path.Combine(AppShared.ExecutableDir, "version.txt"))) 28 | { 29 | return reader.ReadToEnd(); 30 | } 31 | } 32 | catch (IOException) 33 | { 34 | return "unknown"; 35 | } 36 | } 37 | } 38 | 39 | public static System.Windows.Forms.Timer refreshGlucoseTimer; 40 | public static string ExecutableDir => Path.GetDirectoryName(Application.ExecutablePath); 41 | 42 | public static bool SettingsFormShouldFocusAdvancedSettings = false; 43 | public static bool SettingsUpdatedSuccessfully = false; 44 | 45 | private static Func callback; 46 | 47 | public static void RegisterSettingsChangedCallback(Func lambda) 48 | { 49 | AppShared.callback = lambda; 50 | } 51 | 52 | public static void NotifyFormSettingsHaveChanged() => callback?.Invoke(); 53 | } 54 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/Extensions/DatetimeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace FloatingGlucose.Classes.Extensions 6 | { 7 | internal static class DatetimeExtensions 8 | { 9 | public static double ToUnixTimeStampMilliseconds(this DateTime dt) 10 | { 11 | return dt.ToUniversalTime().Subtract( 12 | new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) 13 | ).TotalMilliseconds; 14 | } 15 | 16 | public static string ToTimeAgo(this DateTime dt) 17 | { 18 | TimeSpan span = DateTime.Now - dt; 19 | if (span.Days > 365) 20 | { 21 | int years = (span.Days / 365); 22 | if (span.Days % 365 != 0) 23 | years += 1; 24 | return String.Format("{0} {1} ago", 25 | years, years == 1 ? "year" : "years"); 26 | } 27 | if (span.Days > 30) 28 | { 29 | int months = (span.Days / 30); 30 | if (span.Days % 31 != 0) 31 | months += 1; 32 | return String.Format("{0} {1} ago", 33 | months, months == 1 ? "month" : "months"); 34 | } 35 | if (span.Days > 0) 36 | return String.Format("{0} {1} ago", 37 | span.Days, span.Days == 1 ? "day" : "days"); 38 | if (span.Hours > 0) 39 | return String.Format("{0} {1} ago", 40 | span.Hours, span.Hours == 1 ? "hour" : "hours"); 41 | if (span.Minutes > 0) 42 | return String.Format("{0} {1} ago", 43 | span.Minutes, span.Minutes == 1 ? "minute" : "minutes"); 44 | 45 | return "now"; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/Extensions/IDataSourcePluginExtensions.cs: -------------------------------------------------------------------------------- 1 | using FloatingGlucose.Classes.DataSources; 2 | using System.Linq; 3 | using System.Text; 4 | using System; 5 | using System.Diagnostics; 6 | using static FloatingGlucose.Properties.Settings; 7 | 8 | namespace FloatingGlucose.Classes.Extensions 9 | { 10 | internal static class IDataSourcePluginExtensions 11 | { 12 | public static bool UserWantsMmolUnits(this IDataSourcePlugin plugin) => Default.GlucoseUnits == "mmol"; 13 | 14 | public static string FormattedDelta(this IDataSourcePlugin plugin) 15 | { 16 | return $"{(plugin.RoundedDelta() >= 0.0 ? "+" : "")}{plugin.RoundedDelta():N1}"; 17 | } 18 | 19 | public static string FormattedRawDelta(this IDataSourcePlugin plugin) 20 | { 21 | return $"{(plugin.RoundedRawDelta() >= 0.0 ? "+" : "")}{plugin.RoundedRawDelta():N1}"; 22 | } 23 | 24 | public static void WriteDebug(this IDataSourcePlugin plugin, string line) 25 | { 26 | var now = DateTime.Now.ToUniversalTime(); 27 | Debug.WriteLine(now + ":" + line); 28 | } 29 | 30 | public static string DirectionArrow(this IDataSourcePlugin plugin) 31 | { 32 | switch (plugin.Direction) 33 | { 34 | case "DoubleUp": 35 | return "⇈"; 36 | 37 | case "SingleUp": 38 | return "↑"; 39 | 40 | case "FortyFiveUp": 41 | return "↗"; 42 | 43 | case "Flat": 44 | return "→"; 45 | 46 | case "FortyFiveDown": 47 | return "↘"; 48 | 49 | case "SingleDown": 50 | return "↓"; 51 | 52 | case "DoubleDown": 53 | return "⇊"; 54 | 55 | case "NOT COMPUTABLE": 56 | return "-"; 57 | 58 | case "OUT OF RANGE": 59 | return "⇕"; 60 | 61 | case "": 62 | return ""; 63 | 64 | default: 65 | case "None": 66 | return "⇼"; 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FloatingGlucose 2 | FloatingGlucose is a windows program aiming to display your current bloogsugars live on your desktop. 3 | 4 | A transparent popup window will be displayed in the bottom right corner of your desktop, with your current blood glucose values fetched from your nightscout site. 5 | 6 | ## Screenshot 7 | ![Floatingglucose in action](floatingglucose.png) 8 | 9 | ## Installation 10 | Download The Setup.exe of the latest release, [which you can find here](https://github.com/dabear/FloatingGlucose/releases/) and run it. You should normally just install it to the default directory which is C:\Program Files (x86)\FloatingGlucose. 11 | 12 | 13 | ## Plugins ## 14 | 15 | 16 | 17 | - Dexcom Share Dexcom share plugins by Bjørn inge Vikhammermo. Connects to Dexcom share (both US and Non-US) servers to fetch glucose data 18 | 19 | - Nightscout Nightscout plugin by Bjørn inge Vikhammermo. The original - fetches glucose from a specified nightscout site. 20 | 21 | 22 | 23 | ## Running the program 24 | Should be as simple as double clicking the app. A settings dialog will appear asking you to specify an endpoint (plugin) you'd like to connect to 25 | 26 | ## Hidden / Nice to Know features 27 | * You can drag the window around and place it where you want to. 28 | * To show the settings again, please right click the notification area icon or the app. A menu will pop up allowing you to access the application settings 29 | * The same pop up menu can be used to temporarily disable sound alarms. 30 | 31 | ## Debugging 32 | Having troubles with crashes or with the widget not updating? Please make sure you have enabled exception logging to stderr (in the configfile) then run the program from command line: 33 | 34 | ```bash 35 | cd "C:\Program Files (x86)\FloatingGlucose" 36 | FloatingGlucose.exe 2> glucose_log.txt 37 | ``` 38 | 39 | Then open glucose_log.txt 40 | 41 | ## Settings files 42 | Please note that you can edit the settings from inside the application. If you still need to access the conig files directly, please go to either of these locations 43 | 44 | * Installation directory, and open FloatingGlucose.exe.config 45 | * %localappdata%\Bjorns_opensource_utils and locate user.config 46 | 47 | The user.config file will override any values inside the FloatingGlucose.exe.config file 48 | 49 | 50 | -------------------------------------------------------------------------------- /FloatingGlucose/Classes/Utils/DataProtectorConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace FloatingGlucose.Classes.Utils 7 | { 8 | public class DataProtectorConverter : TypeConverter 9 | { 10 | public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) 11 | { 12 | return sourceType == typeof(string); 13 | } 14 | 15 | public override object ConvertFrom( 16 | ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) 17 | { 18 | if (value is string) 19 | { 20 | //converts from encrypted text settings into plain text 21 | var val = (string)value; 22 | DataProtector protector; 23 | if (val == null || (val != null && val.Length == 0)) 24 | { 25 | protector = new DataProtector(""); 26 | } 27 | else 28 | { 29 | val = DataProtector.ConvertFromSecureTextString(DataProtector.Base64Decode(val)); 30 | 31 | protector = new DataProtector(val); 32 | } 33 | 34 | return protector; 35 | } 36 | return base.ConvertFrom(context, culture, value); 37 | } 38 | 39 | public override object ConvertTo( 40 | ITypeDescriptorContext context, System.Globalization.CultureInfo culture, 41 | object value, Type destinationType) 42 | { 43 | if (destinationType == typeof(string)) 44 | { 45 | // Room room = value as Room; 46 | // return string.Format("{0},{1}", room.RoomNumber, room.Location); 47 | 48 | var val = (DataProtector)value; 49 | //encrypts the plaintext and base64 encodes it for storage 50 | if (val == null || val.Text == null || val.Text.Length == 0) 51 | { 52 | return ""; 53 | } 54 | else 55 | { 56 | return 57 | DataProtector.Base64Encode(DataProtector.ConvertToSecureTextString(val.Text)); 58 | } 59 | } 60 | return base.ConvertTo(context, culture, value, destinationType); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/Audio/LoopStream.cs: -------------------------------------------------------------------------------- 1 | using NAudio.Wave; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace FloatingGlucose.Classes 6 | { 7 | /// 8 | /// Stream for looping playback 9 | /// 10 | public class LoopStream : WaveStream 11 | { 12 | private WaveStream sourceStream; 13 | 14 | /// 15 | /// Creates a new Loop stream 16 | /// 17 | /// The stream to read from. Note: the Read method of this stream should return 0 when it reaches the end 18 | /// or else we will not loop to the start again. 19 | public LoopStream(WaveStream sourceStream) 20 | { 21 | this.sourceStream = sourceStream; 22 | this.EnableLooping = true; 23 | } 24 | 25 | /// 26 | /// Use this to turn looping on or off 27 | /// 28 | public bool EnableLooping { get; set; } 29 | 30 | /// 31 | /// Return source stream's wave format 32 | /// 33 | public override WaveFormat WaveFormat 34 | { 35 | get { return sourceStream.WaveFormat; } 36 | } 37 | 38 | /// 39 | /// LoopStream simply returns 40 | /// 41 | public override long Length 42 | { 43 | get { return sourceStream.Length; } 44 | } 45 | 46 | /// 47 | /// LoopStream simply passes on positioning to source stream 48 | /// 49 | public override long Position 50 | { 51 | get { return sourceStream.Position; } 52 | set { sourceStream.Position = value; } 53 | } 54 | 55 | public override int Read(byte[] buffer, int offset, int count) 56 | { 57 | int totalBytesRead = 0; 58 | 59 | while (totalBytesRead < count) 60 | { 61 | int bytesRead = sourceStream.Read(buffer, offset + totalBytesRead, count - totalBytesRead); 62 | if (bytesRead == 0) 63 | { 64 | if (sourceStream.Position == 0 || !EnableLooping) 65 | { 66 | // something wrong with the source stream 67 | break; 68 | } 69 | // loop 70 | sourceStream.Position = 0; 71 | } 72 | totalBytesRead += bytesRead; 73 | } 74 | return totalBytesRead; 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /FloatingGlucose/FormWebbrowser.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace FloatingGlucose 2 | { 3 | partial class FormWebbrowser 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.webBrowser1 = new System.Windows.Forms.WebBrowser(); 32 | this.SuspendLayout(); 33 | // 34 | // webBrowser1 35 | // 36 | this.webBrowser1.Dock = System.Windows.Forms.DockStyle.Fill; 37 | this.webBrowser1.Location = new System.Drawing.Point(0, 0); 38 | this.webBrowser1.MinimumSize = new System.Drawing.Size(20, 20); 39 | this.webBrowser1.Name = "webBrowser1"; 40 | this.webBrowser1.Size = new System.Drawing.Size(1042, 586); 41 | this.webBrowser1.TabIndex = 0; 42 | this.webBrowser1.DocumentCompleted += new System.Windows.Forms.WebBrowserDocumentCompletedEventHandler(this.webBrowser1_DocumentCompleted); 43 | this.webBrowser1.Navigated += new System.Windows.Forms.WebBrowserNavigatedEventHandler(this.webBrowser1_Navigated); 44 | // 45 | // FormWebbrowser 46 | // 47 | this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); 48 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 49 | this.ClientSize = new System.Drawing.Size(1042, 586); 50 | this.Controls.Add(this.webBrowser1); 51 | this.Name = "FormWebbrowser"; 52 | this.Text = "Webbrowser"; 53 | this.Load += new System.EventHandler(this.FormWebbrowser_Load); 54 | this.ResumeLayout(false); 55 | 56 | } 57 | 58 | #endregion 59 | 60 | private System.Windows.Forms.WebBrowser webBrowser1; 61 | } 62 | } -------------------------------------------------------------------------------- /FloatingGlucose/FormWebbrowser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Security.Permissions; 5 | using System.Text; 6 | using System.Windows.Forms; 7 | 8 | namespace FloatingGlucose 9 | { 10 | // [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] 11 | [System.Runtime.InteropServices.ComVisibleAttribute(true)] 12 | public partial class FormWebbrowser : Form 13 | { 14 | private string injectScript = ""; 15 | 16 | //private string _webpageReturnValue = "/place/Norway/Sør-Trøndelag/Trondheim/Trondheim/"; 17 | private string _webpageReturnValue = ""; 18 | 19 | public string WebPageReturnValue => this._webpageReturnValue; 20 | 21 | private bool disableScrollBars = true; 22 | 23 | public void SetReturnValueAndClose(string value) 24 | { 25 | this._webpageReturnValue = value; 26 | this.Close(); 27 | } 28 | 29 | public FormWebbrowser(string injectScript, bool disableScrollBars = true) 30 | { 31 | InitializeComponent(); 32 | this.injectScript = injectScript; 33 | 34 | var wb = this.webBrowser1; 35 | 36 | wb.ObjectForScripting = this; 37 | 38 | //if (!AppShared.isDebuggingBuild) 39 | //{ 40 | wb.AllowWebBrowserDrop = false; 41 | wb.IsWebBrowserContextMenuEnabled = false; 42 | wb.WebBrowserShortcutsEnabled = false; 43 | wb.ScriptErrorsSuppressed = true; 44 | //} 45 | this.disableScrollBars = disableScrollBars; 46 | } 47 | 48 | public void SetBrowserUrl(string url) 49 | { 50 | this.webBrowser1.Url = new Uri(url); 51 | } 52 | 53 | public string InvokeScript(string scriptName) 54 | { 55 | return this.webBrowser1.Document.InvokeScript(scriptName).ToString(); 56 | } 57 | 58 | private void FormWebbrowser_Load(object sender, EventArgs e) 59 | { 60 | } 61 | 62 | private void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e) 63 | { 64 | } 65 | 66 | private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) 67 | { 68 | var doc = this.webBrowser1.Document; 69 | 70 | if (this.injectScript != null) 71 | { 72 | var head = doc.GetElementsByTagName("head")[0]; 73 | var s = doc.CreateElement("script"); 74 | s.SetAttribute("text", this.injectScript); 75 | head.AppendChild(s); 76 | Debug.WriteLine("Inserted script into web page!"); 77 | } 78 | 79 | if (this.disableScrollBars) 80 | { 81 | doc.Body.Style = "overflow:hidden"; 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/PluginLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using static FloatingGlucose.Properties.Settings; 6 | 7 | namespace FloatingGlucose.Classes.DataSources 8 | { 9 | public class PluginLoader 10 | { 11 | private static readonly PluginLoader instance = new PluginLoader(); 12 | private IDataSourcePlugin plugin; 13 | private List loadedPlugins; 14 | 15 | public List GetAllPlugins(bool forceReload = false) 16 | { 17 | if (forceReload || loadedPlugins == null) 18 | { 19 | var list = new List(); 20 | 21 | // Fill the list of plugins, these are fairly static and won't change during runtime 22 | var type = typeof(IDataSourcePlugin); 23 | var allPlugins = AppDomain.CurrentDomain.GetAssemblies() 24 | .SelectMany((x) => x.GetTypes().Where((y) => type.IsAssignableFrom(y) && !y.IsInterface)); 25 | 26 | foreach (Type plugin in allPlugins) 27 | { 28 | var dsinfo = new DataSourceInfo(plugin); 29 | if (!dsinfo.Instance.PluginDisabled) 30 | { 31 | list.Add(dsinfo); 32 | } 33 | } 34 | list.Sort((x, y) => x.FullName.CompareTo(y.FullName)); 35 | this.loadedPlugins = list; 36 | } 37 | 38 | return this.loadedPlugins; 39 | } 40 | 41 | public IDataSourcePlugin GetActivePlugin() 42 | { 43 | if (this.plugin != null) 44 | { 45 | return this.plugin; 46 | } 47 | //use default plugin from config file 48 | // this can change between run 49 | var fullname = Default.DataSourceFullName; 50 | if (fullname.Length == 0) 51 | { 52 | throw new NoPluginChosenException(""); 53 | } 54 | return this.SetActivePlugin(fullname); 55 | } 56 | 57 | public IDataSourcePlugin SetActivePlugin(string dataSourceFullName) 58 | { 59 | try 60 | { 61 | this.plugin = this.GetAllPlugins().Where((x) => x.FullName == dataSourceFullName).First().Instance; 62 | } 63 | catch (InvalidOperationException) 64 | { 65 | throw new NoSuchPluginException("Invalid plugin: " + dataSourceFullName); 66 | } 67 | return this.plugin; 68 | } 69 | 70 | // Explicit static constructor to tell C# compiler 71 | // not to mark type as beforefieldinit 72 | static PluginLoader() 73 | { 74 | } 75 | 76 | private PluginLoader() 77 | { 78 | } 79 | 80 | public static PluginLoader Instance => instance; 81 | } 82 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/Plugins/NightscoutPebbleFileEndpoint.cs: -------------------------------------------------------------------------------- 1 | using FloatingGlucose.Classes.Utils; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Specialized; 5 | using System.Globalization; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net.Http; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace FloatingGlucose.Classes.DataSources.Plugins 13 | { 14 | internal class NightscoutPebbleFileEndpoint : NightscoutPebbleEndpoint, IDataSourcePlugin 15 | { 16 | public override bool RequiresUserNameAndPassword => false; 17 | public override bool RequiresDataSource => true; 18 | public override bool PluginDisabled => false; 19 | public override bool RequiresBrowseButton => true; 20 | public override string BrowseDialogFileFilter => "Nightscout Dumps|*.nsdata.dump;*.nsdata|Text files|*.txt"; 21 | public override string DataSourceShortName => "Nightscout File Dump"; 22 | public override int SortOrder => 11; 23 | 24 | public override void OnPluginSelected(FormGlucoseSettings form) 25 | { 26 | form.lblDataSourceLocation.Text = "Your File Dump location"; 27 | var source = form.txtDataSourceLocation.Text.ToLower(); 28 | //for this plugin, we don't handle http:// and https:// 29 | //if the source does not resemble an url, it should clearly be removed. 30 | if (source.StartsWith("http://") || source.StartsWith("https://")) 31 | { 32 | form.txtDataSourceLocation.Text = ""; 33 | } 34 | } 35 | 36 | public override bool VerifyConfig(Properties.Settings settings) 37 | { 38 | if (!Validators.IsReadableFile(settings.DataPathLocation)) 39 | { 40 | throw new ConfigValidationException("You have entered an invalid file path for the data dump!"); 41 | } 42 | 43 | return true; 44 | } 45 | 46 | public override async Task GetDataSourceDataAsync(NameValueCollection locations) 47 | { 48 | var datapath = locations["raw"]; 49 | var client = new HttpClient(); 50 | string fileContents; 51 | 52 | // datapath is expected to be a valid file 53 | // Exceptions will be handled by the main program 54 | using (var reader = File.OpenText(datapath)) 55 | { 56 | fileContents = await reader.ReadToEndAsync(); 57 | } 58 | 59 | Bg bgs = null; 60 | var parsed = 61 | this.NsData = JsonConvert.DeserializeObject(fileContents); 62 | 63 | bgs = parsed.bgs.First(); 64 | this.Direction = bgs.direction; 65 | this.Glucose = Double.Parse(bgs.sgv, NumberStyles.Any, CultureInfo.InvariantCulture); 66 | this.Date = DateTimeOffset.FromUnixTimeMilliseconds(bgs.datetime).DateTime; 67 | this.Delta = Double.Parse(bgs.bgdelta, NumberStyles.Any, CultureInfo.InvariantCulture); 68 | 69 | return this; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /FloatingGlucose/Classes/Utils/DataProtector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.ComponentModel; 4 | using System.Configuration; 5 | using System.Linq; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | 9 | namespace FloatingGlucose.Classes.Utils 10 | { 11 | [TypeConverter(typeof(DataProtectorConverter))] 12 | [SettingsSerializeAs(SettingsSerializeAs.String)] 13 | public class DataProtector 14 | { 15 | /// 16 | /// This class implements some of the best practices for generating a secure string that can only be read 17 | /// by the current user. However, as it is untested security wise, this can only be considered as secure as 18 | /// a two way hash, where the encrpytion key is stored on a per user setting. 19 | /// 20 | /// 21 | 22 | private static BitArray aditionalEntropy = new BitArray(new byte[7] { 64, 3, 16, 32, 28, 99, 33 }); 23 | private static BitArray nr2 = new BitArray(new byte[7] { 7, 9, 3, 2, 8, 9, 3 }); 24 | 25 | public string Text { get; set; } 26 | 27 | public DataProtector() 28 | { 29 | } 30 | 31 | public DataProtector(string text) 32 | { 33 | this.Text = text; 34 | } 35 | 36 | public static string Base64Encode(string plainText) 37 | { 38 | var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); 39 | return System.Convert.ToBase64String(plainTextBytes); 40 | } 41 | 42 | public static string Base64Decode(string base64EncodedData) 43 | { 44 | var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData); 45 | return System.Text.Encoding.UTF8.GetString(base64EncodedBytes); 46 | } 47 | 48 | public static string ConvertToSecureTextString(string password) 49 | { 50 | var data = System.Text.ASCIIEncoding.Default.GetBytes(password); 51 | var converted = ConvertToBytes((aditionalEntropy.Clone() as BitArray).Xor(nr2)); 52 | var protecteddata = ProtectedData.Protect(data, converted, DataProtectionScope.CurrentUser); 53 | return System.Text.Encoding.Default.GetString(protecteddata); 54 | } 55 | 56 | public static string ConvertFromSecureTextString(string securestring) 57 | { 58 | var data = System.Text.ASCIIEncoding.Default.GetBytes(securestring); 59 | var converted = ConvertToBytes((aditionalEntropy.Clone() as BitArray).Xor(nr2)); 60 | var unprotecteddata = ProtectedData.Unprotect(data, converted, DataProtectionScope.CurrentUser); 61 | 62 | return System.Text.Encoding.Default.GetString(unprotecteddata); 63 | } 64 | 65 | public static byte[] ConvertToBytes(BitArray bits) 66 | { 67 | byte[] ret = new byte[(bits.Length - 1) / 8 + 1]; 68 | bits.CopyTo(ret, 0); 69 | return ret; 70 | } 71 | 72 | public static string ConvertByteArrayToIntString(byte[] array) 73 | { 74 | return ((Int32)(BitConverter.ToInt16(array, 0))).ToString(); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /FloatingGlucose/buildscripts/create-zip-file.ps1: -------------------------------------------------------------------------------- 1 | # Out-Zip.msh 2 | # creates out-Zip function 3 | # /\/\o\/\/ 2006 4 | # http://mow001.blogspot.com 5 | 6 | function out-zip($zipfilename, $files) { 7 | #Load some assemblys. (No line break!) 8 | [System.Reflection.Assembly]::Load("WindowsBase, 9 | Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35") | Out-Null 10 | 11 | #Create a zip file named "MyZipFile.zip". (No line break!) 12 | $ZipPackage=[System.IO.Packaging.ZipPackage]::Open("$zipfilename", 13 | [System.IO.FileMode]"OpenOrCreate", [System.IO.FileAccess]"ReadWrite") 14 | 15 | #For each file you want to add, we must extract the bytes 16 | #and add them to a part of the zip file. 17 | ForEach ($file In $files) 18 | { 19 | $partName=New-Object System.Uri(( "/"+ $file.Name) , [System.UriKind]"Relative") 20 | #Create each part. (No line break!) 21 | $part=$ZipPackage.CreatePart($partName, "application/zip", 22 | [System.IO.Packaging.CompressionOption]"Maximum") 23 | $bytes=[System.IO.File]::ReadAllBytes($file) 24 | $stream=$part.GetStream() 25 | $stream.Write($bytes, 0, $bytes.Length) 26 | $stream.Close() 27 | } 28 | 29 | #Close the package when we're done. 30 | $ZipPackage.Close() 31 | } 32 | 33 | function getGitTagOrReleaseName() { 34 | $log = (git log --pretty=oneline --decorate | select -first 1) 35 | #$log = (git log --pretty=oneline --decorate | select -Index 3) 36 | $commit_id = "commit-" + ($log.Split(" ") | select -first 1).substring(0,8) 37 | 38 | $changes = (git status -s --porcelain) 39 | $has_uncommitted_changes = ($changes.length -ne 0) 40 | 41 | #echo "has changes?"$has_uncommitted_changes 42 | #echo "last id:"$commit_id 43 | 44 | if($log -match "tag: (?.*?)[,)]") { 45 | #basically, if there is a release associated with this commit, use that release isntead of commit 46 | $commit_id = $Matches["ver"] 47 | } 48 | 49 | if($has_uncommitted_changes) { 50 | $version = $commit_id+"+changes" 51 | 52 | } else { 53 | $version = $commit_id 54 | 55 | } 56 | return $version 57 | } 58 | 59 | 60 | 61 | function getGitReleaseZipFileName() { 62 | #$projectname = "FloatingGlucose" 63 | $projectname = $env:projectname 64 | $buildconfigname = ($env:buildconfigname).toLower() 65 | $version = getGitTagOrReleaseName 66 | return "standalone-$projectname-$version-$buildconfigname.zip" 67 | 68 | } 69 | 70 | function writeVersionFile(){ 71 | $buildver = getGitTagOrReleaseName 72 | #echo $buildver > version.txt #|out-file "version.txt" -Encoding "ascii" 73 | #writealltext must be used to avoid newline 74 | [System.IO.File]::WriteAllText("version.txt", $buildver) 75 | echo "wrote $buildver to version.txt" 76 | } 77 | 78 | function createReleaseZipFile(){ 79 | 80 | $zipfilename = getGitReleaseZipFileName 81 | $dir = pwd 82 | $outpath = "$dir\$zipfilename" 83 | 84 | $files = ls . | where { -not ($_.name.EndsWith(".vshost.exe") -or $_.name.EndsWith(".zip")) } 85 | 86 | echo "writing zip to $outpath" 87 | $files | out-zip -zipfilename $outpath -files $files 88 | return $zipfilename 89 | } 90 | 91 | -------------------------------------------------------------------------------- /FloatingGlucose/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 53 | 58 | 59 | 60 | 61 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /FloatingGlucose/installer/floatingglucose.iss: -------------------------------------------------------------------------------- 1 | ; Script generated by the Inno Setup Script Wizard. 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | 4 | #define MyAppName "FloatingGlucose" 5 | 6 | #define MyAppPublisher "code.bjorninge.no" 7 | #define MyAppURL "http://code.bjorninge.no" 8 | #define MyAppExeName "FloatingGlucose.exe" 9 | #define SourceDir ".." 10 | ;should be set via iscc 11 | ;cd installer 12 | ;"%programfiles(x86)%/Inno Setup 5/ISCC.exe" floatingglucose.iss 13 | ;#define MyAppVersion "0.9.9test" 14 | ;#define ReleaseType "Release" 15 | 16 | ;setting up signing: 17 | ; in inno setup, go to tools->configure sign tools 18 | ; add a new tool, call it "signtool" with the following value 19 | ; "C:\Program Files (x86)\Windows Kits\8.1\bin\x64\signtool.exe" sign /fd sha256 /tr http://time.certum.pl/ /sha1 0B360481F3F59C7F398FB0417AA4B341EB018906 $f 20 | [Setup] 21 | ; NOTE: The value of AppId uniquely identifies this application. 22 | ; Do not use the same AppId value in installers for other applications. 23 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 24 | SignTool=signtool /d $qFloatingGlucoseInstaller$q $f 25 | AppId={{B271DEE6-C788-4604-B392-8B8DD16C97A8} 26 | AppName={#MyAppName} 27 | AppVersion={#MyAppVersion} 28 | ;AppVerName={#MyAppName} {#MyAppVersion} 29 | AppPublisher={#MyAppPublisher} 30 | AppPublisherURL={#MyAppURL} 31 | AppSupportURL={#MyAppURL} 32 | AppUpdatesURL={#MyAppURL} 33 | DefaultDirName={pf}\{#MyAppName} 34 | DisableProgramGroupPage=yes 35 | OutputBaseFilename="{#MyAppName}Setup-{#ReleaseType}-{#MyAppVersion}" 36 | Compression=lzma 37 | SolidCompression=yes 38 | SetupIconFile="{#SourceDir}\noun_335372_cc_v2.ico" 39 | 40 | [Languages] 41 | Name: "english"; MessagesFile: "compiler:Default.isl" 42 | 43 | [Tasks] 44 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 45 | Name: "startup"; Description: "{cm:AutoStartProgram,{#MyAppName}}"; GroupDescription: "{cm:AdditionalIcons}" 46 | 47 | [Files] 48 | Source: "..\bin\{#ReleaseType}\FloatingGlucose.exe"; DestDir: "{app}"; Flags: ignoreversion 49 | Source: "..\bin\{#ReleaseType}\FloatingGlucose.exe.config"; DestDir: "{app}"; Flags: ignoreversion 50 | Source: "..\bin\{#ReleaseType}\FloatingGlucose.pdb"; DestDir: "{app}"; Flags: ignoreversion 51 | Source: "..\bin\{#ReleaseType}\Newtonsoft.Json.dll"; DestDir: "{app}"; Flags: ignoreversion 52 | Source: "..\bin\{#ReleaseType}\Newtonsoft.Json.xml"; DestDir: "{app}"; Flags: ignoreversion 53 | Source: "..\bin\{#ReleaseType}\NAudio.dll"; DestDir: "{app}"; Flags: ignoreversion 54 | Source: "..\bin\{#ReleaseType}\NAudio.xml"; DestDir: "{app}"; Flags: ignoreversion 55 | Source: "..\bin\{#ReleaseType}\README_attributions.txt"; DestDir: "{app}"; Flags: ignoreversion 56 | Source: "..\bin\{#ReleaseType}\version.txt"; DestDir: "{app}"; Flags: ignoreversion 57 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 58 | [Dirs] 59 | Name: "{app}"; Permissions: users-full 60 | 61 | [Icons] 62 | Name: "{commonprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" 63 | Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon 64 | Name: "{userstartup}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: startup 65 | 66 | [Run] 67 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent 68 | 69 | -------------------------------------------------------------------------------- /FloatingGlucose/installer/floatingglucosenosign.iss: -------------------------------------------------------------------------------- 1 | ; Script generated by the Inno Setup Script Wizard. 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | 4 | #define MyAppName "FloatingGlucose" 5 | 6 | #define MyAppPublisher "code.bjorninge.no" 7 | #define MyAppURL "http://code.bjorninge.no" 8 | #define MyAppExeName "FloatingGlucose.exe" 9 | #define SourceDir ".." 10 | ;should be set via iscc 11 | ;cd installer 12 | ;"%programfiles(x86)%/Inno Setup 5/ISCC.exe" floatingglucose.iss 13 | #define MyAppVersion "1.5.4" 14 | #define ReleaseType "Release+NoSign" 15 | 16 | ;setting up signing: 17 | ; in inno setup, go to tools->configure sign tools 18 | ; add a new tool, call it "signtool" with the following value 19 | ; "C:\Program Files (x86)\Windows Kits\8.1\bin\x64\signtool.exe" sign /fd sha256 /tr http://time.certum.pl/ /sha1 0B360481F3F59C7F398FB0417AA4B341EB018906 $f 20 | [Setup] 21 | ; NOTE: The value of AppId uniquely identifies this application. 22 | ; Do not use the same AppId value in installers for other applications. 23 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 24 | ;SignTool=signtool /d $qFloatingGlucoseInstaller$q $f 25 | AppId={{B271DEE6-C788-4604-B392-8B8DD16C97A8} 26 | AppName={#MyAppName} 27 | AppVersion={#MyAppVersion} 28 | ;AppVerName={#MyAppName} {#MyAppVersion} 29 | AppPublisher={#MyAppPublisher} 30 | AppPublisherURL={#MyAppURL} 31 | AppSupportURL={#MyAppURL} 32 | AppUpdatesURL={#MyAppURL} 33 | DefaultDirName={pf}\{#MyAppName} 34 | DisableProgramGroupPage=yes 35 | OutputBaseFilename="{#MyAppName}Setup-{#ReleaseType}-{#MyAppVersion}" 36 | Compression=lzma 37 | SolidCompression=yes 38 | SetupIconFile="{#SourceDir}\noun_335372_cc_v2.ico" 39 | 40 | [Languages] 41 | Name: "english"; MessagesFile: "compiler:Default.isl" 42 | 43 | [Tasks] 44 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 45 | Name: "startup"; Description: "{cm:AutoStartProgram,{#MyAppName}}"; GroupDescription: "{cm:AdditionalIcons}" 46 | 47 | [Files] 48 | Source: "..\bin\{#ReleaseType}\FloatingGlucose.exe"; DestDir: "{app}"; Flags: ignoreversion 49 | Source: "..\bin\{#ReleaseType}\FloatingGlucose.exe.config"; DestDir: "{app}"; Flags: ignoreversion 50 | Source: "..\bin\{#ReleaseType}\FloatingGlucose.pdb"; DestDir: "{app}"; Flags: ignoreversion 51 | Source: "..\bin\{#ReleaseType}\Newtonsoft.Json.dll"; DestDir: "{app}"; Flags: ignoreversion 52 | Source: "..\bin\{#ReleaseType}\Newtonsoft.Json.xml"; DestDir: "{app}"; Flags: ignoreversion 53 | Source: "..\bin\{#ReleaseType}\NAudio.dll"; DestDir: "{app}"; Flags: ignoreversion 54 | Source: "..\bin\{#ReleaseType}\NAudio.xml"; DestDir: "{app}"; Flags: ignoreversion 55 | Source: "..\bin\{#ReleaseType}\README_attributions.txt"; DestDir: "{app}"; Flags: ignoreversion 56 | Source: "..\bin\{#ReleaseType}\version.txt"; DestDir: "{app}"; Flags: ignoreversion 57 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 58 | [Dirs] 59 | Name: "{app}"; Permissions: users-full 60 | 61 | [Icons] 62 | Name: "{commonprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" 63 | Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon 64 | Name: "{userstartup}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: startup 65 | 66 | [Run] 67 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent 68 | 69 | -------------------------------------------------------------------------------- /FloatingGlucose/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | https://... 7 | 8 | 9 | True 10 | 11 | 12 | 60 13 | 14 | 15 | 1 16 | 17 | 18 | True 19 | 20 | 21 | 13 22 | 23 | 24 | 11 25 | 26 | 27 | 4.5 28 | 29 | 30 | 3.8 31 | 32 | 33 | False 34 | 35 | 36 | mmol 37 | 38 | 39 | 15 40 | 41 | 42 | 30 43 | 44 | 45 | True 46 | 47 | 48 | False 49 | 50 | 51 | 85 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | FF000000 64 | 65 | 66 | 67 | 68 | 69 | Stretch 70 | 71 | 72 | -------------------------------------------------------------------------------- /FloatingGlucose/FloatingGlucose.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net5.0-windows 4 | WinExe 5 | false 6 | publish\ 7 | true 8 | Disk 9 | false 10 | Foreground 11 | 7 12 | Days 13 | false 14 | false 15 | false 16 | false 17 | 0 18 | 1.0.0.%2a 19 | false 20 | true 21 | false 22 | true 23 | true 24 | 25 | 26 | noun_335372_cc_v2.ico 27 | 28 | 29 | Always 30 | 31 | 32 | 33 | 34 | 35 | 36 | false 37 | 38 | 39 | true 40 | bin\Debug+nosign\ 41 | MinimumRecommendedRules.ruleset 42 | default 43 | 44 | 45 | bin\Release+nosign\ 46 | true 47 | MinimumRecommendedRules.ruleset 48 | 49 | 50 | FloatingGlucose.Program 51 | 52 | 53 | true 54 | 55 | 56 | 57 | _Inlined\ShareClientDotNet\%(RecursiveDir)%(Filename)%(Extension) 58 | 59 | 60 | 61 | 62 | 63 | 64 | Always 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /FloatingGlucose.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31221.73 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FloatingGlucose", "FloatingGlucose\FloatingGlucose.csproj", "{8D044F87-547D-4E76-8A67-3225DB984C63}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShareClientDotNet", "ShareClientDotNet\ShareClientDotNet.csproj", "{2AD02968-E7EA-4989-8BD8-FAD6A31C0072}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChartWin", "winforms-datavisualization-net5\src\System.Windows.Forms.DataVisualization\ChartWin.csproj", "{CD571259-3FDE-4B5E-B0A7-F87388583326}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0A541A11-C497-425D-9DA1-E81757B2CDEF}" 13 | ProjectSection(SolutionItems) = preProject 14 | .github\workflows\FloatingGlucose.yaml = .github\workflows\FloatingGlucose.yaml 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Debug+nosign|Any CPU = Debug+nosign|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | Release+nosign|Any CPU = Release+nosign|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {8D044F87-547D-4E76-8A67-3225DB984C63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {8D044F87-547D-4E76-8A67-3225DB984C63}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {8D044F87-547D-4E76-8A67-3225DB984C63}.Debug+nosign|Any CPU.ActiveCfg = Debug+nosign|Any CPU 28 | {8D044F87-547D-4E76-8A67-3225DB984C63}.Debug+nosign|Any CPU.Build.0 = Debug+nosign|Any CPU 29 | {8D044F87-547D-4E76-8A67-3225DB984C63}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {8D044F87-547D-4E76-8A67-3225DB984C63}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {8D044F87-547D-4E76-8A67-3225DB984C63}.Release+nosign|Any CPU.ActiveCfg = Release+nosign|Any CPU 32 | {8D044F87-547D-4E76-8A67-3225DB984C63}.Release+nosign|Any CPU.Build.0 = Release+nosign|Any CPU 33 | {2AD02968-E7EA-4989-8BD8-FAD6A31C0072}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {2AD02968-E7EA-4989-8BD8-FAD6A31C0072}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {2AD02968-E7EA-4989-8BD8-FAD6A31C0072}.Debug+nosign|Any CPU.ActiveCfg = Debug|Any CPU 36 | {2AD02968-E7EA-4989-8BD8-FAD6A31C0072}.Debug+nosign|Any CPU.Build.0 = Debug|Any CPU 37 | {2AD02968-E7EA-4989-8BD8-FAD6A31C0072}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {2AD02968-E7EA-4989-8BD8-FAD6A31C0072}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {2AD02968-E7EA-4989-8BD8-FAD6A31C0072}.Release+nosign|Any CPU.ActiveCfg = Release|Any CPU 40 | {2AD02968-E7EA-4989-8BD8-FAD6A31C0072}.Release+nosign|Any CPU.Build.0 = Release|Any CPU 41 | {CD571259-3FDE-4B5E-B0A7-F87388583326}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {CD571259-3FDE-4B5E-B0A7-F87388583326}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {CD571259-3FDE-4B5E-B0A7-F87388583326}.Debug+nosign|Any CPU.ActiveCfg = Debug|Any CPU 44 | {CD571259-3FDE-4B5E-B0A7-F87388583326}.Debug+nosign|Any CPU.Build.0 = Debug|Any CPU 45 | {CD571259-3FDE-4B5E-B0A7-F87388583326}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {CD571259-3FDE-4B5E-B0A7-F87388583326}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {CD571259-3FDE-4B5E-B0A7-F87388583326}.Release+nosign|Any CPU.ActiveCfg = Release|Any CPU 48 | {CD571259-3FDE-4B5E-B0A7-F87388583326}.Release+nosign|Any CPU.Build.0 = Release|Any CPU 49 | EndGlobalSection 50 | GlobalSection(SolutionProperties) = preSolution 51 | HideSolutionNode = FALSE 52 | EndGlobalSection 53 | GlobalSection(ExtensibilityGlobals) = postSolution 54 | SolutionGuid = {343C9185-4732-4FB7-B2B6-DA4C0640083F} 55 | EndGlobalSection 56 | EndGlobal 57 | -------------------------------------------------------------------------------- /FloatingGlucose/Classes/Utils/GlucoseMath.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Text; 3 | using System; 4 | 5 | using static FloatingGlucose.Properties.Settings; 6 | 7 | namespace FloatingGlucose.Classes.Utils 8 | { 9 | internal class GlucoseMath 10 | { 11 | public static decimal ToMmol(decimal number) 12 | { 13 | return decimal.Round(number / 18.01559M, 1); 14 | } 15 | 16 | public static decimal ToMgdl(decimal number) 17 | { 18 | return decimal.Round(number * 18.01559M, 0);//no decimals for mgdl values 19 | } 20 | 21 | private static double calculateSlope(BgReading current, BgReading last) 22 | { 23 | if (current.Timestamp == last.Timestamp) 24 | { 25 | return 0.0; 26 | } 27 | 28 | return (last._glucose - current._glucose) / (last.Timestamp - current.Timestamp); 29 | } 30 | 31 | private static double calculateSlopeByMinute(BgReading current, BgReading last) 32 | { 33 | return calculateSlope(current, last) * 60000; 34 | } 35 | 36 | public static string GetGlucoseDirection(BgReading current, BgReading last) 37 | { 38 | //converted from: 39 | //https://github.com/NightscoutFoundation/xDrip/blob/b34e63223573a367105b31536e413d4b83b78dab/app/src/main/java/com/eveningoutpost/dexdrip/Models/BgReading.java#L563 40 | 41 | double sloapPerMinute = calculateSlopeByMinute(current, last); 42 | 43 | String arrow = "Unknown"; 44 | if (sloapPerMinute <= (-3.5)) 45 | { 46 | arrow = "DoubleDown"; 47 | } 48 | else if (sloapPerMinute <= (-2)) 49 | { 50 | arrow = "SingleDown"; 51 | } 52 | else if (sloapPerMinute <= (-1)) 53 | { 54 | arrow = "FortyFiveDown"; 55 | } 56 | else if (sloapPerMinute <= (1)) 57 | { 58 | arrow = "Flat"; 59 | } 60 | else if (sloapPerMinute <= (2)) 61 | { 62 | arrow = "FortyFiveUp"; 63 | } 64 | else if (sloapPerMinute <= (3.5)) 65 | { 66 | arrow = "SingleUp"; 67 | } 68 | else if (sloapPerMinute <= (40)) 69 | { 70 | arrow = "DoubleUp"; 71 | } 72 | return arrow; 73 | } 74 | 75 | public static GlucoseAlarmStatusEnum GetGlucoseAlarmStatus(decimal glucose) 76 | { 77 | if (!Default.EnableAlarms) 78 | { 79 | return GlucoseAlarmStatusEnum.Normal; 80 | } 81 | decimal urgentHigh = Default.AlarmUrgentHigh; 82 | decimal high = Default.AlarmHigh; 83 | decimal low = Default.AlarmLow; 84 | decimal urgentLow = Default.AlarmUrgentLow; 85 | 86 | if (glucose <= urgentLow) 87 | { 88 | return GlucoseAlarmStatusEnum.UrgentLow; 89 | } 90 | else if (glucose <= low) 91 | { 92 | return GlucoseAlarmStatusEnum.Low; 93 | } 94 | else if (glucose <= high) 95 | { 96 | return GlucoseAlarmStatusEnum.Normal; 97 | } 98 | else if (glucose <= urgentHigh) 99 | { 100 | return GlucoseAlarmStatusEnum.High; 101 | } 102 | else if (glucose >= urgentHigh) 103 | { 104 | return GlucoseAlarmStatusEnum.UrgentHigh; 105 | } 106 | 107 | return GlucoseAlarmStatusEnum.Unknown; 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /FloatingGlucose/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 0, 0, 0, 0 14 | 15 | 16 | 17 | 18 | https://... 19 | 20 | 21 | True 22 | 23 | 24 | 60 25 | 26 | 27 | 1 28 | 29 | 30 | True 31 | 32 | 33 | 13 34 | 35 | 36 | 11 37 | 38 | 39 | 4.5 40 | 41 | 42 | 3.8 43 | 44 | 45 | False 46 | 47 | 48 | mmol 49 | 50 | 51 | 15 52 | 53 | 54 | 30 55 | 56 | 57 | True 58 | 59 | 60 | False 61 | 62 | 63 | 85 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | FF000000 73 | 74 | 75 | 76 | 77 | 78 | Stretch 79 | 80 | 81 | 82 | 83 | 84 | 85 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /FloatingGlucose/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace FloatingGlucose.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FloatingGlucose.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Byte[]. 65 | /// 66 | internal static byte[] alarm { 67 | get { 68 | object obj = ResourceManager.GetObject("alarm", resourceCulture); 69 | return ((byte[])(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized resource of type System.Byte[]. 75 | /// 76 | internal static byte[] alarm2 { 77 | get { 78 | object obj = ResourceManager.GetObject("alarm2", resourceCulture); 79 | return ((byte[])(obj)); 80 | } 81 | } 82 | 83 | /// 84 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 85 | /// 86 | internal static System.Drawing.Icon browse_file { 87 | get { 88 | object obj = ResourceManager.GetObject("browse_file", resourceCulture); 89 | return ((System.Drawing.Icon)(obj)); 90 | } 91 | } 92 | 93 | /// 94 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 95 | /// 96 | internal static System.Drawing.Icon noun_335372_cc_v2 { 97 | get { 98 | object obj = ResourceManager.GetObject("noun_335372_cc_v2", resourceCulture); 99 | return ((System.Drawing.Icon)(obj)); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /FloatingGlucose/Classes/Audio/SoundAlarm.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Text; 3 | using NAudio.Wave; 4 | using System; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using static FloatingGlucose.Properties.Settings; 8 | 9 | namespace FloatingGlucose.Classes 10 | { 11 | public class SoundAlarm : IDisposable 12 | { 13 | private static readonly SoundAlarm instance = new SoundAlarm(); 14 | 15 | private bool disposed; 16 | private IWavePlayer device = new WaveOut(); 17 | 18 | private readonly Mp3FileReader glucoseAlarm = new Mp3FileReader(new MemoryStream(Properties.Resources.alarm)); 19 | private readonly Mp3FileReader staleAlarm = new Mp3FileReader(new MemoryStream(Properties.Resources.alarm2)); 20 | 21 | private bool isCurrentlyPlaying = false; 22 | 23 | private DateTime? postponed; 24 | 25 | public DateTime? GetPostponedUntil() => this.postponed; 26 | 27 | // Explicit static constructor to tell C# compiler 28 | // not to mark type as beforefieldinit 29 | static SoundAlarm() 30 | { 31 | } 32 | 33 | private SoundAlarm() 34 | { 35 | } 36 | 37 | public static SoundAlarm Instance 38 | { 39 | get 40 | { 41 | return instance; 42 | } 43 | } 44 | 45 | public void RemovePostpone() 46 | { 47 | this.postponed = null; 48 | } 49 | 50 | public void PostponeAlarm(int minutes) 51 | { 52 | var now = DateTime.Now; 53 | var postponeUntil = now.AddMinutes(minutes); 54 | 55 | this.postponed = postponeUntil; 56 | Debug.WriteLine($"Postponed any audible alarms until {postponeUntil.ToLocalTime()}"); 57 | if (this.isCurrentlyPlaying) 58 | { 59 | this.StopAlarm(); 60 | } 61 | } 62 | 63 | public bool IsPostponed() 64 | { 65 | if (this.postponed == null) 66 | { 67 | return false; 68 | } 69 | 70 | var now = DateTime.Now; 71 | return this.postponed > now; 72 | } 73 | 74 | public void PlayAlarm(Mp3FileReader fileReader) 75 | { 76 | if (this.isCurrentlyPlaying || !Default.EnableAlarms || !Default.EnableSoundAlarms) 77 | { 78 | // We don't want to play if there is already other players active 79 | // even if the other players are playing other alarms.. 80 | return; 81 | } 82 | //alarms should not be sound if actively snoozed or workstation is locked 83 | if (this.IsPostponed() || (AppShared.IsWorkStationLocked && Default.DisableSoundAlarmsOnWorkstationLock)) 84 | { 85 | return; 86 | } 87 | 88 | var loop = new LoopStream(fileReader); 89 | device.Init(loop); 90 | device.Play(); 91 | this.isCurrentlyPlaying = true; 92 | } 93 | 94 | public void PlayStaleAlarm() 95 | { 96 | this.PlayAlarm(staleAlarm); 97 | } 98 | 99 | public void StopAlarmIfPostponed() 100 | { 101 | if (this.IsPostponed()) 102 | { 103 | this.StopAlarm(); 104 | } 105 | } 106 | 107 | public void StopAlarm() 108 | { 109 | this.device.Stop(); 110 | this.isCurrentlyPlaying = false; 111 | staleAlarm.CurrentTime = 112 | glucoseAlarm.CurrentTime = new TimeSpan(0); 113 | } 114 | 115 | public void PlayGlucoseAlarm() 116 | { 117 | this.PlayAlarm(glucoseAlarm); 118 | } 119 | 120 | /// 121 | /// The dispose method that implements IDisposable. 122 | /// 123 | public void Dispose() 124 | { 125 | this.Dispose(true); 126 | GC.SuppressFinalize(this); 127 | } 128 | 129 | /// 130 | /// The virtual dispose method that allows 131 | /// classes inherited from this one to dispose their resources. 132 | /// 133 | /// 134 | protected virtual void Dispose(bool disposing) 135 | { 136 | if (!disposed) 137 | { 138 | if (disposing) 139 | { 140 | // Dispose managed resources here. 141 | } 142 | 143 | this.device.Dispose(); 144 | this.device = null; 145 | } 146 | 147 | disposed = true; 148 | } 149 | 150 | ~SoundAlarm() 151 | { 152 | this.Dispose(false); 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | # User-specific files 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # User-specific files (MonoDevelop/Xamarin Studio) 10 | *.userprefs 11 | 12 | # Build results 13 | [Dd]ebug/ 14 | [Dd]ebugPublic/ 15 | [Rr]elease/ 16 | [Rr]eleases/ 17 | [Xx]64/ 18 | [Xx]86/ 19 | [Bb]uild/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | *.VC.db 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 88 | *.sap 89 | 90 | # TFS 2012 Local Workspace 91 | $tf/ 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | *.DotSettings.user 100 | 101 | # JustCode is a .NET coding add-in 102 | .JustCode 103 | 104 | # TeamCity is a build add-in 105 | _TeamCity* 106 | 107 | # DotCover is a Code Coverage Tool 108 | *.dotCover 109 | 110 | # NCrunch 111 | _NCrunch_* 112 | .*crunch*.local.xml 113 | nCrunchTemp_* 114 | 115 | # MightyMoose 116 | *.mm.* 117 | AutoTest.Net/ 118 | 119 | # Web workbench (sass) 120 | .sass-cache/ 121 | 122 | # Installshield output folder 123 | [Ee]xpress/ 124 | 125 | # DocProject is a documentation generator add-in 126 | DocProject/buildhelp/ 127 | DocProject/Help/*.HxT 128 | DocProject/Help/*.HxC 129 | DocProject/Help/*.hhc 130 | DocProject/Help/*.hhk 131 | DocProject/Help/*.hhp 132 | DocProject/Help/Html2 133 | DocProject/Help/html 134 | 135 | # Click-Once directory 136 | publish/ 137 | 138 | # Publish Web Output 139 | *.[Pp]ublish.xml 140 | *.azurePubxml 141 | 142 | # TODO: Un-comment the next line if you do not want to checkin 143 | # your web deploy settings because they may include unencrypted 144 | # passwords 145 | #*.pubxml 146 | *.publishproj 147 | 148 | # NuGet Packages 149 | *.nupkg 150 | # The packages folder can be ignored because of Package Restore 151 | **/packages/* 152 | # except build/, which is used as an MSBuild target. 153 | !**/packages/build/ 154 | # Uncomment if necessary however generally it will be regenerated when needed 155 | #!**/packages/repositories.config 156 | # NuGet v3's project.json files produces more ignoreable files 157 | *.nuget.props 158 | *.nuget.targets 159 | 160 | # Microsoft Azure Build Output 161 | csx/ 162 | *.build.csdef 163 | 164 | # Microsoft Azure Emulator 165 | ecf/ 166 | rcf/ 167 | 168 | # Microsoft Azure ApplicationInsights config file 169 | ApplicationInsights.config 170 | 171 | # Windows Store app package directory 172 | AppPackages/ 173 | BundleArtifacts/ 174 | 175 | # Visual Studio cache files 176 | # files ending in .cache can be ignored 177 | *.[Cc]ache 178 | # but keep track of directories ending in .cache 179 | !*.[Cc]ache/ 180 | 181 | # Others 182 | ClientBin/ 183 | [Ss]tyle[Cc]op.* 184 | ~$* 185 | *~ 186 | *.dbmdl 187 | *.dbproj.schemaview 188 | *.pfx 189 | *.publishsettings 190 | node_modules/ 191 | orleans.codegen.cs 192 | 193 | # RIA/Silverlight projects 194 | Generated_Code/ 195 | 196 | # Backup & report files from converting an old project file 197 | # to a newer Visual Studio version. Backup files are not needed, 198 | # because we have git ;-) 199 | _UpgradeReport_Files/ 200 | Backup*/ 201 | UpgradeLog*.XML 202 | UpgradeLog*.htm 203 | 204 | # SQL Server files 205 | *.mdf 206 | *.ldf 207 | 208 | # Business Intelligence projects 209 | *.rdl.data 210 | *.bim.layout 211 | *.bim_*.settings 212 | 213 | # Microsoft Fakes 214 | FakesAssemblies/ 215 | 216 | # GhostDoc plugin setting file 217 | *.GhostDoc.xml 218 | 219 | # Node.js Tools for Visual Studio 220 | .ntvs_analysis.dat 221 | 222 | # Visual Studio 6 build log 223 | *.plg 224 | 225 | # Visual Studio 6 workspace options file 226 | *.opt 227 | 228 | # Visual Studio LightSwitch build output 229 | **/*.HTMLClient/GeneratedArtifacts 230 | **/*.DesktopClient/GeneratedArtifacts 231 | **/*.DesktopClient/ModelManifest.xml 232 | **/*.Server/GeneratedArtifacts 233 | **/*.Server/ModelManifest.xml 234 | _Pvt_Extensions 235 | 236 | # LightSwitch generated files 237 | GeneratedArtifacts/ 238 | ModelManifest.xml 239 | 240 | # Paket dependency manager 241 | .paket/paket.exe 242 | 243 | # FAKE - F# Make 244 | .fake/ -------------------------------------------------------------------------------- /FloatingGlucose/FormWebbrowser.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /FloatingGlucose/FormGlucoseSettings.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | -------------------------------------------------------------------------------- /FloatingGlucose/.editorconfig: -------------------------------------------------------------------------------- 1 | # To learn more about .editorconfig see https://aka.ms/editorconfigdocs 2 | ############################### 3 | # Core EditorConfig Options # 4 | ############################### 5 | root = true 6 | # All files 7 | [*] 8 | indent_style = space 9 | # Code files 10 | [*.{cs,csx,vb,vbx}] 11 | indent_size = 4 12 | insert_final_newline = true 13 | charset = utf-8-bom 14 | ############################### 15 | # .NET Coding Conventions # 16 | ############################### 17 | [*.{cs,vb}] 18 | # Organize usings 19 | dotnet_sort_system_directives_first = true 20 | # this. preferences 21 | dotnet_style_qualification_for_field = false:silent 22 | dotnet_style_qualification_for_property = false:silent 23 | dotnet_style_qualification_for_method = false:silent 24 | dotnet_style_qualification_for_event = false:silent 25 | # Language keywords vs BCL types preferences 26 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 27 | dotnet_style_predefined_type_for_member_access = true:silent 28 | # Parentheses preferences 29 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 30 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 31 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 32 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 33 | # Modifier preferences 34 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 35 | dotnet_style_readonly_field = true:suggestion 36 | # Expression-level preferences 37 | dotnet_style_object_initializer = true:suggestion 38 | dotnet_style_collection_initializer = true:suggestion 39 | dotnet_style_explicit_tuple_names = true:suggestion 40 | dotnet_style_null_propagation = true:suggestion 41 | dotnet_style_coalesce_expression = true:suggestion 42 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent 43 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 44 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 45 | dotnet_style_prefer_auto_properties = true:silent 46 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 47 | dotnet_style_prefer_conditional_expression_over_return = true:silent 48 | 49 | dotnet_diagnostic.CA1416.severity = none 50 | ############################### 51 | # Naming Conventions # 52 | ############################### 53 | # Style Definitions 54 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 55 | # Use PascalCase for constant fields 56 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion 57 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 58 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 59 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 60 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = * 61 | dotnet_naming_symbols.constant_fields.required_modifiers = const 62 | ############################### 63 | # C# Coding Conventions # 64 | ############################### 65 | [*.cs] 66 | # var preferences 67 | csharp_style_var_for_built_in_types = true:silent 68 | csharp_style_var_when_type_is_apparent = true:silent 69 | csharp_style_var_elsewhere = true:silent 70 | # Expression-bodied members 71 | csharp_style_expression_bodied_methods = false:silent 72 | csharp_style_expression_bodied_constructors = false:silent 73 | csharp_style_expression_bodied_operators = false:silent 74 | csharp_style_expression_bodied_properties = true:silent 75 | csharp_style_expression_bodied_indexers = true:silent 76 | csharp_style_expression_bodied_accessors = true:silent 77 | # Pattern matching preferences 78 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 79 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 80 | # Null-checking preferences 81 | csharp_style_throw_expression = true:suggestion 82 | csharp_style_conditional_delegate_call = true:suggestion 83 | # Modifier preferences 84 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion 85 | # Expression-level preferences 86 | csharp_prefer_braces = true:silent 87 | csharp_style_deconstructed_variable_declaration = true:suggestion 88 | csharp_prefer_simple_default_expression = true:suggestion 89 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 90 | csharp_style_inlined_variable_declaration = true:suggestion 91 | ############################### 92 | # C# Formatting Rules # 93 | ############################### 94 | # New line preferences 95 | csharp_new_line_before_open_brace = all 96 | csharp_new_line_before_else = true 97 | csharp_new_line_before_catch = true 98 | csharp_new_line_before_finally = true 99 | csharp_new_line_before_members_in_object_initializers = true 100 | csharp_new_line_before_members_in_anonymous_types = true 101 | csharp_new_line_between_query_expression_clauses = true 102 | # Indentation preferences 103 | csharp_indent_case_contents = true 104 | csharp_indent_switch_labels = true 105 | csharp_indent_labels = flush_left 106 | # Space preferences 107 | csharp_space_after_cast = false 108 | csharp_space_after_keywords_in_control_flow_statements = true 109 | csharp_space_between_method_call_parameter_list_parentheses = false 110 | csharp_space_between_method_declaration_parameter_list_parentheses = false 111 | csharp_space_between_parentheses = false 112 | csharp_space_before_colon_in_inheritance_clause = true 113 | csharp_space_after_colon_in_inheritance_clause = true 114 | csharp_space_around_binary_operators = before_and_after 115 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 116 | csharp_space_between_method_call_name_and_opening_parenthesis = false 117 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 118 | # Wrapping preferences 119 | csharp_preserve_single_line_statements = true 120 | csharp_preserve_single_line_blocks = true 121 | ############################### 122 | # VB Coding Conventions # 123 | ############################### 124 | [*.vb] 125 | # Modifier preferences 126 | visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion 127 | -------------------------------------------------------------------------------- /FloatingGlucose/FloatingGlucose.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | 124 | 150, 17 125 | 126 | -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/Plugins/DexcomShare/DexcomShareUS.cs: -------------------------------------------------------------------------------- 1 | using FloatingGlucose.Classes.Extensions; 2 | using ShareClientDotNet; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.Specialized; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace FloatingGlucose.Classes.DataSources.Plugins 11 | { 12 | internal class DexcomShareEndpoint : IDataSourcePlugin 13 | { 14 | public string Acknowledgment => "Dexcom share plugin by Bjørn inge Vikhammermo"; 15 | 16 | public string AcknowledgmentUrl => ""; 17 | public string Author => "Bjørn inge Vikhammermo"; 18 | 19 | public bool PluginDisabled => false; 20 | public bool RequiresUserNameAndPassword => true; 21 | public bool RequiresDataSource => false; 22 | 23 | public List HandleFormatting() => null; 24 | 25 | public bool RequiresBrowseButton => false; 26 | public bool PluginHandlesFormatting => false; 27 | public string BrowseDialogFileFilter => ""; 28 | public virtual string DataSourceShortName => "Dexcom Share (US)"; 29 | public virtual int SortOrder => 25; 30 | 31 | //private List csv = new List(); 32 | 33 | public DateTime Date 34 | { 35 | get 36 | { 37 | var reading = this.shareGlucose.First(); 38 | return reading.LocalTime; 39 | } 40 | } 41 | 42 | public double Delta => this.Glucose - this.PreviousGlucose; 43 | 44 | // 45 | // Raw glucose is not supported for this plugin 46 | // 47 | public double RawDelta => 0.0; 48 | 49 | public double RoundedRawDelta() => 0.0; 50 | 51 | public double RawGlucose => 0.0; 52 | public double PreviousRawGlucose => 0.0; 53 | 54 | public DateTime LocalDate => this.Date; 55 | 56 | public double RoundedDelta() => Math.Round(this.Delta, 1); 57 | 58 | public double Glucose 59 | { 60 | get 61 | { 62 | var reading = this.shareGlucose?.First(); 63 | if (reading == null) 64 | { 65 | return 0; 66 | } 67 | return Convert.ToDouble(this.UserWantsMmolUnits() ? reading.ValueMmol : reading.ValueMgdl); 68 | } 69 | } 70 | 71 | protected ShareClient shareClient = new DebuggableShareClient(); 72 | 73 | protected List shareGlucose = new List(); 74 | 75 | public double PreviousGlucose 76 | { 77 | get 78 | { 79 | var reading = this.shareGlucose?.Skip(1)?.First(); 80 | if (reading == null) 81 | { 82 | return 0; 83 | } 84 | return Convert.ToDouble(this.UserWantsMmolUnits() ? reading.ValueMmol : reading.ValueMgdl); 85 | } 86 | } 87 | 88 | public string Direction 89 | { 90 | get 91 | { 92 | var first = this.shareGlucose?.First(); 93 | 94 | // 95 | // Converts between share glucose Direction ordinals to nightscout glucose directions 96 | // which is the expected format 97 | // 98 | switch (first.Trend) 99 | { 100 | case ShareGlucoseSlopeOrdinals.DOUBLE_UP: 101 | return "DoubleUp"; 102 | 103 | case ShareGlucoseSlopeOrdinals.SINGLE_UP: 104 | return "SingleUp"; 105 | 106 | case ShareGlucoseSlopeOrdinals.UP_45: 107 | return "FortyFiveUp"; 108 | 109 | case ShareGlucoseSlopeOrdinals.FLAT: 110 | return "Flat"; 111 | 112 | case ShareGlucoseSlopeOrdinals.DOWN_45: 113 | return "FortyFiveDown"; 114 | 115 | case ShareGlucoseSlopeOrdinals.SINGLE_DOWN: 116 | return "SingleDown"; 117 | 118 | case ShareGlucoseSlopeOrdinals.DOUBLE_DOWN: 119 | return "DoubleDown"; 120 | 121 | case ShareGlucoseSlopeOrdinals.NOT_COMPUTABLE: 122 | return "NOT COMPUTABLE"; 123 | 124 | case ShareGlucoseSlopeOrdinals.OUT_OF_RANGE: 125 | return "OUT OF RANGE"; 126 | 127 | case ShareGlucoseSlopeOrdinals.NONE: 128 | return "None"; 129 | } 130 | 131 | return ""; 132 | } 133 | } 134 | 135 | public virtual void OnPluginSelected(FormGlucoseSettings form) 136 | { 137 | form.lblDataSourceLocation.Text = "Dexcom share server"; 138 | form.txtDataSourceLocation.Text = this.shareClient.CurrentDexcomServer; 139 | 140 | form.lblUsername.Text = "User Name"; 141 | form.lblPassword.Enabled = true; 142 | form.txtPassword.Enabled = true; 143 | } 144 | 145 | public virtual bool VerifyConfig(Properties.Settings settings) 146 | { 147 | var username = settings.UserName; 148 | var password = settings.HashedPassword?.Text ?? ""; 149 | 150 | if (username.Length < 2) 151 | { 152 | throw new ConfigValidationException("UserName field was not correctly filled!"); 153 | } 154 | 155 | if (password.Length < 1) 156 | { 157 | throw new ConfigValidationException("Password field was not correctly filled!"); 158 | } 159 | 160 | shareClient.Username = username; 161 | shareClient.Password = password; 162 | 163 | return true; 164 | } 165 | 166 | public virtual async Task GetDataSourceDataAsync(NameValueCollection locations) 167 | { 168 | try 169 | { 170 | //this can return null if the internet connection is broken 171 | Console.WriteLine($"Will attempt {this.shareClient.CurrentDexcomServer}, user: {shareClient.Username}"); 172 | this.shareGlucose = await shareClient.FetchLast(3); 173 | } 174 | catch (SpecificShareError err) 175 | { 176 | if (err.code == ShareKnownRemoteErrorCodes.AuthenticateAccountNotFound || err.code == ShareKnownRemoteErrorCodes.AuthenticatePasswordInvalid) 177 | { 178 | this.shareGlucose = null; 179 | throw new ConfigValidationException($"Dexcom share client uknown username or password. Entered username: {shareClient.Username}"); 180 | } 181 | } 182 | 183 | return this; 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/Plugins/GlimpFileEndpoint.cs: -------------------------------------------------------------------------------- 1 | using FloatingGlucose.Classes.Extensions; 2 | using FloatingGlucose.Classes.Utils; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.Specialized; 6 | using System.Diagnostics; 7 | using System.Globalization; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Net.Http; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace FloatingGlucose.Classes.DataSources.Plugins 15 | { 16 | internal class RawGlimpData : BgReading 17 | { 18 | public string FileVersion; 19 | public string RawGlucose; 20 | public string SensorId; 21 | } 22 | 23 | internal class GlimpFileEndpoint : IDataSourcePlugin 24 | { 25 | public string Acknowledgment => "Glimp plugin by Bjørn inge Vikhammermo"; 26 | 27 | public string AcknowledgmentUrl => ""; 28 | public string Author => "Bjørn inge Vikhammermo"; 29 | 30 | public bool PluginDisabled => false; 31 | public bool RequiresUserNameAndPassword => false; 32 | public bool RequiresDataSource => true; 33 | 34 | public List HandleFormatting() => null; 35 | 36 | public bool RequiresBrowseButton => true; 37 | public bool PluginHandlesFormatting => false; 38 | public string BrowseDialogFileFilter => "Glimp Glucose file |GlicemiaMisurazioni.csv"; 39 | public string DataSourceShortName => "Glimp Dropbox File"; 40 | public virtual int SortOrder => 20; 41 | 42 | private List csv = new List(); 43 | 44 | public DateTime Date => this.csv.First().DateReading; 45 | 46 | public double Delta => this.Glucose - this.PreviousGlucose; 47 | 48 | // 49 | // Raw glucose is not supported for this plugin 50 | // 51 | public double RawDelta => 0.0; 52 | 53 | public double RoundedRawDelta() => 0.0; 54 | 55 | public double RawGlucose => 0.0; 56 | public double PreviousRawGlucose => 0.0; 57 | 58 | public DateTime LocalDate => this.Date; 59 | 60 | public double RoundedDelta() => Math.Round(this.Delta, 1); 61 | 62 | public double Glucose => this.UserWantsMmolUnits() ? this.csv.First().GlucoseMmol : this.csv.First().GlucoseMgdl; 63 | 64 | private DateTime GlimpDateStringToDateTime(string reading) => DateTime.ParseExact(reading, "dd/MM/yyyy HH.mm.ss", CultureInfo.InvariantCulture); 65 | 66 | public double PreviousGlucose 67 | { 68 | get 69 | { 70 | RawGlimpData reading; 71 | 72 | try 73 | { 74 | reading = this.csv.Skip(1).First(); 75 | } 76 | catch (InvalidOperationException) 77 | { 78 | //this is factually incorrect, but will provide a delta of 0 in the GUI, 79 | //which is what we want 80 | //reading the first entry might also fail, but we presume the file has content when it's created 81 | reading = this.csv.First(); 82 | } 83 | 84 | return this.UserWantsMmolUnits() ? reading.GlucoseMmol : reading.GlucoseMgdl; 85 | } 86 | } 87 | 88 | public string Direction 89 | { 90 | get 91 | { 92 | //Sligthly more advanced implementation of direction 93 | //Calculates a slope per minute 94 | //(how much glucose has changed every minute between two readings) 95 | 96 | var first = this.csv.First(); 97 | RawGlimpData last; 98 | try 99 | { 100 | last = this.csv.Skip(1).First(); 101 | } 102 | catch (InvalidOperationException) 103 | { 104 | last = first; 105 | } 106 | 107 | var dir = first.GetRelativeGlucoseDirection(last); 108 | Debug.WriteLine($"glimpfile got glucose direction:{dir}"); 109 | return dir; 110 | } 111 | } 112 | 113 | public void OnPluginSelected(FormGlucoseSettings form) 114 | { 115 | form.lblDataSourceLocation.Text = "Your File Dump location"; 116 | var source = form.txtDataSourceLocation.Text.ToLower(); 117 | //for this plugin, we don't handle http:// and https:// 118 | //if the source does not resemble an url, it should clearly be removed. 119 | if (source.StartsWith("http://") || source.StartsWith("https://")) 120 | { 121 | form.txtDataSourceLocation.Text = ""; 122 | } 123 | } 124 | 125 | public bool VerifyConfig(Properties.Settings settings) 126 | { 127 | if (!Validators.IsReadableFile(settings.DataPathLocation)) 128 | { 129 | throw new ConfigValidationException("You have entered an invalid file path for the data dump!"); 130 | } 131 | 132 | return true; 133 | } 134 | 135 | public async Task GetDataSourceDataAsync(NameValueCollection locations) 136 | { 137 | var datapath = locations["raw"]; 138 | var client = new HttpClient(); 139 | 140 | this.csv.Clear(); 141 | // datapath is expected to be a valid file 142 | // Exceptions will be handled by the main program 143 | using (var reader = new StreamReader(datapath, System.Text.Encoding.Unicode)) 144 | { 145 | int i = 0; 146 | 147 | while (true) 148 | { 149 | //something wrong here, it is read wrongly.. 150 | string line = await reader.ReadLineAsync(); 151 | if (line == null || i++ > 100) 152 | { 153 | break; 154 | } 155 | if (line.Trim().Length == 0) 156 | { 157 | continue; 158 | } 159 | string[] items = line.Split(';'); 160 | var data = new RawGlimpData(); 161 | 162 | //Measurement type (0=manual measurement, 1=Freestyle Libre) 163 | //if (items[6] == "1") 164 | //{ 165 | data.FileVersion = items[0]; 166 | data.DateReading = GlimpDateStringToDateTime(items[1]); 167 | data.RawGlucose = items[4]; 168 | data._glucose = Double.Parse(items[5], NumberStyles.Any, CultureInfo.InvariantCulture); 169 | data.SensorId = items[7]; 170 | //} 171 | 172 | this.csv.Add(data); 173 | } 174 | } 175 | 176 | return this; 177 | } 178 | } 179 | } -------------------------------------------------------------------------------- /FloatingGlucose/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\alarm.mp3;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 123 | 124 | 125 | ..\Resources\alarm2.mp3;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 126 | 127 | 128 | ..\Resources\browse-file.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 129 | 130 | 131 | ..\Resources\noun_335372_cc_v2.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 132 | 133 | -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/Plugins/NightscoutPebbleEndpoint/NightscoutPebbleEndpoint.cs: -------------------------------------------------------------------------------- 1 | using FloatingGlucose.Classes.Extensions; 2 | using FloatingGlucose.Classes.Utils; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.Specialized; 7 | using System.Globalization; 8 | using System.Linq; 9 | using System.Net.Http; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace FloatingGlucose.Classes.DataSources.Plugins 14 | { 15 | internal class NightscoutPebbleEndpoint : IDataSourcePlugin 16 | { 17 | public string Acknowledgment => "Nightscout plugin by Bjørn inge Vikhammermo"; 18 | 19 | public string AcknowledgmentUrl => ""; 20 | public string Author => "Bjørn inge Vikhammermo"; 21 | 22 | public bool PluginHandlesFormatting => false; 23 | public virtual bool RequiresDataSource => true; 24 | 25 | public List HandleFormatting() => null; 26 | 27 | public virtual bool RequiresUserNameAndPassword => true; 28 | public virtual bool PluginDisabled => false; 29 | public virtual bool RequiresBrowseButton => false; 30 | public virtual string BrowseDialogFileFilter => ""; 31 | public GeneratedNsData NsData; 32 | public DateTime Date { get; set; } 33 | 34 | public double Glucose { get; set; } 35 | public double Delta { get; set; } 36 | public string Direction { get; set; } 37 | 38 | public virtual int SortOrder => 10; 39 | 40 | public double RawDelta => this.RawGlucose - this.PreviousRawGlucose; 41 | 42 | public double RoundedDelta() => Math.Round(this.Delta, 1); 43 | 44 | public double RoundedRawDelta() => Math.Round(this.RawDelta, 1); 45 | 46 | //public static CultureInfo Culture = new CultureInfo("en-US"); 47 | 48 | public double CalculateRawGlucose(Cal cal, Bg bg, double actualGlucose) 49 | { 50 | double number; 51 | double curBG = actualGlucose; 52 | int specialValue = 0; 53 | 54 | if (this.UserWantsMmolUnits()) 55 | { 56 | if ((actualGlucose < 2.2) || (actualGlucose > 22.2)) 57 | { 58 | specialValue = 1; 59 | } 60 | 61 | curBG = curBG * 18.01559; 62 | } 63 | else 64 | { 65 | if ((actualGlucose < 40) || (actualGlucose > 400)) 66 | { 67 | specialValue = 1; 68 | } 69 | } 70 | 71 | //this special value is only triggered when the Dexcom upload is brand new 72 | //from a brand new sensor? 73 | if (specialValue == 1) 74 | { 75 | number = cal.scale * (bg.unfiltered - cal.intercept) / cal.slope; 76 | } 77 | else 78 | { 79 | number = cal.scale * (bg.filtered - cal.intercept) / cal.slope / curBG; 80 | number = cal.scale * (bg.unfiltered - cal.intercept) / cal.slope / number; 81 | } 82 | 83 | if (this.UserWantsMmolUnits()) 84 | { 85 | number = number / 18.01559; 86 | } 87 | 88 | return number; 89 | } 90 | 91 | public double PreviousGlucose 92 | { 93 | get 94 | { 95 | var bgs = this.NsData.bgs.Skip(1).First(); 96 | return Double.Parse(bgs.sgv, NumberStyles.Any, CultureInfo.InvariantCulture); 97 | } 98 | } 99 | 100 | public double PreviousRawGlucose 101 | { 102 | get 103 | { 104 | try 105 | { 106 | var cal = this.NsData.cals.Skip(1).First(); 107 | var bg = this.NsData.bgs.Skip(1).First(); 108 | return this.CalculateRawGlucose(cal, bg, this.PreviousGlucose); 109 | } 110 | catch (InvalidOperationException) 111 | { 112 | throw new InvalidJsonDataException("The raw data are not available, enable RAWBG in your azure settings"); 113 | } 114 | } 115 | } 116 | 117 | public double RawGlucose 118 | { 119 | get 120 | { 121 | try 122 | { 123 | var cal = this.NsData.cals.First(); 124 | var bg = this.NsData.bgs.First(); 125 | return this.CalculateRawGlucose(cal, bg, this.Glucose); 126 | } 127 | catch (InvalidOperationException) 128 | { 129 | throw new InvalidJsonDataException("The raw data are not available, you may have enable RAWBG in your azure settings"); 130 | } 131 | } 132 | } 133 | 134 | public DateTime LocalDate => this.Date.ToLocalTime(); 135 | 136 | public virtual string DataSourceShortName => "Nightscout URL"; 137 | 138 | public virtual void OnPluginSelected(FormGlucoseSettings form) 139 | { 140 | form.lblDataSourceLocation.Text = "Your Nightscout installation URL"; 141 | form.lblUsername.Text = "Access Token"; 142 | form.lblPassword.Enabled = false; 143 | form.txtPassword.Enabled = false; 144 | 145 | var source = form.txtDataSourceLocation.Text.ToLower(); 146 | //for this plugin, we handle only http:// and https:// 147 | //if the source does not resemble an url, it should clearly be removed. 148 | if (!(source.StartsWith("http://") || source.StartsWith("https://"))) 149 | { 150 | form.txtDataSourceLocation.Text = ""; 151 | } 152 | } 153 | 154 | public virtual bool VerifyConfig(Properties.Settings settings) 155 | { 156 | if (!Validators.IsUrl(settings.DataPathLocation) || settings.DataPathLocation == "https://mysite.azurewebsites.net") 157 | { 158 | throw new ConfigValidationException("You have entered an invalid Nightscout site URL"); 159 | } 160 | 161 | return true; 162 | } 163 | 164 | public virtual async Task GetDataSourceDataAsync(NameValueCollection locations) 165 | { 166 | var datapath = locations["location"]; 167 | var client = new HttpClient(); 168 | Bg bgs = null; 169 | 170 | string urlContents = await client.GetStringAsync(datapath); 171 | 172 | //urlContents = "{ \"status\":[{\"now\":1471947452808}],\"bgs\":[],\"cals\":[]"; 173 | //urlContents = "{}" 174 | var parsed = 175 | this.NsData = JsonConvert.DeserializeObject(urlContents); 176 | try 177 | { 178 | bgs = parsed.bgs.First(); 179 | this.Direction = bgs.direction; 180 | this.Glucose = Double.Parse(bgs.sgv, NumberStyles.Any, CultureInfo.InvariantCulture); 181 | this.Date = DateTimeOffset.FromUnixTimeMilliseconds(bgs.datetime).DateTime; 182 | this.Delta = Double.Parse(bgs.bgdelta, NumberStyles.Any, CultureInfo.InvariantCulture); 183 | } 184 | catch (InvalidOperationException) 185 | { 186 | //this exception might be hit when the Nightscout installation is brand new or contains no recent data; 187 | throw new MissingDataException("No data"); 188 | } 189 | 190 | return this; 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/Plugins/YrWeatherService/YrWeatherServiceEndpoint.cs: -------------------------------------------------------------------------------- 1 | using FloatingGlucose.Classes.Extensions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.Specialized; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Xml.Serialization; 11 | using Xml2CSharp; 12 | 13 | namespace FloatingGlucose.Classes.DataSources.Plugins 14 | { 15 | internal class YrWeatherServiceEndpoint : IDataSourcePlugin 16 | { 17 | public string Acknowledgment => "Weather forecast from Yr, delivered by the Norwegian Meteorological Institute and the NRK"; 18 | 19 | public string AcknowledgmentUrl => ""; 20 | public string Author => "Bjørn inge Vikhammermo"; 21 | 22 | public bool PluginHandlesFormatting => true; 23 | public bool RequiresUserNameAndPassword => false; 24 | public bool RequiresDataSource => true; 25 | public bool PluginDisabled => true; 26 | public bool RequiresBrowseButton => false; 27 | public string BrowseDialogFileFilter => ""; 28 | public string DataSourceShortName => "Yr.no Weather"; 29 | public virtual int SortOrder => 20; 30 | 31 | public DateTime Date 32 | { 33 | get 34 | { 35 | var from = this.weatherData?.Forecast?.Tabular?.Time[0]?.From; 36 | return from == null || from.Length == 0 ? DateTime.MinValue : DateTime.Parse(from); 37 | } 38 | } 39 | 40 | public double Delta => this.Glucose - this.PreviousGlucose; 41 | 42 | //glucose,lastupdate,delta,notification 43 | public List HandleFormatting() 44 | { 45 | var texts = new List(); 46 | 47 | if (this.weatherData != null) 48 | { 49 | var temp = this.weatherData.Forecast?.Tabular?.Time?[0]?.Temperature; 50 | var val = temp?.Value ?? ""; 51 | var unit = temp.Unit?[0].ToString()?.ToUpper() ?? ""; 52 | var temperature = $"{val} °{unit}"; 53 | texts.Add(temperature); 54 | texts.Add(this.Date.ToLocalTime().ToShortTimeString()); 55 | texts.Add(this.weatherData.Location?.Name ?? ""); 56 | 57 | texts.Add("Weatherdata plugin active"); 58 | 59 | return texts; 60 | } 61 | texts.Add("Error"); 62 | texts.Add("Cannot"); 63 | texts.Add("get weatherdata"); 64 | texts.Add("error"); 65 | 66 | return texts; 67 | } 68 | 69 | // 70 | // Raw glucose is not supported for this plugin 71 | // 72 | public double RawDelta => 0.0; 73 | 74 | public double RoundedRawDelta() => 0.0; 75 | 76 | public double RawGlucose => 0.0; 77 | public double PreviousRawGlucose => 0.0; 78 | 79 | public DateTime LocalDate => this.Date; 80 | 81 | public double RoundedDelta() => Math.Round(this.Delta, 1); 82 | 83 | public double Glucose => 0.0; 84 | 85 | public double PreviousGlucose => 0.0; 86 | 87 | public string Direction => "Flat"; 88 | 89 | private string yrSearchUrl = "https://www.yr.no/soek/soek.aspx?&land=&spr=eng®ion1=&sok=Search&sted="; 90 | private string yrForeCast = "http://www.yr.no/{0}/forecast_hour_by_hour.xml"; 91 | 92 | private Weatherdata weatherData; 93 | 94 | private string getYrForeCastURL(string pathname) => String.Format(this.yrForeCast, pathname); 95 | 96 | //override specific for this plugin as it doesn't really handle deltas 97 | 98 | public FormWebbrowser createYrSearchBrowser(string city) 99 | { 100 | var js = @" 101 | 102 | (function(){ 103 | if (!String.prototype.startsWith) { 104 | String.prototype.startsWith = function(searchString, position){ 105 | position = position || 0; 106 | return this.substr(position, searchString.length) === searchString; 107 | }; 108 | } 109 | 110 | function handler(ev) { 111 | var event = window.event || event; 112 | var target = event.srcElement || event.target; 113 | 114 | if( target.tagName != 'A') { 115 | return false; 116 | } 117 | 118 | var pathname = target.pathname; 119 | if(pathname.substring(0, 1) != '/') { 120 | pathname = '/'+ pathname; 121 | } 122 | //alert('will now try to close, pathname: ' + pathname); 123 | if(pathname.startsWith('/place/') || 124 | pathname.startsWith('/sted/') || 125 | pathname.startsWith('/stad/') || 126 | pathname.startsWith('/sadji/') 127 | ) { 128 | //alert('will now close with reval:' + pathname) 129 | //console.log('set return value and close:' + pathname); 130 | window.external.SetReturnValueAndClose(pathname); 131 | } 132 | 133 | ev.preventDefault && ev.preventDefault(); 134 | 135 | return false; 136 | } 137 | var aEL=window.addEventListener; 138 | alert('Please choose the most appropriate location in the following web page') 139 | 140 | document.body[ aEL ? 'addEventListener' : 'attachEvent' ]( aEL ? 'click' : 'onclick', handler ) 141 | })(); 142 | "; 143 | var form = new FormWebbrowser(js); 144 | form.SetBrowserUrl(this.yrSearchUrl + city); 145 | form.ShowDialog(); 146 | return form; 147 | } 148 | 149 | private Weatherdata deserializeWeatherData(string xmlsource) 150 | { 151 | Weatherdata weather; 152 | 153 | using (var reader = new StringReader(xmlsource)) 154 | { 155 | var serializer = new XmlSerializer(typeof(Weatherdata)); 156 | weather = (Weatherdata)serializer.Deserialize(reader); 157 | 158 | return weather; 159 | } 160 | } 161 | 162 | private bool verifyYrPathString(string path) 163 | { 164 | if (path.StartsWith("/place/") || path.StartsWith("/sted/") || 165 | path.StartsWith("/stad/") || 166 | path.StartsWith("/sadji/")) 167 | { 168 | return path.Count(x => x == '/') > 4; 169 | } 170 | 171 | return false; 172 | } 173 | 174 | public void OnPluginSelected(FormGlucoseSettings form) 175 | { 176 | form.lblDataSourceLocation.Text = "Your closest location"; 177 | 178 | var source = form.txtDataSourceLocation.Text.ToLower(); 179 | //for this plugin, we handle only city names and yr.no path names 180 | //if the source resembles an url, it should clearly be removed. 181 | if (source.StartsWith("http://") || source.StartsWith("https://")) 182 | { 183 | form.txtDataSourceLocation.Text = ""; 184 | } 185 | } 186 | 187 | public bool VerifyConfig(Properties.Settings settings) 188 | { 189 | var pathname = settings.DataPathLocation; 190 | settings.DataPathLocation = pathname; 191 | if (this.verifyYrPathString(pathname)) 192 | { 193 | return true; 194 | } 195 | // 196 | // pathname entered by user is most probably in the form of "Stockholm" 197 | // We need the a more specific path "/place/Sweden/Stockholm/Stockholm" 198 | // 199 | 200 | var client = this.createYrSearchBrowser(pathname); 201 | 202 | settings.DataPathLocation = client.WebPageReturnValue; 203 | 204 | var url = this.getYrForeCastURL(settings.DataPathLocation); 205 | 206 | if (!this.verifyYrPathString(settings.DataPathLocation)) 207 | { 208 | throw new ConfigValidationException("The entered weather location was not correctly formed!"); 209 | } 210 | 211 | return true; 212 | } 213 | 214 | public async Task GetDataSourceDataAsync(NameValueCollection locations) 215 | { 216 | this.WriteDebug("Will now try refresh using YR.no"); 217 | var pathname = locations["raw"]; 218 | var forecastUrl = this.getYrForeCastURL(pathname); 219 | 220 | try 221 | { 222 | var client = new HttpClient(); 223 | string urlContents = await client.GetStringAsync(forecastUrl); 224 | 225 | this.weatherData = this.deserializeWeatherData(urlContents); 226 | } 227 | catch (Exception err) 228 | { 229 | this.WriteDebug($"got error in fetching xmldata from {forecastUrl}: {err.Message}"); 230 | throw; 231 | } 232 | 233 | return this; 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /FloatingGlucose/Classes/DataSources/Plugins/YrWeatherService/YrHourByHour.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 3 | 4 | http://www.apache.org/licenses/LICENSE-2.0 5 | 6 | Adapted from http://xmltocsharp.azurewebsites.net/ by bjorninges.spam@bjorninge.no 7 | */ 8 | 9 | using System.Collections.Generic; 10 | using System.Xml.Serialization; 11 | 12 | namespace Xml2CSharp 13 | { 14 | [XmlRoot(ElementName = "timezone")] 15 | public class Timezone 16 | { 17 | [XmlAttribute(AttributeName = "id")] 18 | public string Id { get; set; } 19 | 20 | [XmlAttribute(AttributeName = "utcoffsetMinutes")] 21 | public string UtcoffsetMinutes { get; set; } 22 | } 23 | 24 | [XmlRoot(ElementName = "location")] 25 | public class Location2 26 | { 27 | [XmlAttribute(AttributeName = "altitude")] 28 | public string Altitude { get; set; } 29 | 30 | [XmlAttribute(AttributeName = "latitude")] 31 | public string Latitude { get; set; } 32 | 33 | [XmlAttribute(AttributeName = "longitude")] 34 | public string Longitude { get; set; } 35 | 36 | [XmlAttribute(AttributeName = "geobase")] 37 | public string Geobase { get; set; } 38 | 39 | [XmlAttribute(AttributeName = "geobaseid")] 40 | public string Geobaseid { get; set; } 41 | } 42 | 43 | [XmlRoot(ElementName = "location")] 44 | public class Location 45 | { 46 | [XmlElement(ElementName = "name")] 47 | public string Name { get; set; } 48 | 49 | [XmlElement(ElementName = "type")] 50 | public string Type { get; set; } 51 | 52 | [XmlElement(ElementName = "country")] 53 | public string Country { get; set; } 54 | 55 | [XmlElement(ElementName = "timezone")] 56 | public Timezone Timezone { get; set; } 57 | 58 | [XmlElement(ElementName = "location2")] 59 | public Location2 Location2 { get; set; } 60 | } 61 | 62 | [XmlRoot(ElementName = "link")] 63 | public class Link 64 | { 65 | [XmlAttribute(AttributeName = "text")] 66 | public string Text { get; set; } 67 | 68 | [XmlAttribute(AttributeName = "url")] 69 | public string Url { get; set; } 70 | 71 | [XmlAttribute(AttributeName = "id")] 72 | public string Id { get; set; } 73 | } 74 | 75 | [XmlRoot(ElementName = "credit")] 76 | public class Credit 77 | { 78 | [XmlElement(ElementName = "link")] 79 | public Link Link { get; set; } 80 | } 81 | 82 | [XmlRoot(ElementName = "links")] 83 | public class Links 84 | { 85 | [XmlElement(ElementName = "link")] 86 | public List Link { get; set; } 87 | } 88 | 89 | [XmlRoot(ElementName = "meta")] 90 | public class Meta 91 | { 92 | [XmlElement(ElementName = "lastupdate")] 93 | public string Lastupdate { get; set; } 94 | 95 | [XmlElement(ElementName = "nextupdate")] 96 | public string Nextupdate { get; set; } 97 | } 98 | 99 | [XmlRoot(ElementName = "sun")] 100 | public class Sun 101 | { 102 | [XmlAttribute(AttributeName = "rise")] 103 | public string Rise { get; set; } 104 | 105 | [XmlAttribute(AttributeName = "set")] 106 | public string Set { get; set; } 107 | } 108 | 109 | [XmlRoot(ElementName = "time")] 110 | public class Time 111 | { 112 | [XmlElement(ElementName = "title")] 113 | public string Title { get; set; } 114 | 115 | [XmlElement(ElementName = "body")] 116 | public string Body { get; set; } 117 | 118 | [XmlAttribute(AttributeName = "from")] 119 | public string From { get; set; } 120 | 121 | [XmlAttribute(AttributeName = "to")] 122 | public string To { get; set; } 123 | 124 | [XmlElement(ElementName = "symbol")] 125 | public Symbol Symbol { get; set; } 126 | 127 | [XmlElement(ElementName = "precipitation")] 128 | public Precipitation Precipitation { get; set; } 129 | 130 | [XmlElement(ElementName = "windDirection")] 131 | public WindDirection WindDirection { get; set; } 132 | 133 | [XmlElement(ElementName = "windSpeed")] 134 | public WindSpeed WindSpeed { get; set; } 135 | 136 | [XmlElement(ElementName = "temperature")] 137 | public Temperature Temperature { get; set; } 138 | 139 | [XmlElement(ElementName = "pressure")] 140 | public Pressure Pressure { get; set; } 141 | } 142 | 143 | [XmlRoot(ElementName = "location")] 144 | public class Location3 145 | { 146 | [XmlElement(ElementName = "time")] 147 | public List