BLE (Heart Rate) readings to a file

FilipeSantos

Member
Joined
May 27, 2019
Messages
12
Programming Experience
1-3
Hello,
Am new here!
Need help on building a windows app to read a Polar H10 HR measure band.
Am starting by using this code; Bluetooth Generic Attribute Profile - Heart Rate Service.zip
Sample app reads HR and presents values to a window as bar chart and listbox.
I need to export the readings to a TXT file or even a virtual com port.
Anyways, not even a text file creation/appending am being successful :D
My knowledge about C# is very low...
My code below. Can anyone help out?
All and any ideas would be greatly appreciated.
Thank you!

async void Instance_ValueChangeCompleted:
private async void Instance_ValueChangeCompleted(HeartRateMeasurement heartRateMeasurementValue)

        {

            // Serialize UI update to the the main UI thread.

            await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>

            {

                statusTextBlock.Text = "Latest received heart rate measurement: " +

                    heartRateMeasurementValue.HeartRateValue;


#if WINDOWS_APP

                outputDataChart.PlotChart(HeartRateService.Instance.DataPoints);

#endif


                outputListBox.Items.Insert(0, heartRateMeasurementValue);



                string path = @"C:\Users\eliseu.santos\Documents\file.txt";


                // convert string to stream

                byte[] byteArray = Encoding.UTF8.GetBytes(path);

                MemoryStream stream = new MemoryStream(byteArray);


                using (TextWriter tw = new StreamWriter(stream))

                {

                    tw.WriteLine("The next line!");

                    tw.WriteLine(heartRateMeasurementValue.HeartRateValue);

                    //tw.Dispose();

                }


            });

        }
 
Last edited:
Hello again,

My latest tries...
Console Writing:
private void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs)
        {
            var HRm = new BluetoothGattHeartRate.HeartRateMeasurement();
            var HRmeasurement = HRm.HeartRateValue;

            if (eventArgs.Advertisement.LocalName == "Polar H10 1B80EF25")
            {
                // Tell the user we see an advertisement and print some properties
                Console.WriteLine(String.Format("Advertisement:"));
                Console.WriteLine(String.Format("  BT_ADDR: {0}", eventArgs.BluetoothAddress));
                Console.WriteLine(String.Format("  FR_NAME: {0}", eventArgs.Advertisement.LocalName));
                Console.WriteLine(String.Format("  TEST: {0}", HRmeasurement));
                Console.WriteLine();
            }
        }

HRmeasurment variable comes from code in attached file (Project added file);
Readings are always 0 from HRmeasurements...
Any ideas?

Thanks.
Filipe Santos

. Untitled.png
 

Attachments

  • HeartRateService.zip
    3.3 KB · Views: 90
Unless the constructor call on line 3 of that snippet does more than just construct an empty object, why would you expect any value other than 0? In other words, do you know if that constructor actually reads from the Bluetooth data stream?
 
Yes Skydiver, understood your point.
My knowledge about C# is killing me... :D
That Heart Rate Services file have 2 public classes, but the one I think would give me HR values; "HeartRateService", cannot be called from Program. Error says it is protected, and I dont have a clue why.
Can you give a hand using that file from my Program, and manage to see HR values in the console?
Thank you.

Filipe Santos.
 
What is the exact error? Might also be helpful if you'd post the relative code where the values are said to be read from the heart rate measurements -- coming in.

Please post your code in code tags, not as attachments, as people are less likely to bother downloading them. I've included them for you.
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage.Streams;

using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.Devices.Enumeration.Pnp;

namespace BluetoothGattHeartRate
{
    public class HeartRateMeasurement
    {
        public ushort HeartRateValue { get; set; }
        public bool HasExpendedEnergy { get; set; }
        public ushort ExpendedEnergy { get; set; }
        public DateTimeOffset Timestamp { get; set; }

        public override string ToString()
        {
            return HeartRateValue.ToString() + " bpm @ " + Timestamp.ToString();
        }
    }

    public delegate void ValueChangeCompletedHandler(HeartRateMeasurement heartRateMeasurementValue);

    public delegate void DeviceConnectionUpdatedHandler(bool isConnected);

    public class HeartRateService
    {
        // Heart Rate Constants

        // The Characteristic we want to obtain measurements for is the Heart Rate Measurement characteristic
        private Guid CHARACTERISTIC_UUID = GattCharacteristicUuids.HeartRateMeasurement;
        // Heart Rate devices typically have only one Heart Rate Measurement characteristic.
        // Make sure to check your device's documentation to find out how many characteristics your specific device has.
        private const int CHARACTERISTIC_INDEX = 0;
        // The Heart Rate Profile specification requires that the Heart Rate Measurement characteristic is notifiable.
        private const GattClientCharacteristicConfigurationDescriptorValue CHARACTERISTIC_NOTIFICATION_TYPE =
            GattClientCharacteristicConfigurationDescriptorValue.Notify;

        // A pointer back to the main page.  This is needed if you want to call methods in MainPage such
        // as NotifyUser().
        //MainPage rootPage = MainPage.Current;

        private static readonly HeartRateService instance = new HeartRateService();
        private GattDeviceService service;
        private GattCharacteristic characteristic;
        private List<HeartRateMeasurement> datapoints;
        private PnpObjectWatcher watcher;
        private String deviceContainerId;

        public event ValueChangeCompletedHandler ValueChangeCompleted;
        public event DeviceConnectionUpdatedHandler DeviceConnectionUpdated;

        public static HeartRateService Instance
        {
            get { return instance; }
        }

        public bool IsServiceInitialized { get; set; }

        public GattDeviceService Service
        {
            get { return service; }
        }

        public HeartRateMeasurement[] DataPoints
        {
            get
            {
                HeartRateMeasurement[] retval;
                lock (datapoints)
                {
                    retval = datapoints.ToArray();
                }

                return retval;
            }
        }

        private HeartRateService()
        {
            datapoints = new List<HeartRateMeasurement>();
            //App.Current.Suspending += App_Suspending;
            //App.Current.Resuming += App_Resuming;
        }

        private void App_Resuming(object sender, object e)
        {
            // Since the Windows Runtime will close resources to the device when the app is suspended,
            // the device needs to be reinitialized when the app is resumed.
        }

        private void App_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e)
        {
            IsServiceInitialized = false;

            // This is an appropriate place to save to persistent storage any datapoint the application cares about.
            // For the purpose of this sample we just discard any values.
            datapoints.Clear();

            // Allow the GattDeviceService to get cleaned up by the Windows Runtime.
            // The Windows runtime will clean up resources used by the GattDeviceService object when the application is
            // suspended. The GattDeviceService object will be invalid once the app resumes, which is why it must be
            // marked as invalid, and reinitalized when the application resumes.
            if (service != null)
            {
                service.Dispose();
                service = null;
            }

            if (characteristic != null)
            {
                characteristic = null;
            }

            if (watcher != null)
            {
                watcher.Stop();
                watcher = null;
            }
        }

        public async Task InitializeServiceAsync(DeviceInformation device)
        {
            try
            {
                deviceContainerId = "{" + device.Properties["System.Devices.ContainerId"] + "}";

                service = await GattDeviceService.FromIdAsync(device.Id);
                if (service != null)
                {
                    IsServiceInitialized = true;
                    await ConfigureServiceForNotificationsAsync();
                }
            }
            catch (Exception e)
            {
            }
        }
 
C#:
        /// <summary>

        /// Configure the Bluetooth device to send notifications whenever the Characteristic value changes

        /// </summary>

        private async Task ConfigureServiceForNotificationsAsync()

        {

            try

            {

                // Obtain the characteristic for which notifications are to be received

                characteristic = service.GetCharacteristics(CHARACTERISTIC_UUID)[CHARACTERISTIC_INDEX];



                // While encryption is not required by all devices, if encryption is supported by the device,

                // it can be enabled by setting the ProtectionLevel property of the Characteristic object.

                // All subsequent operations on the characteristic will work over an encrypted link.

                characteristic.ProtectionLevel = GattProtectionLevel.EncryptionRequired;



                // Register the event handler for receiving notifications

                characteristic.ValueChanged += Characteristic_ValueChanged;



                // In order to avoid unnecessary communication with the device, determine if the device is already

                // correctly configured to send notifications.

                // By default ReadClientCharacteristicConfigurationDescriptorAsync will attempt to get the current

                // value from the system cache and communication with the device is not typically required.

                var currentDescriptorValue = await characteristic.ReadClientCharacteristicConfigurationDescriptorAsync();



                if ((currentDescriptorValue.Status != GattCommunicationStatus.Success) ||

                    (currentDescriptorValue.ClientCharacteristicConfigurationDescriptor != CHARACTERISTIC_NOTIFICATION_TYPE))

                {

                    // Set the Client Characteristic Configuration Descriptor to enable the device to send notifications

                    // when the Characteristic value changes

                    GattCommunicationStatus status =

                        await characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(

                        CHARACTERISTIC_NOTIFICATION_TYPE);



                    if (status == GattCommunicationStatus.Unreachable)

                    {

                        // Register a PnpObjectWatcher to detect when a connection to the device is established,

                        // such that the application can retry device configuration.

                        StartDeviceConnectionWatcher();

                    }

                }

            }

            catch (Exception e)

            {

            }

        }



        /// <summary>

        /// Register to be notified when a connection is established to the Bluetooth device

        /// </summary>

        private void StartDeviceConnectionWatcher()

        {

            watcher = PnpObject.CreateWatcher(PnpObjectType.DeviceContainer,

                new string[] { "System.Devices.Connected" }, String.Empty);



            watcher.Updated += DeviceConnection_Updated;

            watcher.Start();

        }



        /// <summary>

        /// Invoked when a connection is established to the Bluetooth device

        /// </summary>

        /// <param name="sender">The watcher object that sent the notification</param>

        /// <param name="args">The updated device object properties</param>

        private async void DeviceConnection_Updated(PnpObjectWatcher sender, PnpObjectUpdate args)

        {

            var connectedProperty = args.Properties["System.Devices.Connected"];

            bool isConnected = false;

            if ((deviceContainerId == args.Id) && Boolean.TryParse(connectedProperty.ToString(), out isConnected) &&

                isConnected)

            {

                var status = await characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(

                    CHARACTERISTIC_NOTIFICATION_TYPE);



                if (status == GattCommunicationStatus.Success)

                {

                    IsServiceInitialized = true;



                    // Once the Client Characteristic Configuration Descriptor is set, the watcher is no longer required

                    watcher.Stop();

                    watcher = null;

                }



                // Notifying subscribers of connection state updates

                if (DeviceConnectionUpdated != null)

                {

                    DeviceConnectionUpdated(isConnected);

                }

            }

        }



        /// <summary>

        /// Invoked when Windows receives data from your Bluetooth device.

        /// </summary>

        /// <param name="sender">The GattCharacteristic object whose value is received.</param>

        /// <param name="args">The new characteristic value sent by the device.</param>

        private void Characteristic_ValueChanged(

            GattCharacteristic sender,

            GattValueChangedEventArgs args)

        {

            var data = new byte[args.CharacteristicValue.Length];



            DataReader.FromBuffer(args.CharacteristicValue).ReadBytes(data);



            // Process the raw data received from the device.

            var value = ProcessData(data);

            value.Timestamp = args.Timestamp;



            lock (datapoints)

            {

                datapoints.Add(value);

            }



            if (ValueChangeCompleted != null)

            {

                ValueChangeCompleted(value);

            }

        }



        /// <summary>

        /// Process the raw data received from the device into application usable data,

        /// according the the Bluetooth Heart Rate Profile.

        /// </summary>

        /// <param name="data">Raw data received from the heart rate monitor.</param>

        /// <returns>The heart rate measurement value.</returns>

        private HeartRateMeasurement ProcessData(byte[] data)

        {

            // Heart Rate profile defined flag values

            const byte HEART_RATE_VALUE_FORMAT = 0x01;

            const byte ENERGY_EXPANDED_STATUS = 0x08;



            byte currentOffset = 0;

            byte flags = data[currentOffset];

            bool isHeartRateValueSizeLong = ((flags & HEART_RATE_VALUE_FORMAT) != 0);

            bool hasEnergyExpended = ((flags & ENERGY_EXPANDED_STATUS) != 0);



            currentOffset++;



            ushort heartRateMeasurementValue = 0;

            if (isHeartRateValueSizeLong)

            {

                heartRateMeasurementValue = (ushort)((data[currentOffset + 1] << 8) + data[currentOffset]);

                currentOffset += 2;

            }

            else

            {

                heartRateMeasurementValue = data[currentOffset];

                currentOffset++;

            }



            // The Heart Rate Bluetooth profile can also contain sensor contact status information,

            // and R-Wave interval measurements, which can also be processed here.

            // For the purpose of this sample, we don't need to interpret that data.



            return new HeartRateMeasurement

            {

                HeartRateValue = heartRateMeasurementValue,

            };

        }

    }

}
Due to the 1000 posting restriction I had to split the file.
 
Can you give a hand using that file from my Program, and manage to see HR values in the console?
Since I don't have such a device to test with, and I'm too lazy to read the specs that class of Bluetooth device to create a mock of such a device, it'll be very hard for me to do so.

From that I recall of the code I scanned through in github, the data comes in on a event. What you need to do is instead of appending the data to the UI control as the sample code was doing, you simply append the data to a file.

The simplest way (albeit inefficient way) to append data to a file is just use the the File.AppendAllText(). No messing with streams and stream writers. Just pass in the filename and the text that you want appended to the file.
 
Thank you both: Sheepings and Skydiver.

@Sheepings, good idea to post in two posts the code.
That was the problem for not post the code in usual way, in my post above; Limit of 1000 char per post.

@Skydiver,
Understand what you say, but problem is that console sample dont give me HR values.
Am trying to "complete" that code to get the HR values.
That sample only gives device address and its name.
My problem is how to make it giving me heart rate values, and that why I tried to use that HeartRateServices file...
The help I need is how to use that file in console's sample.
Thanks anyway.

Filipe Santos.
 
Back
Top Bottom