├── OpenWindesheart ├── Properties │ └── launchSettings.json ├── Exceptions │ ├── ReadException.cs │ ├── BatteryException.cs │ └── ConnectionException.cs ├── Models │ ├── HeartrateData.cs │ ├── BLEScanResult.cs │ ├── BatteryData.cs │ ├── StepData.cs │ └── ActivitySample.cs ├── OpenWindesheart.csproj ├── OpenWindesheart.sln ├── Devices │ └── MiBand3 │ │ ├── Services │ │ ├── MiBand3DateTimeService.cs │ │ ├── MiBand3HeartrateService.cs │ │ ├── MiBand3StepsService.cs │ │ ├── MiBand3BatteryService.cs │ │ ├── MiBand3AuthenticationService.cs │ │ ├── MiBand3ConfigurationService.cs │ │ └── MiBand3SampleService.cs │ │ ├── Helpers │ │ └── MiBand3ConversionHelper.cs │ │ ├── Resources │ │ └── MiBand3Resource.cs │ │ └── Models │ │ └── MiBand3.cs ├── Windesheart.cs ├── Helpers │ └── ConversionHelper.cs ├── BLEDevice.cs └── Services │ └── BluetoothService.cs ├── README-Scanning.md ├── README-Readingdata.md ├── README-Connecting.md ├── README-SupportNewDevice.md ├── README-Settings.md ├── README-Samples.md ├── README.md ├── .gitignore └── LICENSE.md /OpenWindesheart/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "OpenWindesheart": { 4 | "commandName": "Project", 5 | "nativeDebugging": true 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /OpenWindesheart/Exceptions/ReadException.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using System; 16 | 17 | namespace OpenWindesheart.Exceptions 18 | { 19 | class ReadException : Exception 20 | { 21 | public ReadException() { } 22 | 23 | public ReadException(string message) : base(message) { } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /OpenWindesheart/Exceptions/BatteryException.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using System; 16 | 17 | namespace OpenWindesheart.Exceptions 18 | { 19 | public class BatteryException : Exception 20 | { 21 | public BatteryException() { } 22 | 23 | public BatteryException(string message) : base(message) { } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /OpenWindesheart/Exceptions/ConnectionException.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using System; 16 | 17 | namespace OpenWindesheart.Exceptions 18 | { 19 | public class ConnectionException : Exception 20 | { 21 | public ConnectionException() { } 22 | 23 | public ConnectionException(string message) : base(message) { } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /OpenWindesheart/Models/HeartrateData.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using OpenWindesheart.Exceptions; 16 | 17 | namespace OpenWindesheart.Models 18 | { 19 | public class HeartrateData 20 | { 21 | public byte[] Rawdata { get; } 22 | 23 | public int Heartrate { get; } 24 | 25 | public HeartrateData(byte[] rawdata) 26 | { 27 | Rawdata = rawdata; 28 | Heartrate = rawdata[1]; 29 | } 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /OpenWindesheart/OpenWindesheart.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | true 6 | 2.0.3 7 | K. van Sloten, R. Abächerli, H. van der Gugten, T.C. Marschalk 8 | Research group ICT innovations in Health Care - Windesheim University of Applied Sciences 9 | The open-source OpenWindesheart is an SDK used for scanning, connecting and getting data from activity-trackers using bluetooth. 10 | Current support: - Mi Band 3 - 11 | Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 12 | https://github.com/ictinnovaties-zorg/openwindesheart 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /OpenWindesheart/OpenWindesheart.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29424.173 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenWindesheart", "OpenWindesheart.csproj", "{804BBA3E-3027-45D6-8384-E806A51E504A}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {804BBA3E-3027-45D6-8384-E806A51E504A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {804BBA3E-3027-45D6-8384-E806A51E504A}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {804BBA3E-3027-45D6-8384-E806A51E504A}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {804BBA3E-3027-45D6-8384-E806A51E504A}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {5A250640-D341-4F92-B01F-666E9FB58489} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /OpenWindesheart/Models/BLEScanResult.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using Plugin.BluetoothLE; 16 | 17 | namespace OpenWindesheart.Models 18 | { 19 | public class BLEScanResult 20 | { 21 | public BLEDevice Device { get; } 22 | public int Rssi { get; } 23 | public IAdvertisementData AdvertisementData { get; } 24 | 25 | public BLEScanResult(BLEDevice device, int rssi, IAdvertisementData advertisementData) 26 | { 27 | Device = device; 28 | Rssi = rssi; 29 | AdvertisementData = advertisementData; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /OpenWindesheart/Models/BatteryData.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | namespace OpenWindesheart.Models 16 | { 17 | public enum BatteryStatus 18 | { 19 | Charging, 20 | NotCharging 21 | } 22 | 23 | public class BatteryData 24 | { 25 | public BatteryStatus Status 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | public int Percentage 32 | { 33 | get; 34 | set; 35 | } 36 | 37 | public byte[] RawData 38 | { 39 | get; 40 | set; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /OpenWindesheart/Models/StepData.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using OpenWindesheart.Helpers; 16 | 17 | namespace OpenWindesheart.Models 18 | { 19 | public class StepData 20 | { 21 | public StepData() 22 | { 23 | 24 | } 25 | 26 | public StepData(byte[] rawData) 27 | { 28 | RawData = rawData; 29 | byte[] stepsValue = new byte[] { RawData[1], RawData[2] }; 30 | StepCount = ConversionHelper.ToUint16(stepsValue); 31 | } 32 | 33 | public byte[] RawData { get; } 34 | public int StepCount { get; } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /README-Scanning.md: -------------------------------------------------------------------------------- 1 | 2 | # Scanning 3 | 4 | Scanning allows the user to scan for BLEDevices. 5 | 6 | ### Windesheart.StartScanning(Callback) 7 | This method starts the scanning progress and calls the provided callback method when a BLEDevice is found. It returns a boolean to indicate whether scanning has started. An example: 8 | 9 | 10 | 11 | ```csharp 12 | public void Main(){ 13 | if(Windesheart.StartScanning(WhenDeviceFound)){ 14 | Console.WriteLine("Scanning started!"); 15 | } 16 | else 17 | { 18 | Console.WriteLine("Couldn't start scanning. Is Bluetooth turned on?"); 19 | } 20 | } 21 | 22 | public void WhenDeviceFound(BLEScanResult result){ 23 | Console.WriteLine("Device found!"); 24 | BLEDevice device = result.device; 25 | int Rssi = result.Rssi; 26 | AdvertisementData data = result.AdvertismentData; 27 | } 28 | ``` 29 | As you can see in `WhenDeviceFound`, a BLEScanResult parameter is given. This parameter contains the device itself, the initial Rssi of the device, and the AdvertisementData for that device. 30 | 31 | When using this method, be sure to call the `Windesheart.StopScanning()` method at some point as well, otherwise your device will continue scanning indefinitely. 32 | 33 | ### Windesheart.StopScanning() 34 | This method stops the scanning process. This is a void method. You can assume that this method always succeeds. 35 | ```csharp 36 | Windesheart.StartScanning(WhenDeviceFound); //Start scanning 37 | await Task.Delay(2000); //Wait 2 seconds 38 | Windesheart.StopScanning(); //Stop scanning 39 | ``` 40 | 41 | [<---- Back to mainpage](https://github.com/ictinnovaties-zorg/openwindesheart/blob/master/) -------------------------------------------------------------------------------- /OpenWindesheart/Models/ActivitySample.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using System; 16 | 17 | namespace OpenWindesheart.Models 18 | { 19 | public class ActivitySample 20 | { 21 | public byte[] RawData { get; } 22 | public DateTime Timestamp { get; } 23 | public int UnixEpochTimestamp { get; } 24 | public int Category { get; } 25 | public int RawIntensity { get; } 26 | public int Steps { get; } 27 | public int HeartRate { get; } 28 | 29 | public ActivitySample(DateTime timestamp, int category, int intensity, int steps, int heartrate, byte[] rawdata = null) 30 | { 31 | this.RawData = rawdata; 32 | this.Timestamp = timestamp; 33 | this.UnixEpochTimestamp = (Int32)(timestamp.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; 34 | this.Category = category; 35 | this.RawIntensity = intensity; 36 | this.Steps = steps; 37 | this.HeartRate = heartrate; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /OpenWindesheart/Devices/MiBand3/Services/MiBand3DateTimeService.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using System; 16 | using System.Reactive.Linq; 17 | using OpenWindesheart.Devices.MiBand3Device.Models; 18 | using OpenWindesheart.Devices.MiBand3Device.Resources; 19 | using OpenWindesheart.Helpers; 20 | 21 | namespace OpenWindesheart.Devices.MiBand3Device.Services 22 | { 23 | public class MiBand3DateTimeService 24 | { 25 | private readonly MiBand3 _miBand3; 26 | 27 | public MiBand3DateTimeService(MiBand3 device) 28 | { 29 | _miBand3 = device; 30 | } 31 | 32 | public void SetTime(DateTime time) 33 | { 34 | //Convert time to bytes 35 | byte[] timeToSet = ConversionHelper.GetTimeBytes(time, ConversionHelper.TimeUnit.Seconds); 36 | 37 | //Send to MiBand 38 | _miBand3.GetCharacteristic(MiBand3Resource.GuidCurrentTime).Write(timeToSet).Subscribe(result => 39 | { 40 | Console.WriteLine("Time set to " + time.ToString()); 41 | }); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README-Readingdata.md: -------------------------------------------------------------------------------- 1 | # Reading data 2 | The Windesheart SDK allows reading data from BLE Devices. 3 | 4 | 5 | ## Reading current data 6 | The data that can be read at the moment is: 7 | 8 | * Battery data by using `BLEDevice.GetBattery()` 9 | * Steps by using `BLEDevice.GetSteps()` 10 | 11 | Please note that all of this is **current** data. If you want data of the past, you have to get the samples of your BLE Device. 12 | 13 | **Example** 14 | 15 | ```csharp 16 | BLEDevice device = // your device 17 | StepData steps = device.GetSteps(); 18 | int stepCount = steps.StepCount; 19 | 20 | BatteryData battery= device.GetBattery(); 21 | BatteryStatus status = battery.Status; //Either Charging or NotCharging 22 | int percentage = battery.Percentage; 23 | ``` 24 | 25 | 26 | ## Real time data 27 | There is also an option to continuously get data from your device. This works by providing a callback method that will be run when the data has been updated. 28 | 29 | This works with: 30 | 31 | - Steps by using `BLEDevice.EnableRealTimeSteps(StepData)` 32 | - Battery by using `BLEDevice.EnableRealTimeBattery(BatteryData)` 33 | - Heart rate by using `BLEDevice.EnableRealTimeHeartrate(HeartrateData)` 34 | 35 | **Example** 36 | Let's say we want to continuously get the steps of the device for a period of 1 minute, then we could do this: 37 | 38 | ```csharp 39 | 40 | async void Main(){ 41 | device.EnableRealTimeSteps(onStepsUpdated); 42 | await Task.Delay(60000); 43 | device.DisableRealTimeSteps(); //Don't forget to disable when not needed anymore! 44 | } 45 | 46 | void OnStepsUpdated(StepData data){ 47 | int steps = data.StepCount; 48 | Console.WriteLine("Steps updated: " + steps); 49 | } 50 | ``` 51 | [<---- Back to mainpage](https://github.com/ictinnovaties-zorg/openwindesheart/blob/master/) 52 | -------------------------------------------------------------------------------- /README-Connecting.md: -------------------------------------------------------------------------------- 1 | # Connecting 2 | This documentation explains and demonstrates how to connect with a Bluetooth device. You should have access to the device itself by scanning for it. If you haven't, please take a look at the scanning docs. 3 | 4 | **Please note:** The SDK only supports one device to be connected at a time. 5 | 6 | ## BLEDevice.Connect(callback, secretkey) 7 | This method attempts to connect with your device. The callback parameter will be called when the connection process has finished. **This doesn't mean that it's always successful!** 8 | The callback returns a `ConnectionResult` enum. This enum can be either `Succeeded`, or `Failed`. 9 | 10 | You can also give this method the secretkey of the device you're trying to connect with. If you don't give a key to the connect method, It will generate a new key. This key will be returned in the connection callback. It is important you save this key. The next time you want to connect to this device you'll need use this key with the connect method. This is needed to reconnect without losing data! 11 | 12 | Example: 13 | ``` 14 | BLEDevice device = //Retrieved from scanning 15 | device.Connect(OnConnectionFinished); 16 | ``` 17 | ```csharp 18 | //Our scanning callback method 19 | void OnDeviceFound(BLEScanResult result){ 20 | BLEDevice device = result.Device; 21 | //let's first stop scanning 22 | Windesheart.StopScanning(); 23 | //Then, connect to device 24 | device.Connect(OnConnectionFinished); 25 | } 26 | 27 | void OnConnectionFinished(ConnectionResult result, byte[] secretKey){ 28 | if (result == ConnectionResult.Succeeded) 29 | { 30 | Console.WriteLine("Successful Connection!"); 31 | SaveKeyToProperties(secretKey) 32 | } 33 | else 34 | { 35 | Console.WriteLine("Connection failed... :("); 36 | } 37 | } 38 | ``` 39 | 40 | ## BLEDevice.Disconnect(rememberDevice = true) 41 | This method disconnects the BLEDevice. There is an optional parameter called rememberDevice that defaults to true. 42 | 43 | WindesHeart always saves your currently connected device to the `Windesheart.ConnectedDevice` field. if rememberDevice is true, it won't clear this field after disconnecting. If false is passed, `Windesheart.ConnectedDevice` will be set to null. 44 | 45 | The disconnect method is a void. You can assume that it always works. 46 | 47 | [<---- Back to mainpage](https://github.com/ictinnovaties-zorg/openwindesheart/blob/master/) -------------------------------------------------------------------------------- /OpenWindesheart/Devices/MiBand3/Services/MiBand3HeartrateService.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using Plugin.BluetoothLE; 16 | using System; 17 | using System.Reactive.Linq; 18 | using OpenWindesheart.Devices.MiBand3Device.Models; 19 | using OpenWindesheart.Devices.MiBand3Device.Resources; 20 | using OpenWindesheart.Models; 21 | 22 | namespace OpenWindesheart.Devices.MiBand3Device.Services 23 | { 24 | public class MiBand3HeartrateService 25 | { 26 | private readonly MiBand3 _miBand3; 27 | public IDisposable RealtimeDisposible; 28 | 29 | public MiBand3HeartrateService(MiBand3 device) 30 | { 31 | _miBand3 = device; 32 | } 33 | 34 | /// 35 | /// Add a callback to run everytime the user manually measures their heartrate 36 | /// 37 | /// 38 | public void EnableRealTimeHeartrate(Action callback) 39 | { 40 | RealtimeDisposible?.Dispose(); 41 | RealtimeDisposible = _miBand3.GetCharacteristic(MiBand3Resource.GuidHeartrate).RegisterAndNotify().Subscribe( 42 | x => callback(new HeartrateData(x.Characteristic.Value)) 43 | ); 44 | } 45 | 46 | public void DisableRealTimeHeartrate() 47 | { 48 | RealtimeDisposible?.Dispose(); 49 | } 50 | 51 | /// 52 | /// Set the interval for automatic heartrate measurements 53 | /// 54 | /// 55 | public async void SetMeasurementInterval(int minutes) 56 | { 57 | var Char = _miBand3.GetCharacteristic(MiBand3Resource.GuidHeartRateControl); 58 | await Char.Write(new byte[] { 0x14, (byte)minutes }); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /OpenWindesheart/Devices/MiBand3/Helpers/MiBand3ConversionHelper.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using System; 16 | using System.Security.Cryptography; 17 | using OpenWindesheart.Helpers; 18 | 19 | namespace OpenWindesheart.Devices.MiBand3Device.Helpers 20 | { 21 | public static class MiBand3ConversionHelper 22 | { 23 | /// 24 | /// This generates a new secret key for authenticating a new device 25 | /// 26 | /// 27 | public static byte[] GenerateAuthKey() 28 | { 29 | Random random = new Random(); 30 | byte[] SecretKey = new byte[16]; 31 | for (var i = 0; i < 16; i++) 32 | { 33 | int keyNumber = random.Next(1, 255); 34 | SecretKey[i] = Convert.ToByte(keyNumber); 35 | } 36 | return SecretKey; 37 | } 38 | 39 | public static byte[] CreateKey(byte[] value, byte[] key) 40 | { 41 | byte[] bytes = { 0x03, 0x00 }; 42 | byte[] secretKey = key; 43 | 44 | value = ConversionHelper.CopyOfRange(value, 3, 19); 45 | byte[] buffer = EncryptBuff(secretKey, value); 46 | byte[] endBytes = new byte[18]; 47 | Buffer.BlockCopy(bytes, 0, endBytes, 0, 2); 48 | Buffer.BlockCopy(buffer, 0, endBytes, 2, 16); 49 | return endBytes; 50 | } 51 | 52 | public static byte[] EncryptBuff(byte[] sessionKey, byte[] buffer) 53 | { 54 | AesManaged myAes = new AesManaged(); 55 | 56 | myAes.Mode = CipherMode.ECB; 57 | myAes.Key = sessionKey; 58 | myAes.Padding = PaddingMode.None; 59 | 60 | ICryptoTransform encryptor = myAes.CreateEncryptor(); 61 | return encryptor.TransformFinalBlock(buffer, 0, buffer.Length); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /OpenWindesheart/Devices/MiBand3/Services/MiBand3StepsService.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using Plugin.BluetoothLE; 16 | using System; 17 | using System.Reactive.Linq; 18 | using System.Threading.Tasks; 19 | using OpenWindesheart.Devices.MiBand3Device.Models; 20 | using OpenWindesheart.Devices.MiBand3Device.Resources; 21 | using OpenWindesheart.Models; 22 | 23 | namespace OpenWindesheart.Devices.MiBand3Device.Services 24 | { 25 | public class MiBand3StepsService 26 | { 27 | private readonly MiBand3 _miBand3; 28 | public IDisposable realtimeDisposable; 29 | 30 | public MiBand3StepsService(MiBand3 device) 31 | { 32 | _miBand3 = device; 33 | } 34 | 35 | /// 36 | /// Add a callback to run everytime the Mi Band updates its step count 37 | /// 38 | /// 39 | public void EnableRealTimeSteps(Action callback) 40 | { 41 | realtimeDisposable?.Dispose(); 42 | realtimeDisposable = _miBand3.GetCharacteristic(MiBand3Resource.GuidStepsInfo).RegisterAndNotify().Subscribe( 43 | x => callback(new StepData(x.Characteristic.Value))); 44 | } 45 | 46 | /// 47 | /// Disables real time step count updates 48 | /// 49 | public void DisableRealTimeSteps() 50 | { 51 | realtimeDisposable?.Dispose(); 52 | } 53 | 54 | public async Task GetSteps() 55 | { 56 | if (_miBand3.IsAuthenticated()) 57 | { 58 | var steps = await _miBand3.GetCharacteristic(MiBand3Resource.GuidStepsInfo).Read(); 59 | return new StepData(steps.Characteristic.Value); 60 | } 61 | else 62 | { 63 | return new StepData(new byte[] { 0, 0, 0 }); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README-SupportNewDevice.md: -------------------------------------------------------------------------------- 1 | 2 | # How to add support for a new device 3 | 4 | This documentation offers a step by step guide on how to add support for a new device. We assume you have the WindesheartSDK project cloned and open in Visual Studio. 5 | 6 | ### 1. Add new device folder 7 | ![alt text](https://i.imgur.com/NBTx5ao.png) 8 | To start, you need a folder to put all your code in. In the Devices folder, create a new folder named after your device. Please follow the format as shown in the MiBand3 folder. Put your models inside a Models folder, Helpers in a Helpers folder, and so on. 9 | 10 | ### 2. Add your own Device class extending BLEDevice 11 | Inside your newly created folder, add a "Models" folder, then create a new class in there. 12 | This class should have the exact same name as your folder: 13 | ![alt text](https://i.imgur.com/TsCBYPR.png) 14 | 15 | 16 | The class also has to extend from BLEDevice. Because of this, you're forced to implement all abstract methods that class contains. 17 | 18 | ![Alt text](https://i.imgur.com/EfJguV1.png) 19 | In theory this class can do all the logic for your device, however it is **STRONGLY** recommended that you have different services that handle the implementation, as seen in MiBand3.cs: 20 | 21 | ![Alt text](https://i.imgur.com/AVkwYgb.png) 22 | 23 | A few things you need to have in this class: 24 | * Set BLEDevice's ConnectionCallback of BleDevice in your Connect method. 25 | * Set BLEDevice's DisconnectionCallback of BleDevice in your SubscribeToDisconnect method. 26 | 27 | As seen in the example below: 28 | 29 | ![Alt text](https://i.imgur.com/YBlvsiq.png) 30 | 31 | 32 | 33 | ## 3. Add your code 34 | Now its time to implement everything! To keep things organized and clean, please use different services for different tasks. Mi Band 3 is a good example for this. 35 | 36 | If your device doesn't support an action provided by the BLEDevice class, just throw a NotImplementedException. 37 | For example, if your device doesn't support sleep tracking, you do this: 38 | ![Alt text](https://i.imgur.com/PiEXzim.png) 39 | 40 | ## 4. Edit the GetDevice method 41 | The last step is to edit the GetDevice method inside of `BluetoothService.cs` 42 | This method returns the correct BLEDevice dependent of it's name. Please note that `var name` is the Bluetooth device name. There might be two or more names for one device (in case of the Mi Band 3). 43 | ![Alt text](https://i.imgur.com/wdEOTcZ.png) 44 | And that should be it! Now, when scanning for devices it should detect and return your device! Please make sure everything works as expected before submitting a pull request. 45 | 46 | [<---- Back to mainpage](https://github.com/ictinnovaties-zorg/openwindesheart/blob/master/) -------------------------------------------------------------------------------- /README-Settings.md: -------------------------------------------------------------------------------- 1 | # Settings 2 | 3 | Most devices have some settings that can be changed. The Mi Band 3 is not an exception and that is why we created a way of changing some basic settings for this device. 4 | 5 | ## How to use 6 | 7 | The following settings can be configurated to your liking for the Mi band 3, by using *Windesheart.PairedDevice* when already connected to the device: 8 | 9 | * **Display Language**: You can change your display language to any locale you wish, by calling the SetLanguage(string localeString) method with the preferred locale. 10 | **NOTE: We have not tested all locales that exist, but german(de-DE), dutch(nl-NL) and english(en-EN) definitely work. 11 | [A list of locales that could work can be found here.](https://stackoverflow.com/questions/3191664/list-of-all-locales-and-their-short-codes) 12 | 13 | * **Set the time-displayformat**: You can change the displayformat for time to 12-hour or 24-hour format. Call the SetTimeDisplayFormat(bool is24hours) to change this. 14 | 15 | * **Set the date-displayformat**: You can change the displayformat for dates to either dd-MM-YYYY or MM-dd-YYYY. Use the method SetDateDisplayFormat(bool isddMMYYYY) for this. 16 | 17 | * **Set Stepgoal**: You can set a goal for the amount of steps needed per day. By using the SetStepGoal(int steps) you can set the goal to your liking. You should also call EnableFitnessGoalNotification(bool enable) to actually receive the notification on the device when the goal has been reached. **NOTE: If the goal will be set to 1000 steps (with the notification on) and you already have more than 1000 steps displayed on your device, then this will trigger the next time you take a step.** 18 | 19 | * **Set Time**: You can change the time that will be displayed on your device. This can be done by calling SetTime(DateTime dateTime) with the datetime you want the time to be set to. **NOTE: Changing this could result in loss of samples and can unreliably change data that can be received from the device.** 20 | 21 | * **Activate display on wristlift**: If you want to turn on your display of your device, when lifting your wrist, then use the method SetActivateOnLiftWrist(bool activate). You can also use SetActivateOnLiftWrist(DateTime from, DateTime to) to activate this feature between the two dates. 22 | 23 | * **Enable sleep-tracking**: If you would like to create samples of your sleeping pattern, then this configuration has to be turned on! Use EnableSleepTracking(bool enable) to toggle the functionality. 24 | 25 | * **Set Heartrate-measurementinterval**: If you would like more accurate samples with heartrates, then you should turn on this function for the interval you want. Use SetHeartrateMeasurementInterval(int minutes) to set this interval. **NOTE: It is recommended to use SetHeartrateMeasurementInterval(1) to measure your heartrate automatically every minute. More samples will have accurate heartrate-data this way if the device is worn correctly. This does consume more battery, so be aware of this!** 26 | 27 | *It is recommended to set these features once when connecting to the device. This way you will not forget them.* 28 | 29 | 30 | [<---- Back to mainpage](https://github.com/ictinnovaties-zorg/openwindesheart/blob/master/) -------------------------------------------------------------------------------- /OpenWindesheart/Windesheart.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using OpenWindesheart.Models; 16 | using Plugin.BluetoothLE; 17 | using System; 18 | using System.Threading.Tasks; 19 | 20 | namespace OpenWindesheart 21 | { 22 | public static class Windesheart 23 | { 24 | public static BLEDevice PairedDevice; 25 | 26 | /// 27 | /// Scan for BLEDevices that are not yet connected. 28 | /// 29 | /// Throws exception when trying to start scan when a scan is already running. 30 | /// Called when a device is found 31 | /// List of IScanResult 32 | public static bool StartScanning(Action callback) 33 | { 34 | return BluetoothService.StartScanning(callback); 35 | } 36 | 37 | /// 38 | /// Stops scanning for devices 39 | /// 40 | public static void StopScanning() 41 | { 42 | BluetoothService.StopScanning(); 43 | } 44 | 45 | /// 46 | /// Get a BLEDevice based on the UUID 47 | /// 48 | /// Uuid of the BLEDevice 49 | /// 50 | public static async Task GetKnownDevice(Guid uuid) 51 | { 52 | return await BluetoothService.GetKnownDevice(uuid); 53 | } 54 | 55 | 56 | /// 57 | /// Calls the callback method when Bluetooth adapter state changes to ready 58 | /// 59 | /// Called when adapter is ready 60 | public static void WhenAdapterReady(Action callback) 61 | { 62 | BluetoothService.WhenAdapterReady(callback); 63 | } 64 | 65 | /// 66 | /// Calls the callback method when Bluetooth adapter status changes 67 | /// 68 | /// Called when status changed 69 | public static void OnAdapterChanged(Action callback) 70 | { 71 | BluetoothService.OnAdapterChanged(callback); 72 | } 73 | 74 | /// 75 | /// Return whether device is currently scanning for devices. 76 | /// 77 | public static bool IsScanning() 78 | { 79 | return BluetoothService.IsScanning(); 80 | } 81 | 82 | public static AdapterStatus AdapterStatus { get => CrossBleAdapter.Current.Status; } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /README-Samples.md: -------------------------------------------------------------------------------- 1 | # Reading Samples 2 | The Windesheart SDK allows reading samples from the past from BLE Devices. 3 | 4 | ## *Fetching Samples* 5 | 6 | By using our *MiBand3SamplesService* it is possible to fetch samples from the past days, weeks, months or even years. 7 | 8 | Samples are generated once *every minute* in your device. This means that fetching data for 1 day, 1440 samples will be fetched (24 hours * 60 minutes = 1440 samples). 9 | 10 | The data that is generated will be stored in a class called *ActivitySample*. This class contains the following data: 11 | 12 | * **RawData**: This is the byte-array containing the raw-data of the sample (for debugging purposes). 13 | * **TimeStamp**: The exact DateTime that this sample contains data for. 14 | * **UnixEpochTimeStamp**: The amount of seconds since UnixEpoch. This way time-zones can be countered. 15 | * **Category**: This is a number that tells you the sort of activity that has been measured in that minute. Walking, running, sleeping and other activities all have their separate number. We are unsure in what all the categories are, but some are obvious in our opinion. **NOTE: The accuracy of this number can vary. Sometimes the Mi band 3 thinks you are sleeping, when in reality you are sitting without moving.** 16 | * **RawIntensity**: This number can tell you whether your activity was intense or not. If you walk faster, the measured intensity will most likely be higher. 17 | * **Steps**: This is the number of steps that have been measured in that minute. If you use addition on all the step-numbers of the current day, then this should be exactly or close to your displayed step-number on your device. 18 | * **HeartRate**: The measured heartrate in that minute. **NOTE: This only gets measured if you wear your device properly and have set the heartratemeasurement-interval to 1 minute by using *MiBand3.SetHeartrateMeasurementInterval(1)*** 19 | 20 | A list of AcitivitySample can be acquired by calling *MiBand3SampleService.StartFetching(). You will have to provide the datetime of the starting date (Example: For a week of data you will call this method with DateTime.Now.AddDays(-7)). 21 | 22 | The callbacks are useful if you want to know when fetching has been finished and if you want to get a callback on every 250 samples for creating a progressbar in the mobile-app. 23 | 24 | Fetching can take a while, because of the speed of Bluetooth Low Energy. Storing data in a database and only fetching the data of the last synchronization will speed up the process alot. 25 | 26 | ## *Example* 27 | 28 | To get the list of samples of your device correctly, you will have to connect to the device first and then use this example: 29 | 30 | ```csharp 31 | //Fetching from a week ago until now 32 | DateTime startDate = DateTime.Now.AddDays(-7); 33 | Windesheart.PairedDevice.GetSamples(startDate, FetchingFinished, UpdateProgression); 34 | 35 | private void UpdateProgression(int remainingSamples) { 36 | /*Do something with the calculated remaining samples. 37 | For example: remainingSamples = 1440, 38 | means that this amount of samples is remaining 39 | until all samples have been fetched.*/ 40 | } 41 | 42 | private void FetchingFinished(List samples) { 43 | /*Do something with the samples that have been found*/ 44 | } 45 | ``` 46 | 47 | *The demo-application contains a working example of this in the directory 'Services' -> 'SamplesService.cs'* 48 | 49 | [<---- Back to mainpage](https://github.com/ictinnovaties-zorg/openwindesheart/blob/master/) -------------------------------------------------------------------------------- /OpenWindesheart/Devices/MiBand3/Resources/MiBand3Resource.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using System; 16 | namespace OpenWindesheart.Devices.MiBand3Device.Resources 17 | { 18 | public static class MiBand3Resource 19 | { 20 | //Authentication 21 | public static Guid GuidCharacteristicAuth = new Guid("00000009-0000-3512-2118-0009af100700"); 22 | 23 | public static byte AuthResponse = 0x10; 24 | public static byte AuthSendKey = 0x01; 25 | public static byte AuthRequestRandomAuthNumber = 0x02; 26 | public static byte AuthSendEncryptedAuthNumber = 0x03; 27 | public static byte AuthSuccess = 0x01; 28 | public static readonly byte[] RequestNumber = { 0x02, 0x00 }; 29 | 30 | //General Guid for device settings 31 | public static Guid GuidDeviceConfiguration = new Guid("00000003-0000-3512-2118-0009af100700"); 32 | 33 | public static byte[] Byte_EnableActivateOnLiftWrist = new byte[] { 0x06, 0x05, 0x00, 0x01 }; 34 | public static byte[] Byte_DisableActivateOnLiftWrist = new byte[] { 0x06, 0x05, 0x00, 0x00 }; 35 | public static byte[] Byte_ScheduleActivateOnLiftWrist_Template = new byte[] { 0x06, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 }; 36 | 37 | public static byte[] Byte_SetLanguage_Template = new byte[] { 0x06, 0x17, 0x00, 0, 0, 0, 0, 0 }; 38 | 39 | public static byte[] Byte_TimeFomat_24hours = new byte[] { 0x06, 0x02, 0x0, 0x1 }; 40 | public static byte[] Byte_TimeFomat_12hours = new byte[] { 0x06, 0x02, 0x0, 0x0 }; 41 | 42 | public static byte[] Byte_EnableGoalNotification = new byte[] { 0x06, 0x06, 0x00, 0x01}; 43 | public static byte[] Byte_DisableGoalNotification = new byte[] { 0x06, 0x06, 0x00, 0x00}; 44 | 45 | public static byte[] Byte_EnableSleepMeasurement = new byte[] { 0x15, 0x00, 0x01 }; 46 | public static byte[] Byte_DisableSleepMeasurement = new byte[] { 0x15, 0x00, 0x00 }; 47 | 48 | public static byte[] Byte_DateFormat_dd_MM_YYYY = new byte[] { 0x06, 30, 0x00, Convert.ToByte('d'), Convert.ToByte('d'), Convert.ToByte('/'), Convert.ToByte('M'), Convert.ToByte('M'), Convert.ToByte('/'), Convert.ToByte('y'), Convert.ToByte('y'), Convert.ToByte('y'), Convert.ToByte('y') }; 49 | public static byte[] Byte_DateFormat_MM_dd_YYYY = new byte[] { 0x06, 30, 0x00, Convert.ToByte('M'), Convert.ToByte('M'), Convert.ToByte('/'), Convert.ToByte('d'), Convert.ToByte('d'), Convert.ToByte('/'), Convert.ToByte('y'), Convert.ToByte('y'), Convert.ToByte('y'), Convert.ToByte('y') }; 50 | 51 | //User settings 52 | public static Guid GuidUserInfo = new Guid("00000008-0000-3512-2118-0009af100700"); 53 | 54 | //Battery Guid 55 | public static Guid GuidBatteryInfo = new Guid("00000006-0000-3512-2118-0009af100700"); 56 | 57 | //Current Time Guid 58 | public static Guid GuidCurrentTime = new Guid("00002A2B-0000-1000-8000-00805f9b34fb"); 59 | 60 | //Heartrate Control Point Guid 61 | public static Guid GuidHeartRateControl = new Guid("00002A39-0000-1000-8000-00805f9b34fb"); 62 | 63 | //Heartrate Realtime Guid 64 | public static Guid GuidHeartrate = new Guid("00002A37-0000-1000-8000-00805f9b34fb"); 65 | 66 | //Request & get samples 67 | public static Guid GuidSamplesRequest = new Guid("00000004-0000-3512-2118-0009af100700"); 68 | public static Guid GuidActivityData = new Guid("00000005-0000-3512-2118-0009af100700"); 69 | 70 | public static byte Response = 0x10; 71 | public static byte CommandActivityDataStartDate = 0x01; 72 | public static byte Success = 0x01; 73 | 74 | public static byte[] ResponseActivityDataStartDateSuccess = { Response, CommandActivityDataStartDate, Success }; 75 | 76 | //Steps Realtime Guid 77 | public static Guid GuidStepsInfo = new Guid("00000007-0000-3512-2118-0009af100700"); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /OpenWindesheart/Helpers/ConversionHelper.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using System; 16 | 17 | namespace OpenWindesheart.Helpers 18 | { 19 | public static class ConversionHelper 20 | { 21 | public enum TimeUnit { Seconds, Days, Hours, Minutes, Unknown = -1 } 22 | 23 | public static byte[] ShortDateTimeToRawBytes(DateTime dateTime) 24 | { 25 | byte[] year = FromUint16(dateTime.Year); 26 | return new[] { 27 | year[0], 28 | year[1], 29 | FromUint8(dateTime.Month), 30 | FromUint8(dateTime.Day), 31 | FromUint8(dateTime.Hour), 32 | FromUint8(dateTime.Minute) 33 | }; 34 | } 35 | 36 | public static byte[] DateTimeToRawBytes(DateTime dateTime) 37 | { 38 | byte[] year = FromUint16(dateTime.Year); 39 | return new[] { 40 | year[0], 41 | year[1], 42 | FromUint8(dateTime.Month), 43 | FromUint8(dateTime.Day), 44 | FromUint8(dateTime.Hour), 45 | FromUint8(dateTime.Minute), 46 | FromUint8(dateTime.Second), 47 | DayOfWeekToRawBytes(dateTime), 48 | (byte) 0 49 | }; 50 | } 51 | 52 | public static byte[] GetTimeBytes(DateTime dateTime, TimeUnit precision) 53 | { 54 | byte[] bytes; 55 | if (precision == TimeUnit.Minutes) 56 | { 57 | bytes = ShortDateTimeToRawBytes(dateTime); 58 | } 59 | else if (precision == TimeUnit.Seconds) 60 | { 61 | bytes = DateTimeToRawBytes(dateTime); 62 | } 63 | else 64 | { 65 | throw new ArgumentException(); 66 | } 67 | 68 | byte[] all = new byte[bytes.Length + 2]; 69 | Buffer.BlockCopy(bytes, 0, all, 0, bytes.Length); 70 | Buffer.BlockCopy(new byte[] { 0, 4 }, 0, all, bytes.Length, 2); 71 | 72 | return all; 73 | } 74 | 75 | private static byte DayOfWeekToRawBytes(DateTime dateTime) 76 | { 77 | int dayValue = (int)dateTime.DayOfWeek; 78 | if (dateTime.DayOfWeek == DayOfWeek.Saturday) 79 | { 80 | return 7; 81 | } 82 | return (byte)dayValue; 83 | } 84 | 85 | public static byte FromUint8(int value) 86 | { 87 | byte[] bytes = BitConverter.GetBytes(value); 88 | return bytes[0]; 89 | } 90 | 91 | public static byte[] FromUint16(int value) 92 | { 93 | return BitConverter.GetBytes((short)value); 94 | } 95 | 96 | public static int ToUint16(byte[] bytes) 97 | { 98 | return (bytes[1] << 8 | bytes[0]); 99 | } 100 | 101 | public static DateTime RawBytesToCalendar(byte[] value) 102 | { 103 | if (value.Length >= 7) 104 | { 105 | int year = ToUint16(new byte[] { value[0], value[1], 0, 0 }); 106 | DateTime timestamp = new DateTime( 107 | year, 108 | (value[2] & 0xff), 109 | value[3] & 0xff, 110 | value[4] & 0xff, 111 | value[5] & 0xff, 112 | value[6] & 0xff 113 | ); 114 | return timestamp; 115 | } 116 | return new DateTime(); 117 | } 118 | 119 | public static byte[] CopyOfRange(byte[] src, int start, int end) 120 | { 121 | int len = end - start; 122 | byte[] dest = new byte[len]; 123 | // note i is always from 0 124 | for (int i = 0; i < len; i++) 125 | { 126 | dest[i] = src[start + i]; // so 0..n = 0+x..n+x 127 | } 128 | return dest; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # OpenWindesheart 3 | 4 | The open-source OpenWindesheart is a library used for scanning, connecting and getting data from activity-trackers using bluetooth. 5 | 6 | ## Support 7 | 8 | This project has been created by students of Windesheim University of Applied Sciences and does not guarantee any support in the future. 9 | 10 | This library uses the [ACR Reactive BluetoothLE Plugin](https://github.com/aritchie/bluetoothle) to do anything BluetoothLE related. Our project only supports versions that are supported by this library, because our project depends heavily upon it. 11 | 12 | The features have been tested with Android/iOS phones in combination with the Mi Band 3/Xiaomi Band 3. 13 | 14 | ## Features 15 | 16 | * Scanning for BLE devices 17 | * Pairing with a BLE device 18 | * Connecting to BLE device 19 | * Reading current battery-data and status 20 | * Reading current step-data 21 | * Reading step-data from the past days 22 | * Reading heartrate after measurement. 23 | * Reading heartrate from the past days with measurement-intervals 24 | * Reading sleep-data from the past days 25 | * Setting step-goal on BLE device 26 | * Setting language on BLE device 27 | * Setting Date and time on BLE device 28 | * Setting Date-format and Hour-notation on BLE device 29 | * Toggle screen of BLE device on wrist-lift 30 | * Auto-reconnect after Bluetooth-adapter toggle 31 | * Auto-reconnect when within range of BLE device 32 | * Disconnect with BLE device 33 | 34 | ## Supported Devices 35 | 36 | At this moment the OpenWindesheart library only fully supports the Mi Band 3. 37 | 38 | The library is designed in a way that other devices can be added easily. If you want to add support for a device, please check out our documentation: [How to add support for a new device](https://github.com/ictinnovaties-zorg/openwindesheart/blob/master/README-SupportNewDevice.md) 39 | 40 | 41 | ### Mi Band 4 42 | If you want these features with a Mi Band 4, then you will have to get the authentication-key for your Mi Band here: 43 | https://www.freemyband.com/2019/08/mi-band-4-auth-key.html 44 | 45 | We have a branch for the Mi Band 4, called MiBand4, which allows these devices to show up in the list of scanned devices. 46 | 47 | To make your app work with our logic, please update line 13 of this file: https://github.com/ictinnovaties-zorg/openwindesheart/blob/MiBand4/WindesHeartSDK/Devices/MiBand4/Helpers/MiBand4ConversionHelper.cs 48 | 49 | This is where you fill in your own secret key. For example; the key '0a1bc2' should be altered to {0x0a, 0x1b, 0xc2} in the byte-array on line 13 of the code. 50 | 51 | After that, you will only have to build the SDK and the mobile-project. 52 | Once all is built, it should be ready for use with the Mi Band 4. 53 | 54 | DISCLAIMER: The source of www.freemyband.com is unofficial. Use this website at your own risk, we do not take responsibility for anything related to this source. 55 | 56 | ## Documentation 57 | 58 | #### User docs: 59 | * [Scanning](https://github.com/ictinnovaties-zorg/openwindesheart/blob/master/README-Scanning.md) 60 | * [Connecting & Disconnecting](https://github.com/ictinnovaties-zorg/openwindesheart/blob/master/README-Connecting.md) 61 | * [Reading data](https://github.com/ictinnovaties-zorg/openwindesheart/blob/master/README-Readingdata.md) 62 | * [Reading samples](https://github.com/ictinnovaties-zorg/openwindesheart/blob/master/README-Samples.md) 63 | * [Supported settings](https://github.com/ictinnovaties-zorg/openwindesheart/blob/master/README-Settings.md) 64 | 65 | #### Contributor docs: 66 | * [How to add support for a new device](https://github.com/ictinnovaties-zorg/openwindesheart/blob/master/README-SupportNewDevice.md) 67 | 68 | ## SETUP 69 | 1. Clone this repository into your solution and manage a dependency from your mobile-project to this one. 70 | 2. Carefully read the docs for implementation of different features. For a working example, have a look at the Xamarin Forms project on this page. 71 | 3. Implement the features in your mobile-application the way you want them! 72 | 73 | ## Credits 74 | 75 | We would like to thank Allan Ritchie for creating the [ACR Reactive BluetoothLE Plugin](https://github.com/aritchie/bluetoothle). Without this open-source library our project would not have been finished within the specified amount of time. 76 | 77 | ## Contributions 78 | 79 | To make contributions to this project, please open up a [pull request](https://github.com/ictinnovaties-zorg/openwindesheart/pull/new/master). 80 | 81 | ## Creators 82 | 83 | * R. Abächerli [@ramonb1996](https://github.com/ramonB1996) 84 | * H. van der Gugten [@hielkeg](https://github.com/hielkeg) 85 | * T.C. Marschalk [@marstc](https://github.com/marstc) 86 | * K. van Sloten [@kevinvansloten](https://github.com/kevinvansloten) 87 | 88 | ## Copyright 89 | 90 | Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences. 91 | -------------------------------------------------------------------------------- /OpenWindesheart/BLEDevice.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using OpenWindesheart.Models; 16 | using Plugin.BluetoothLE; 17 | using System; 18 | using System.Collections.Generic; 19 | using System.Threading.Tasks; 20 | 21 | namespace OpenWindesheart 22 | { 23 | public abstract class BLEDevice 24 | { 25 | public string Name { get => IDevice.Name; } 26 | public ConnectionStatus Status { get => IDevice.Status; } 27 | public Guid Uuid { get => IDevice.Uuid; } 28 | public List Characteristics = new List(); 29 | public readonly IDevice IDevice; 30 | protected readonly BluetoothService BluetoothService; 31 | public bool Authenticated = false; 32 | public bool NeedsAuthentication = false; 33 | public byte[] SecretKey; 34 | 35 | //Callbacks & Disposables 36 | public Action ConnectionCallback; 37 | internal Action DisconnectCallback; 38 | internal IDisposable ConnectionDisposable; 39 | internal IDisposable CharacteristicDisposable; 40 | 41 | public BLEDevice() 42 | { 43 | } 44 | 45 | public BLEDevice(IDevice device) 46 | { 47 | IDevice = device; 48 | BluetoothService = new BluetoothService(this); 49 | ConnectionDisposable = IDevice.WhenConnected().Subscribe(x => OnConnect()); 50 | } 51 | 52 | public bool IsConnected() 53 | { 54 | return IDevice.IsConnected(); 55 | } 56 | 57 | public bool IsDisconnected() 58 | { 59 | return IDevice.IsDisconnected(); 60 | } 61 | 62 | public bool IsAuthenticated() 63 | { 64 | return (IsConnected() && Authenticated); 65 | } 66 | 67 | public bool IsPairingAvailable() 68 | { 69 | return IDevice.IsPairingAvailable(); 70 | } 71 | 72 | public abstract void DisposeDisposables(); 73 | public abstract void OnConnect(); 74 | public abstract void SetStepGoal(int steps); 75 | public abstract void SubscribeToDisconnect(Action disconnectCallback); 76 | public abstract void Connect(Action connectCallback, byte[] secretKey = null); 77 | public abstract void Disconnect(bool rememberDevice = true); 78 | public abstract void EnableFitnessGoalNotification(bool enable); 79 | public abstract void SetTimeDisplayFormat(bool is24hours); 80 | public abstract void SetDateDisplayFormat(bool isddMMYYYY); 81 | public abstract void SetLanguage(string localeString); 82 | public abstract void SetTime(DateTime dateTime); 83 | 84 | public abstract void SetActivateOnLiftWrist(bool activate); 85 | public abstract void SetActivateOnLiftWrist(DateTime from, DateTime to); 86 | public abstract void EnableRealTimeSteps(Action OnStepsChanged); 87 | public abstract void DisableRealTimeSteps(); 88 | public abstract Task GetSteps(); 89 | public abstract Task GetBattery(); 90 | public abstract void EnableSleepTracking(bool enable); 91 | public abstract void GetSamples(DateTime startDate, Action> finishedCallback, Action remainingSamplesCallback); 92 | 93 | /// 94 | /// Get a certain characteristic with its UUID. 95 | /// 96 | /// 97 | /// IGattCharacteristic 98 | public IGattCharacteristic GetCharacteristic(Guid uuid) 99 | { 100 | return Characteristics.Find(x => x.Uuid == uuid); 101 | } 102 | 103 | public abstract void EnableRealTimeBattery(Action getBatteryStatus); 104 | public abstract void DisableRealTimeBattery(); 105 | public abstract void EnableRealTimeHeartrate(Action getHeartrate); 106 | public abstract void DisableRealTimeHeartrate(); 107 | public abstract void SetHeartrateMeasurementInterval(int minutes); 108 | } 109 | 110 | public enum ConnectionResult { Failed, Succeeded } 111 | } 112 | -------------------------------------------------------------------------------- /OpenWindesheart/Devices/MiBand3/Services/MiBand3BatteryService.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using Plugin.BluetoothLE; 16 | using System; 17 | using System.Reactive.Linq; 18 | using System.Threading.Tasks; 19 | using OpenWindesheart.Devices.MiBand3Device.Models; 20 | using OpenWindesheart.Devices.MiBand3Device.Resources; 21 | using OpenWindesheart.Models; 22 | 23 | namespace OpenWindesheart.Devices.MiBand3Device.Services 24 | { 25 | public class MiBand3BatteryService 26 | { 27 | private readonly MiBand3 _miBand3; 28 | public IDisposable RealTimeDisposible; 29 | 30 | public MiBand3BatteryService(MiBand3 device) 31 | { 32 | _miBand3 = device; 33 | } 34 | 35 | /// 36 | /// Get Raw Battery data. 37 | /// 38 | /// Throws exception if BatteryCharacteristic or its value is null. 39 | /// byte[] 40 | public async Task GetRawBatteryData() 41 | { 42 | var batteryCharacteristic = GetBatteryCharacteristic(); 43 | if (batteryCharacteristic != null) 44 | { 45 | var gattResult = await batteryCharacteristic.Read(); 46 | 47 | if (gattResult.Characteristic.Value != null) 48 | { 49 | var rawData = gattResult.Characteristic.Value; 50 | return rawData; 51 | } 52 | throw new NullReferenceException("BatteryCharacteristic value is null!"); 53 | } 54 | throw new NullReferenceException("BatteryCharacteristic could not be found!"); 55 | } 56 | 57 | /// 58 | /// Get Battery-object from raw data. 59 | /// 60 | /// Throws exception if rawData is null. 61 | /// Battery 62 | public async Task GetCurrentBatteryData() 63 | { 64 | var rawData = await GetRawBatteryData(); 65 | if (rawData != null) 66 | { 67 | return CreateBatteryObject(rawData); 68 | } 69 | throw new NullReferenceException("Rawdata is null!"); 70 | } 71 | 72 | /// 73 | /// Creates Battery-object from rawData. 74 | /// 75 | /// 76 | /// Throws exception if rawData is null. 77 | /// Battery 78 | private BatteryData CreateBatteryObject(byte[] rawData) 79 | { 80 | if (rawData != null) 81 | { 82 | var batteryPercentage = rawData[1]; 83 | BatteryStatus status = BatteryStatus.NotCharging; 84 | 85 | if (rawData[2] == 1) 86 | { 87 | status = BatteryStatus.Charging; 88 | } 89 | 90 | var battery = new BatteryData 91 | { 92 | RawData = rawData, 93 | Percentage = batteryPercentage, 94 | Status = status 95 | }; 96 | return battery; 97 | } 98 | throw new NullReferenceException("Rawdata of battery is null!"); 99 | } 100 | 101 | /// 102 | /// Receive BatteryStatus-updates continuously. 103 | /// 104 | public void EnableRealTimeBattery(Action callback) 105 | { 106 | RealTimeDisposible?.Dispose(); 107 | RealTimeDisposible = _miBand3.GetCharacteristic(MiBand3Resource.GuidBatteryInfo).RegisterAndNotify().Subscribe( 108 | x => callback(CreateBatteryObject(x.Characteristic.Value)) 109 | ); 110 | } 111 | 112 | public void DisableRealTimeBattery() 113 | { 114 | RealTimeDisposible?.Dispose(); 115 | } 116 | 117 | /// 118 | /// Get Battery Characteristic 119 | /// 120 | /// IGattCharacteristic 121 | private IGattCharacteristic GetBatteryCharacteristic() 122 | { 123 | return _miBand3.GetCharacteristic(MiBand3Resource.GuidBatteryInfo); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /OpenWindesheart/Devices/MiBand3/Services/MiBand3AuthenticationService.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using Plugin.BluetoothLE; 16 | using System; 17 | using System.Diagnostics; 18 | using System.Reactive.Linq; 19 | using System.Threading.Tasks; 20 | using OpenWindesheart.Devices.MiBand3Device.Helpers; 21 | using OpenWindesheart.Devices.MiBand3Device.Models; 22 | using OpenWindesheart.Devices.MiBand3Device.Resources; 23 | using OpenWindesheart.Exceptions; 24 | 25 | namespace OpenWindesheart.Devices.MiBand3Device.Services 26 | { 27 | public class MiBand3AuthenticationService 28 | { 29 | private static IGattCharacteristic _authCharacteristic; 30 | private readonly MiBand3 _miBand3; 31 | public IDisposable AuthenticationDisposable; 32 | 33 | public MiBand3AuthenticationService(MiBand3 device) 34 | { 35 | _miBand3 = device; 36 | } 37 | 38 | /// 39 | /// Authenticates Mi Band 3 devices 40 | /// 41 | /// Throws exception if AuthCharacteristic could not be found. 42 | /// Throws exception if authentication went wrong. 43 | public async Task Authenticate() 44 | { 45 | _authCharacteristic = _miBand3.GetCharacteristic(MiBand3Resource.GuidCharacteristicAuth); 46 | if (_authCharacteristic != null) 47 | { 48 | //Fired when Mi Band 3 is tapped 49 | AuthenticationDisposable?.Dispose(); 50 | AuthenticationDisposable = _authCharacteristic.RegisterAndNotify().Subscribe(async result => 51 | { 52 | var data = result.Data; 53 | if (data == null) 54 | { 55 | _miBand3.ConnectionCallback(ConnectionResult.Failed, null); 56 | throw new NullReferenceException("No data found in authentication-result."); 57 | } 58 | 59 | //Check if response is valid 60 | if (data[0] == MiBand3Resource.AuthResponse && data[2] == MiBand3Resource.AuthSuccess) 61 | { 62 | if (data[1] == MiBand3Resource.AuthSendKey) 63 | { 64 | await RequestAuthorizationNumber(); 65 | } 66 | else if (data[1] == MiBand3Resource.AuthRequestRandomAuthNumber) 67 | { 68 | await RequestRandomEncryptionKey(data); 69 | } 70 | else if (data[1] == MiBand3Resource.AuthSendEncryptedAuthNumber) 71 | { 72 | Trace.WriteLine("Authenticated & Connected!"); 73 | _miBand3.Authenticated = true; 74 | _miBand3.ConnectionCallback(ConnectionResult.Succeeded, _miBand3.SecretKey); 75 | AuthenticationDisposable.Dispose(); 76 | return; 77 | } 78 | } 79 | else 80 | { 81 | _miBand3.Authenticated = false; 82 | _miBand3.ConnectionCallback(ConnectionResult.Failed, null); 83 | _miBand3.Disconnect(); 84 | } 85 | }, 86 | exception => 87 | { 88 | _miBand3.ConnectionCallback(ConnectionResult.Failed, null); 89 | throw new ConnectionException(exception.Message); 90 | }); 91 | 92 | if (_miBand3.SecretKey == null) 93 | { 94 | //Triggers vibration on device 95 | await TriggerAuthentication(); 96 | } 97 | else 98 | { 99 | //Continues session with authorization-number 100 | await RequestAuthorizationNumber(); 101 | } 102 | } 103 | else 104 | { 105 | _miBand3.ConnectionCallback(ConnectionResult.Failed, null); 106 | throw new NullReferenceException("AuthCharacteristic is null!"); 107 | } 108 | } 109 | 110 | private async Task TriggerAuthentication() 111 | { 112 | Trace.WriteLine("Writing authentication-key.."); 113 | byte[] KeyBytes = new byte[18]; 114 | byte[] AuthKey = MiBand3ConversionHelper.GenerateAuthKey(); // Key needs to be saved somewhere (BLEDevice or Something) 115 | _miBand3.SecretKey = AuthKey; 116 | KeyBytes[0] = 0x01; 117 | KeyBytes[1] = 0x00; 118 | Buffer.BlockCopy(AuthKey, 0, KeyBytes, 2, 16); 119 | 120 | await _authCharacteristic.WriteWithoutResponse(KeyBytes); 121 | } 122 | 123 | private async Task RequestAuthorizationNumber() 124 | { 125 | Trace.WriteLine("1.Requesting Authorization-number"); 126 | await _authCharacteristic.WriteWithoutResponse(MiBand3Resource.RequestNumber); 127 | } 128 | 129 | private async Task RequestRandomEncryptionKey(byte[] data) 130 | { 131 | Trace.WriteLine("2.Requesting random encryption key"); 132 | await _authCharacteristic.WriteWithoutResponse(MiBand3ConversionHelper.CreateKey(data, _miBand3.SecretKey)); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /OpenWindesheart/Devices/MiBand3/Services/MiBand3ConfigurationService.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using System; 16 | using System.Reactive.Linq; 17 | using System.Text; 18 | using OpenWindesheart.Devices.MiBand3Device.Models; 19 | using OpenWindesheart.Devices.MiBand3Device.Resources; 20 | 21 | namespace OpenWindesheart.Devices.MiBand3Device.Services 22 | { 23 | public class MiBand3ConfigurationService 24 | { 25 | private readonly BLEDevice _miBand3; 26 | 27 | public MiBand3ConfigurationService(MiBand3 device) 28 | { 29 | _miBand3 = device; 30 | } 31 | 32 | /// 33 | /// Sets the language of the Mi Band to the given language. String has to be in format en-EN. If language is not supported nothing will happen 34 | /// 35 | /// 36 | public async void SetLanguage(string localeString) 37 | { 38 | byte[] LanguageBytes = MiBand3Resource.Byte_SetLanguage_Template; 39 | 40 | Buffer.BlockCopy(Encoding.ASCII.GetBytes(localeString), 0, LanguageBytes, 3, Encoding.ASCII.GetBytes(localeString).Length); 41 | 42 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidDeviceConfiguration).WriteWithoutResponse(LanguageBytes); 43 | } 44 | 45 | /// 46 | /// Set the Mi Bands time unit to either 24 hours when true or 12 hours when false 47 | /// 48 | /// 49 | public async void SetTimeDisplayUnit(bool is24format) 50 | { 51 | if (is24format) 52 | { 53 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidDeviceConfiguration).WriteWithoutResponse(MiBand3Resource.Byte_TimeFomat_24hours); 54 | } 55 | else 56 | { 57 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidDeviceConfiguration).WriteWithoutResponse(MiBand3Resource.Byte_TimeFomat_12hours); 58 | } 59 | } 60 | 61 | /// 62 | /// Set the Mi Bands Date format to either dd/MM/YYYY if true or MM/dd/YYYY if false 63 | /// 64 | /// 65 | public async void SetDateDisplayUnit(bool isdMY) 66 | { 67 | if (isdMY) 68 | { 69 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidDeviceConfiguration).WriteWithoutResponse(MiBand3Resource.Byte_DateFormat_dd_MM_YYYY); 70 | } 71 | else 72 | { 73 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidDeviceConfiguration).WriteWithoutResponse(MiBand3Resource.Byte_DateFormat_MM_dd_YYYY); 74 | } 75 | } 76 | 77 | /// 78 | /// Set the step target on the Mi Band. Max value of 2 bytes (around 65.000) 79 | /// 80 | /// 81 | public async void SetStepGoal(int goal) 82 | { 83 | var beginCommand = new byte[] { 0x10, 0x0, 0x0 }; 84 | var endCommand = new byte[] { 0, 0 }; 85 | var goalBytes = BitConverter.GetBytes((ushort) goal); 86 | 87 | byte[] CommandBytes = new byte[beginCommand.Length + endCommand.Length + goalBytes.Length]; 88 | 89 | Buffer.BlockCopy(beginCommand, 0, CommandBytes, 0, beginCommand.Length); 90 | Buffer.BlockCopy(goalBytes, 0, CommandBytes, beginCommand.Length, goalBytes.Length); 91 | Buffer.BlockCopy(endCommand, 0, CommandBytes, beginCommand.Length + goalBytes.Length, endCommand.Length); 92 | 93 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidUserInfo).Write(CommandBytes); 94 | } 95 | 96 | public async void EnableStepGoalNotification(bool enable) 97 | { 98 | if (enable) 99 | { 100 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidDeviceConfiguration).WriteWithoutResponse(MiBand3Resource.Byte_EnableGoalNotification); 101 | } 102 | else 103 | { 104 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidDeviceConfiguration).WriteWithoutResponse(MiBand3Resource.Byte_DisableGoalNotification); 105 | } 106 | } 107 | 108 | /// 109 | /// Set permanent activate on wrist lift. true for enable. false for disable 110 | /// 111 | /// 112 | public async void SetActivateOnWristLift(bool activate) 113 | { 114 | if (activate) 115 | { 116 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidDeviceConfiguration).WriteWithoutResponse(MiBand3Resource.Byte_EnableActivateOnLiftWrist); 117 | } 118 | else 119 | { 120 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidDeviceConfiguration).WriteWithoutResponse(MiBand3Resource.Byte_DisableActivateOnLiftWrist); 121 | } 122 | } 123 | 124 | /// 125 | /// Set the activate display on lift wrist only to be active between the two given times. Date's dont matter 126 | /// 127 | /// 128 | /// 129 | public async void SetActivateOnWristLift(DateTime from, DateTime to) 130 | { 131 | byte[] CommandByte = MiBand3Resource.Byte_ScheduleActivateOnLiftWrist_Template; 132 | 133 | CommandByte[4] = Convert.ToByte(from.Hour); 134 | CommandByte[5] = Convert.ToByte(from.Minute); 135 | 136 | CommandByte[6] = Convert.ToByte(to.Hour); 137 | CommandByte[7] = Convert.ToByte(to.Minute); 138 | 139 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidDeviceConfiguration).WriteWithoutResponse(CommandByte); 140 | } 141 | 142 | /// 143 | /// Enable or Disable the sleep measurement of the devices 144 | /// 145 | /// 146 | public async void EnableSleepTracking(bool enable) 147 | { 148 | if (enable) 149 | { 150 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidDeviceConfiguration).WriteWithoutResponse(MiBand3Resource.Byte_EnableSleepMeasurement); 151 | } 152 | else 153 | { 154 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidDeviceConfiguration).WriteWithoutResponse(MiBand3Resource.Byte_DisableSleepMeasurement); 155 | 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /OpenWindesheart/Devices/MiBand3/Models/MiBand3.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using Plugin.BluetoothLE; 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Threading.Tasks; 19 | using OpenWindesheart.Devices.MiBand3Device.Resources; 20 | using OpenWindesheart.Devices.MiBand3Device.Services; 21 | using OpenWindesheart.Models; 22 | 23 | namespace OpenWindesheart.Devices.MiBand3Device.Models 24 | { 25 | public class MiBand3 : BLEDevice 26 | { 27 | //MiBand 3 Services 28 | private readonly MiBand3BatteryService _batteryService; 29 | private readonly MiBand3HeartrateService _heartrateService; 30 | private readonly MiBand3DateTimeService _dateTimeService; 31 | private readonly MiBand3StepsService _stepsService; 32 | private readonly MiBand3AuthenticationService _authenticationService; 33 | private readonly MiBand3SampleService _fetchService; 34 | private readonly MiBand3ConfigurationService _configurationService; 35 | 36 | public MiBand3(IDevice device) : base(device) 37 | { 38 | _batteryService = new MiBand3BatteryService(this); 39 | _heartrateService = new MiBand3HeartrateService(this); 40 | _dateTimeService = new MiBand3DateTimeService(this); 41 | _authenticationService = new MiBand3AuthenticationService(this); 42 | _fetchService = new MiBand3SampleService(this); 43 | _stepsService = new MiBand3StepsService(this); 44 | _configurationService = new MiBand3ConfigurationService(this); 45 | } 46 | 47 | public MiBand3() : base() 48 | { 49 | 50 | } 51 | 52 | public override void Connect(Action connectCallback, byte[] secretKey = null) 53 | { 54 | ConnectionCallback = connectCallback; 55 | SecretKey = secretKey; 56 | BluetoothService.Connect(); 57 | } 58 | 59 | public override void SubscribeToDisconnect(Action disconnectCallback) 60 | { 61 | DisconnectCallback = disconnectCallback; 62 | IDevice.WhenDisconnected().Subscribe(observer => DisconnectCallback(observer)); 63 | } 64 | 65 | public override void Disconnect(bool rememberDevice = true) 66 | { 67 | BluetoothService.Disconnect(rememberDevice); 68 | } 69 | 70 | public override void SetTimeDisplayFormat(bool is24Hours) 71 | { 72 | _configurationService.SetTimeDisplayUnit(is24Hours); 73 | } 74 | 75 | public override void SetStepGoal(int steps) 76 | { 77 | _configurationService.SetStepGoal(steps); 78 | } 79 | 80 | public override void EnableFitnessGoalNotification(bool enable) 81 | { 82 | _configurationService.EnableStepGoalNotification(enable); 83 | } 84 | 85 | public override void SetDateDisplayFormat(bool isddMMYYYY) 86 | { 87 | _configurationService.SetDateDisplayUnit(isddMMYYYY); 88 | } 89 | 90 | public override void DisposeDisposables() 91 | { 92 | _authenticationService.AuthenticationDisposable?.Dispose(); 93 | _stepsService.realtimeDisposable?.Dispose(); 94 | _heartrateService.RealtimeDisposible?.Dispose(); 95 | _batteryService.RealTimeDisposible?.Dispose(); 96 | ConnectionDisposable?.Dispose(); 97 | CharacteristicDisposable?.Dispose(); 98 | } 99 | 100 | public override void SetLanguage(string localeString) 101 | { 102 | _configurationService.SetLanguage(localeString); 103 | } 104 | 105 | public override void SetActivateOnLiftWrist(bool activate) 106 | { 107 | _configurationService.SetActivateOnWristLift(activate); 108 | } 109 | 110 | public override void SetActivateOnLiftWrist(DateTime from, DateTime to) 111 | { 112 | _configurationService.SetActivateOnWristLift(from, to); 113 | } 114 | 115 | public override void EnableRealTimeBattery(Action getBatteryStatus) 116 | { 117 | _batteryService.EnableRealTimeBattery(getBatteryStatus); 118 | } 119 | 120 | public override Task GetBattery() 121 | { 122 | return _batteryService.GetCurrentBatteryData(); 123 | } 124 | 125 | public override void EnableRealTimeHeartrate(Action getHeartrate) 126 | { 127 | _heartrateService.EnableRealTimeHeartrate(getHeartrate); 128 | } 129 | 130 | public override void SetHeartrateMeasurementInterval(int minutes) 131 | { 132 | _heartrateService.SetMeasurementInterval(minutes); 133 | } 134 | 135 | public override Task GetSteps() 136 | { 137 | return _stepsService.GetSteps(); 138 | } 139 | 140 | public override void DisableRealTimeSteps() 141 | { 142 | _stepsService.DisableRealTimeSteps(); 143 | } 144 | 145 | public override void EnableRealTimeSteps(Action onStepsChanged) 146 | { 147 | _stepsService.EnableRealTimeSteps(onStepsChanged); 148 | } 149 | 150 | public override void SetTime(DateTime dateTime) 151 | { 152 | _dateTimeService.SetTime(dateTime); 153 | } 154 | 155 | public override void GetSamples(DateTime startDate, Action> finishedCallback, Action remainingSamplesCallback) 156 | { 157 | _fetchService.StartFetching(startDate, finishedCallback, remainingSamplesCallback); 158 | } 159 | 160 | public override void EnableSleepTracking(bool enable) 161 | { 162 | _configurationService.EnableSleepTracking(enable); 163 | } 164 | 165 | public override void OnConnect() 166 | { 167 | Console.WriteLine("Device Connected!"); 168 | 169 | Windesheart.PairedDevice = this; 170 | 171 | //Check if bluetooth-state changes to off and then on, to enable reconnection management 172 | BluetoothService.StartListeningForAdapterChanges(); 173 | 174 | Characteristics?.Clear(); 175 | 176 | CharacteristicDisposable?.Dispose(); 177 | //Find unique characteristics 178 | CharacteristicDisposable = IDevice.WhenAnyCharacteristicDiscovered().Subscribe(async characteristic => 179 | { 180 | if (characteristic != null && !Characteristics.Contains(characteristic)) 181 | { 182 | Characteristics.Add(characteristic); 183 | 184 | //Check if authCharacteristic has been found, then authenticate 185 | if (characteristic.Uuid == MiBand3Resource.GuidCharacteristicAuth) 186 | { 187 | //Check if this is a new connection that needs authentication 188 | await _authenticationService.Authenticate(); 189 | } 190 | } 191 | }); 192 | } 193 | 194 | public override void DisableRealTimeBattery() 195 | { 196 | _batteryService.DisableRealTimeBattery(); 197 | } 198 | 199 | public override void DisableRealTimeHeartrate() 200 | { 201 | _heartrateService.DisableRealTimeHeartrate(); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /OpenWindesheart/Services/BluetoothService.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using OpenWindesheart.Devices.MiBand3Device.Models; 16 | using OpenWindesheart.Models; 17 | using Plugin.BluetoothLE; 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Reactive.Linq; 21 | using System.Threading.Tasks; 22 | 23 | namespace OpenWindesheart 24 | { 25 | public class BluetoothService 26 | { 27 | public static AdapterStatus AdapterStatus; 28 | private readonly BLEDevice _bleDevice; 29 | 30 | //Disposables 31 | private static IDisposable _adapterReadyDisposable; 32 | private static IDisposable _currentScan; 33 | private static IDisposable _adapterChangedDisposable; 34 | 35 | public BluetoothService(BLEDevice device) 36 | { 37 | _bleDevice = device; 38 | } 39 | 40 | /// 41 | /// Stops scanning for devices 42 | /// 43 | public static void StopScanning() 44 | { 45 | _currentScan?.Dispose(); 46 | } 47 | 48 | /// 49 | /// Scan for devices that are not yet connected. 50 | /// 51 | /// Throws exception when trying to start scan when a scan is already running. 52 | /// 53 | /// Bool wheter scanning has started 54 | public static bool StartScanning(Action callback) 55 | { 56 | var uniqueGuids = new List(); 57 | 58 | //Start scanning when adapter is powered on. 59 | if (CrossBleAdapter.Current.Status == AdapterStatus.PoweredOn) 60 | { 61 | //Trigger event and add to devices list 62 | Console.WriteLine("Started scanning"); 63 | _currentScan = CrossBleAdapter.Current.Scan().Subscribe(result => 64 | { 65 | if (result.Device != null && !string.IsNullOrEmpty(result.Device.Name) && !uniqueGuids.Contains(result.Device.Uuid)) 66 | { 67 | //Set device 68 | BLEDevice device = GetDevice(result.Device); 69 | BLEScanResult scanResult = new BLEScanResult(device, result.Rssi, result.AdvertisementData); 70 | if (device != null) 71 | { 72 | device.NeedsAuthentication = true; 73 | callback(scanResult); 74 | } 75 | uniqueGuids.Add(result.Device.Uuid); 76 | } 77 | }); 78 | return true; 79 | } 80 | return false; 81 | } 82 | 83 | /// 84 | /// Calls the callback method when Bluetooth adapter state changes to ready 85 | /// 86 | /// Called when adapter is ready 87 | public static void WhenAdapterReady(Action callback) 88 | { 89 | _adapterReadyDisposable?.Dispose(); 90 | _adapterReadyDisposable = CrossBleAdapter.Current.WhenReady().Subscribe(adapter => callback()); 91 | } 92 | 93 | /// 94 | /// Return whether device is currently scanning for devices. 95 | /// 96 | public static bool IsScanning() 97 | { 98 | return CrossBleAdapter.Current.IsScanning; 99 | } 100 | 101 | /// 102 | /// Calls the callback method when Bluetooth adapter status changes 103 | /// 104 | /// Called when status changed 105 | public static void OnAdapterChanged(Action callback) 106 | { 107 | _adapterChangedDisposable?.Dispose(); 108 | _adapterChangedDisposable = CrossBleAdapter.Current.WhenStatusChanged().Subscribe(adapter => callback()); 109 | } 110 | 111 | /// 112 | /// Connect current device 113 | /// 114 | public void Connect() 115 | { 116 | Console.WriteLine("Connecting started..."); 117 | 118 | //Connect 119 | _bleDevice.IDevice.Connect(new ConnectionConfig 120 | { 121 | AutoConnect = true, 122 | AndroidConnectionPriority = ConnectionPriority.High 123 | }); 124 | } 125 | 126 | /// 127 | /// Gets a device based on its uuid 128 | /// 129 | /// Uuid of device to find 130 | /// The device of the uuid 131 | public static async Task GetKnownDevice(Guid uuid) 132 | { 133 | if (uuid != Guid.Empty) 134 | { 135 | var knownDevice = await CrossBleAdapter.Current.GetKnownDevice(uuid); 136 | if (knownDevice != null) 137 | { 138 | var bleDevice = GetDevice(knownDevice); 139 | bleDevice.NeedsAuthentication = false; 140 | return bleDevice; 141 | } 142 | } 143 | return null; 144 | } 145 | 146 | /// 147 | /// Disconnect current device. 148 | /// 149 | public void Disconnect(bool rememberDevice = true) 150 | { 151 | //Cancel the connection 152 | Console.WriteLine("Disconnecting device.."); 153 | if (Windesheart.PairedDevice != null) 154 | { 155 | Windesheart.PairedDevice.Authenticated = false; 156 | Windesheart.PairedDevice.DisposeDisposables(); 157 | _bleDevice.IDevice.CancelConnection(); 158 | if (!rememberDevice) 159 | { 160 | Windesheart.PairedDevice = null; 161 | } 162 | } 163 | } 164 | 165 | /// 166 | /// Enables logging of device status on change. 167 | /// 168 | public static void StartListeningForAdapterChanges() 169 | { 170 | bool startListening = false; 171 | _adapterReadyDisposable?.Dispose(); 172 | _adapterReadyDisposable = CrossBleAdapter.Current.WhenStatusChanged().Subscribe(async status => 173 | { 174 | if (status != AdapterStatus) 175 | { 176 | AdapterStatus = status; 177 | if (status == AdapterStatus.PoweredOff && Windesheart.PairedDevice != null) 178 | { 179 | Windesheart.PairedDevice?.Disconnect(); 180 | } 181 | 182 | if (status == AdapterStatus.PoweredOn && Windesheart.PairedDevice != null && startListening) 183 | { 184 | var tempConnectCallback = Windesheart.PairedDevice.ConnectionCallback; 185 | var DisconnectCallback = Windesheart.PairedDevice.DisconnectCallback; 186 | var secretKey = Windesheart.PairedDevice.SecretKey; 187 | var device = await GetKnownDevice(Windesheart.PairedDevice.IDevice.Uuid); 188 | 189 | if (DisconnectCallback != null) 190 | { 191 | device?.SubscribeToDisconnect(DisconnectCallback); 192 | } 193 | device?.Connect(tempConnectCallback, secretKey); 194 | } 195 | startListening = true; 196 | } 197 | }); 198 | } 199 | 200 | 201 | /// 202 | /// Returns the right BLEDevice based on the ScanResult 203 | /// 204 | private static BLEDevice GetDevice(IDevice device) 205 | { 206 | var name = device.Name; 207 | switch (name) 208 | { 209 | case "Mi Band 3": 210 | case "Xiaomi Mi Band 3": 211 | return new MiBand3(device); 212 | 213 | //Create additional cases for other devices. 214 | } 215 | return null; 216 | } 217 | } 218 | } 219 | 220 | 221 | -------------------------------------------------------------------------------- /OpenWindesheart/Devices/MiBand3/Services/MiBand3SampleService.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Research group ICT innovations in Health Care, Windesheim University of Applied Sciences 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | using Plugin.BluetoothLE; 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Diagnostics; 19 | using System.Linq; 20 | using System.Reactive.Linq; 21 | using System.Threading.Tasks; 22 | using OpenWindesheart.Devices.MiBand3Device.Models; 23 | using OpenWindesheart.Devices.MiBand3Device.Resources; 24 | using OpenWindesheart.Models; 25 | using static OpenWindesheart.Helpers.ConversionHelper; 26 | 27 | namespace OpenWindesheart.Devices.MiBand3Device.Services 28 | { 29 | public class MiBand3SampleService 30 | { 31 | private readonly MiBand3 _miBand3; 32 | private readonly List _samples = new List(); 33 | 34 | //Timestamps 35 | private DateTime _firstTimestamp; 36 | private DateTime _lastTimestamp; 37 | 38 | //Disposables 39 | private IDisposable _charUnknownSub; 40 | private IDisposable _charActivitySub; 41 | 42 | //Callbacks 43 | private Action> _finishedCallback; 44 | private Action _remainingSamplesCallback; 45 | 46 | //Samplecounters 47 | private int _samplenumber = 0; 48 | private int _expectedSamples; 49 | private int _totalSamples; 50 | 51 | public MiBand3SampleService(MiBand3 device) 52 | { 53 | _miBand3 = device; 54 | } 55 | 56 | /// 57 | /// Clear the list of samples and start fetching 58 | /// 59 | public async void StartFetching(DateTime date, Action> finishedCallback, Action remainingSamplesCallback) 60 | { 61 | _samples.Clear(); 62 | _expectedSamples = 0; 63 | _totalSamples = 0; 64 | _finishedCallback = finishedCallback; 65 | _remainingSamplesCallback = remainingSamplesCallback; 66 | await InitiateFetching(date); 67 | } 68 | 69 | private void CalculateExpectedSamples(DateTime startDate) 70 | { 71 | TimeSpan timespan = DateTime.Now - startDate; 72 | _totalSamples = (int)timespan.TotalMinutes; 73 | } 74 | 75 | /// 76 | /// Setup the disposables for the fetch operation 77 | /// 78 | /// 79 | private async Task InitiateFetching(DateTime date) 80 | { 81 | _samplenumber = 0; 82 | //Dispose all DIsposables to prevent double data 83 | _charActivitySub?.Dispose(); 84 | _charUnknownSub?.Dispose(); 85 | 86 | // Subscribe to the unknown and activity characteristics 87 | _charUnknownSub = _miBand3.GetCharacteristic(MiBand3Resource.GuidSamplesRequest).RegisterAndNotify().Subscribe(HandleUnknownChar); 88 | _charActivitySub = _miBand3.GetCharacteristic(MiBand3Resource.GuidActivityData).RegisterAndNotify().Subscribe(HandleActivityChar); 89 | 90 | // Write the date and time from which to receive samples to the Mi Band 91 | await WriteDateBytes(date); 92 | } 93 | 94 | /// 95 | /// Write the date from wich to recieve data to the mi band 96 | /// 97 | /// 98 | /// 99 | private async Task WriteDateBytes(DateTime date) 100 | { 101 | // Convert date to bytes 102 | byte[] Timebytes = GetTimeBytes(date, TimeUnit.Minutes); 103 | byte[] Fetchbytes = new byte[10] { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }; 104 | 105 | // Copy the date in the byte template to send to the device 106 | Buffer.BlockCopy(Timebytes, 0, Fetchbytes, 2, 8); 107 | 108 | // Send the bytes to the device 109 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidSamplesRequest).WriteWithoutResponse(Fetchbytes); 110 | } 111 | 112 | /// 113 | /// Called when recieving MetaData 114 | /// 115 | /// 116 | private async void HandleUnknownChar(CharacteristicGattResult result) 117 | { 118 | //Correct response 119 | if (result.Data.Length >= 3) 120 | { 121 | // Create an empty byte array and copy the response type to it 122 | byte[] responseByte = new byte[3]; 123 | Buffer.BlockCopy(result.Data, 0, responseByte, 0, 3); 124 | 125 | // Start or Continue fetching process 126 | if (responseByte.SequenceEqual(new byte[3] { 0x10, 0x01, 0x01 })) 127 | { 128 | await HandleResponse(result.Data); 129 | return; 130 | } 131 | 132 | // Finished fetching 133 | if (responseByte.SequenceEqual(new byte[3] { 0x10, 0x02, 0x01 })) 134 | { 135 | if (_lastTimestamp >= DateTime.Now.AddMinutes(-1)) 136 | { 137 | _finishedCallback(_samples); 138 | _charActivitySub?.Dispose(); 139 | _charUnknownSub?.Dispose(); 140 | return; 141 | } 142 | 143 | await InitiateFetching(_lastTimestamp.AddMinutes(1)); 144 | return; 145 | } 146 | } 147 | 148 | Trace.WriteLine("No more samples could be fetched, samples returned: " + _samples.Count); 149 | _finishedCallback(_samples); 150 | _charActivitySub?.Dispose(); 151 | _charUnknownSub?.Dispose(); 152 | return; 153 | } 154 | 155 | /// 156 | /// Called when recieving samples 157 | /// 158 | /// 159 | private void HandleActivityChar(CharacteristicGattResult result) 160 | { 161 | // Each sample is made up from 4 bytes. The first byte represents the status. 162 | if (result.Data.Length % 4 != 1) 163 | { 164 | InitiateFetching(_lastTimestamp.AddMinutes(1)); 165 | } 166 | else 167 | { 168 | CreateSamplesFromResponse(result.Data); 169 | } 170 | } 171 | 172 | private async Task HandleResponse(byte[] data) 173 | { 174 | if (data.Length > 6) 175 | { 176 | //Start fetching 177 | try 178 | { 179 | // Get the timestamp of the first sample 180 | byte[] DateTimeBytes = new byte[8]; 181 | Buffer.BlockCopy(data, 7, DateTimeBytes, 0, 8); 182 | _firstTimestamp = RawBytesToCalendar(DateTimeBytes); 183 | 184 | // Write 0x02 to tell the band to start the fetching process 185 | await _miBand3.GetCharacteristic(MiBand3Resource.GuidSamplesRequest).WriteWithoutResponse(new byte[] { 0x02 }); 186 | } 187 | catch (Exception e) 188 | { 189 | Trace.WriteLine("Could not start fetching: " + e); 190 | } 191 | } 192 | else 193 | { 194 | //Continue fetching if more expected 195 | try 196 | { 197 | _expectedSamples = data[5] << 16 | data[4] << 8 | data[3]; 198 | if (_expectedSamples == 0) 199 | { 200 | _finishedCallback(_samples); 201 | } 202 | } 203 | catch (Exception e) 204 | { 205 | Trace.WriteLine("Could not calculate expected samples: " + e); 206 | } 207 | } 208 | } 209 | 210 | private void CreateSamplesFromResponse(byte[] data) 211 | { 212 | var samplecount = _samplenumber; 213 | _samplenumber++; 214 | var i = 1; 215 | while (i < data.Length) 216 | { 217 | int timeIndex = (samplecount) * 4 + (i - 1) / 4; 218 | var timeStamp = _firstTimestamp.AddMinutes(timeIndex); 219 | _lastTimestamp = timeStamp; 220 | 221 | // Create a sample from the received bytes 222 | byte[] rawdata = new byte[] { data[i], data[i + 1], data[i + 2], data[i + 3] }; 223 | var category = data[i] & 0xff; 224 | var intensity = data[i + 1] & 0xff; 225 | var steps = data[i + 2] & 0xff; 226 | var heartrate = data[i + 3]; 227 | 228 | // Add the sample to the sample list 229 | _samples.Add(new ActivitySample(timeStamp, category, intensity, steps, heartrate, rawdata)); 230 | 231 | //Callback for progress 232 | if (_samples.Count % 250 == 0) 233 | { 234 | CalculateExpectedSamples(timeStamp); 235 | _remainingSamplesCallback(_totalSamples); 236 | } 237 | i += 4; 238 | 239 | // Make sure we aren't getting samples from the future 240 | var d = DateTime.Now.AddMinutes(-1); 241 | d.AddSeconds(-d.Second); 242 | d.AddMilliseconds(-d.Millisecond); 243 | 244 | if (timeStamp == d) 245 | { 246 | break; 247 | } 248 | } 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/bower,intellij,visualstudio,xamarinstudio,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=bower,intellij,visualstudio,xamarinstudio,visualstudiocode 4 | 5 | ### Bower ### 6 | bower_components 7 | .bower-cache 8 | .bower-registry 9 | .bower-tmp 10 | 11 | ### Intellij ### 12 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 13 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 14 | 15 | # User-specific stuff 16 | .idea/**/workspace.xml 17 | .idea/**/tasks.xml 18 | .idea/**/usage.statistics.xml 19 | .idea/**/dictionaries 20 | .idea/**/shelf 21 | 22 | # Generated files 23 | .idea/**/contentModel.xml 24 | 25 | # Sensitive or high-churn files 26 | .idea/**/dataSources/ 27 | .idea/**/dataSources.ids 28 | .idea/**/dataSources.local.xml 29 | .idea/**/sqlDataSources.xml 30 | .idea/**/dynamic.xml 31 | .idea/**/uiDesigner.xml 32 | .idea/**/dbnavigator.xml 33 | 34 | # Gradle 35 | .idea/**/gradle.xml 36 | .idea/**/libraries 37 | 38 | # Gradle and Maven with auto-import 39 | # When using Gradle or Maven with auto-import, you should exclude module files, 40 | # since they will be recreated, and may cause churn. Uncomment if using 41 | # auto-import. 42 | # .idea/modules.xml 43 | # .idea/*.iml 44 | # .idea/modules 45 | 46 | # CMake 47 | cmake-build-*/ 48 | 49 | # Mongo Explorer plugin 50 | .idea/**/mongoSettings.xml 51 | 52 | # File-based project format 53 | *.iws 54 | 55 | # IntelliJ 56 | out/ 57 | 58 | # mpeltonen/sbt-idea plugin 59 | .idea_modules/ 60 | 61 | # JIRA plugin 62 | atlassian-ide-plugin.xml 63 | 64 | # Cursive Clojure plugin 65 | .idea/replstate.xml 66 | 67 | # Crashlytics plugin (for Android Studio and IntelliJ) 68 | com_crashlytics_export_strings.xml 69 | crashlytics.properties 70 | crashlytics-build.properties 71 | fabric.properties 72 | 73 | # Editor-based Rest Client 74 | .idea/httpRequests 75 | 76 | # Android studio 3.1+ serialized cache file 77 | .idea/caches/build_file_checksums.ser 78 | 79 | ### Intellij Patch ### 80 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 81 | 82 | # *.iml 83 | # modules.xml 84 | # .idea/misc.xml 85 | # *.ipr 86 | 87 | # Sonarlint plugin 88 | .idea/sonarlint 89 | 90 | ### VisualStudioCode ### 91 | .vscode/* 92 | !.vscode/settings.json 93 | !.vscode/tasks.json 94 | !.vscode/launch.json 95 | !.vscode/extensions.json 96 | 97 | ### VisualStudioCode Patch ### 98 | # Ignore all local history of files 99 | .history 100 | 101 | ### XamarinStudio ### 102 | bin/ 103 | obj/ 104 | *.userprefs 105 | .packages 106 | 107 | ### VisualStudio ### 108 | ## Ignore Visual Studio temporary files, build results, and 109 | ## files generated by popular Visual Studio add-ons. 110 | ## 111 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 112 | 113 | # User-specific files 114 | *.rsuser 115 | *.suo 116 | *.user 117 | *.userosscache 118 | *.sln.docstates 119 | 120 | # User-specific files (MonoDevelop/Xamarin Studio) 121 | 122 | # Build results 123 | [Dd]ebug/ 124 | [Dd]ebugPublic/ 125 | [Rr]elease/ 126 | [Rr]eleases/ 127 | x64/ 128 | x86/ 129 | [Aa][Rr][Mm]/ 130 | [Aa][Rr][Mm]64/ 131 | bld/ 132 | [Bb]in/ 133 | [Oo]bj/ 134 | [Ll]og/ 135 | 136 | # Visual Studio 2015/2017 cache/options directory 137 | .vs/ 138 | # Uncomment if you have tasks that create the project's static files in wwwroot 139 | #wwwroot/ 140 | 141 | # Visual Studio 2017 auto generated files 142 | Generated\ Files/ 143 | 144 | # MSTest test Results 145 | [Tt]est[Rr]esult*/ 146 | [Bb]uild[Ll]og.* 147 | 148 | # NUNIT 149 | *.VisualState.xml 150 | TestResult.xml 151 | 152 | # Build Results of an ATL Project 153 | [Dd]ebugPS/ 154 | [Rr]eleasePS/ 155 | dlldata.c 156 | 157 | # Benchmark Results 158 | BenchmarkDotNet.Artifacts/ 159 | 160 | # .NET Core 161 | project.lock.json 162 | project.fragment.lock.json 163 | artifacts/ 164 | 165 | # StyleCop 166 | StyleCopReport.xml 167 | 168 | # Files built by Visual Studio 169 | *_i.c 170 | *_p.c 171 | *_h.h 172 | *.ilk 173 | *.meta 174 | *.obj 175 | *.iobj 176 | *.pch 177 | *.pdb 178 | *.ipdb 179 | *.pgc 180 | *.pgd 181 | *.rsp 182 | *.sbr 183 | *.tlb 184 | *.tli 185 | *.tlh 186 | *.tmp 187 | *.tmp_proj 188 | *_wpftmp.csproj 189 | *.log 190 | *.vspscc 191 | *.vssscc 192 | .builds 193 | *.pidb 194 | *.svclog 195 | *.scc 196 | 197 | # Chutzpah Test files 198 | _Chutzpah* 199 | 200 | # Visual C++ cache files 201 | ipch/ 202 | *.aps 203 | *.ncb 204 | *.opendb 205 | *.opensdf 206 | *.sdf 207 | *.cachefile 208 | *.VC.db 209 | *.VC.VC.opendb 210 | 211 | # Visual Studio profiler 212 | *.psess 213 | *.vsp 214 | *.vspx 215 | *.sap 216 | 217 | # Visual Studio Trace Files 218 | *.e2e 219 | 220 | # TFS 2012 Local Workspace 221 | $tf/ 222 | 223 | # Guidance Automation Toolkit 224 | *.gpState 225 | 226 | # ReSharper is a .NET coding add-in 227 | _ReSharper*/ 228 | *.[Rr]e[Ss]harper 229 | *.DotSettings.user 230 | 231 | # JustCode is a .NET coding add-in 232 | .JustCode 233 | 234 | # TeamCity is a build add-in 235 | _TeamCity* 236 | 237 | # DotCover is a Code Coverage Tool 238 | *.dotCover 239 | 240 | # AxoCover is a Code Coverage Tool 241 | .axoCover/* 242 | !.axoCover/settings.json 243 | 244 | # Visual Studio code coverage results 245 | *.coverage 246 | *.coveragexml 247 | 248 | # NCrunch 249 | _NCrunch_* 250 | .*crunch*.local.xml 251 | nCrunchTemp_* 252 | 253 | # MightyMoose 254 | *.mm.* 255 | AutoTest.Net/ 256 | 257 | # Web workbench (sass) 258 | .sass-cache/ 259 | 260 | # Installshield output folder 261 | [Ee]xpress/ 262 | 263 | # DocProject is a documentation generator add-in 264 | DocProject/buildhelp/ 265 | DocProject/Help/*.HxT 266 | DocProject/Help/*.HxC 267 | DocProject/Help/*.hhc 268 | DocProject/Help/*.hhk 269 | DocProject/Help/*.hhp 270 | DocProject/Help/Html2 271 | DocProject/Help/html 272 | 273 | # Click-Once directory 274 | publish/ 275 | 276 | # Publish Web Output 277 | *.[Pp]ublish.xml 278 | *.azurePubxml 279 | # Note: Comment the next line if you want to checkin your web deploy settings, 280 | # but database connection strings (with potential passwords) will be unencrypted 281 | *.pubxml 282 | *.publishproj 283 | 284 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 285 | # checkin your Azure Web App publish settings, but sensitive information contained 286 | # in these scripts will be unencrypted 287 | PublishScripts/ 288 | 289 | # NuGet Packages 290 | *.nupkg 291 | # The packages folder can be ignored because of Package Restore 292 | **/[Pp]ackages/* 293 | # except build/, which is used as an MSBuild target. 294 | !**/[Pp]ackages/build/ 295 | # Uncomment if necessary however generally it will be regenerated when needed 296 | #!**/[Pp]ackages/repositories.config 297 | # NuGet v3's project.json files produces more ignorable files 298 | *.nuget.props 299 | *.nuget.targets 300 | 301 | # Microsoft Azure Build Output 302 | csx/ 303 | *.build.csdef 304 | 305 | # Microsoft Azure Emulator 306 | ecf/ 307 | rcf/ 308 | 309 | # Windows Store app package directories and files 310 | AppPackages/ 311 | BundleArtifacts/ 312 | Package.StoreAssociation.xml 313 | _pkginfo.txt 314 | *.appx 315 | 316 | # Visual Studio cache files 317 | # files ending in .cache can be ignored 318 | *.[Cc]ache 319 | # but keep track of directories ending in .cache 320 | !?*.[Cc]ache/ 321 | 322 | # Others 323 | ClientBin/ 324 | ~$* 325 | *~ 326 | *.dbmdl 327 | *.dbproj.schemaview 328 | *.jfm 329 | *.pfx 330 | *.publishsettings 331 | orleans.codegen.cs 332 | 333 | # Including strong name files can present a security risk 334 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 335 | #*.snk 336 | 337 | # Since there are multiple workflows, uncomment next line to ignore bower_components 338 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 339 | #bower_components/ 340 | # ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true 341 | **/wwwroot/lib/ 342 | 343 | # RIA/Silverlight projects 344 | Generated_Code/ 345 | 346 | # Backup & report files from converting an old project file 347 | # to a newer Visual Studio version. Backup files are not needed, 348 | # because we have git ;-) 349 | _UpgradeReport_Files/ 350 | Backup*/ 351 | UpgradeLog*.XML 352 | UpgradeLog*.htm 353 | ServiceFabricBackup/ 354 | *.rptproj.bak 355 | 356 | # SQL Server files 357 | *.mdf 358 | *.ldf 359 | *.ndf 360 | 361 | # Business Intelligence projects 362 | *.rdl.data 363 | *.bim.layout 364 | *.bim_*.settings 365 | *.rptproj.rsuser 366 | *- Backup*.rdl 367 | 368 | # Microsoft Fakes 369 | FakesAssemblies/ 370 | 371 | # GhostDoc plugin setting file 372 | *.GhostDoc.xml 373 | 374 | # Node.js Tools for Visual Studio 375 | .ntvs_analysis.dat 376 | node_modules/ 377 | 378 | # Visual Studio 6 build log 379 | *.plg 380 | 381 | # Visual Studio 6 workspace options file 382 | *.opt 383 | 384 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 385 | *.vbw 386 | 387 | # Visual Studio LightSwitch build output 388 | **/*.HTMLClient/GeneratedArtifacts 389 | **/*.DesktopClient/GeneratedArtifacts 390 | **/*.DesktopClient/ModelManifest.xml 391 | **/*.Server/GeneratedArtifacts 392 | **/*.Server/ModelManifest.xml 393 | _Pvt_Extensions 394 | 395 | # Paket dependency manager 396 | .paket/paket.exe 397 | paket-files/ 398 | 399 | # FAKE - F# Make 400 | .fake/ 401 | 402 | # JetBrains Rider 403 | .idea/ 404 | *.sln.iml 405 | 406 | # CodeRush personal settings 407 | .cr/personal 408 | 409 | # Python Tools for Visual Studio (PTVS) 410 | __pycache__/ 411 | *.pyc 412 | 413 | # Cake - Uncomment if you are using it 414 | # tools/** 415 | # !tools/packages.config 416 | 417 | # Tabs Studio 418 | *.tss 419 | 420 | # Telerik's JustMock configuration file 421 | *.jmconfig 422 | 423 | # BizTalk build output 424 | *.btp.cs 425 | *.btm.cs 426 | *.odx.cs 427 | *.xsd.cs 428 | 429 | # OpenCover UI analysis results 430 | OpenCover/ 431 | 432 | # Azure Stream Analytics local run output 433 | ASALocalRun/ 434 | 435 | # MSBuild Binary and Structured Log 436 | *.binlog 437 | 438 | # NVidia Nsight GPU debugger configuration file 439 | *.nvuser 440 | 441 | # MFractors (Xamarin productivity tool) working folder 442 | .mfractor/ 443 | 444 | # Local History for Visual Studio 445 | .localhistory/ 446 | 447 | # BeatPulse healthcheck temp database 448 | healthchecksdb 449 | 450 | # Mac sucks 451 | **/.DS_Store 452 | 453 | # End of https://www.gitignore.io/api/bower,intellij,visualstudio,xamarinstudio,visualstudiocode 454 | 455 | # Custom Exceptions 456 | !WindesHeartSdk/WindesHeartSdk/*.nupkg -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS --------------------------------------------------------------------------------