diff --git a/Form1.Designer.cs b/Form1.Designer.cs
index fff0f90..f080e93 100644
--- a/Form1.Designer.cs
+++ b/Form1.Designer.cs
@@ -28,6 +28,7 @@ protected override void Dispose(bool disposing)
///
private void InitializeComponent()
{
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
listBoxDevices = new ListBox();
lblStatus = new Label();
btnScan = new Button();
@@ -39,20 +40,20 @@ private void InitializeComponent()
listBoxDevices.ItemHeight = 15;
listBoxDevices.Location = new Point(12, 12);
listBoxDevices.Name = "listBoxDevices";
- listBoxDevices.Size = new Size(300, 109);
+ listBoxDevices.Size = new Size(311, 109);
listBoxDevices.TabIndex = 0;
//
// lblStatus
//
lblStatus.AutoSize = true;
- lblStatus.Location = new Point(93, 131);
+ lblStatus.Location = new Point(99, 135);
lblStatus.Name = "lblStatus";
lblStatus.Size = new Size(0, 15);
lblStatus.TabIndex = 1;
//
// btnScan
//
- btnScan.Location = new Point(12, 127);
+ btnScan.Location = new Point(18, 131);
btnScan.Name = "btnScan";
btnScan.Size = new Size(75, 23);
btnScan.TabIndex = 2;
@@ -64,10 +65,11 @@ private void InitializeComponent()
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
- ClientSize = new Size(321, 163);
+ ClientSize = new Size(335, 168);
Controls.Add(btnScan);
Controls.Add(lblStatus);
Controls.Add(listBoxDevices);
+ Icon = (Icon)resources.GetObject("$this.Icon");
Name = "Form1";
Text = "Polar H10 to LSL";
ResumeLayout(false);
diff --git a/Form1.cs b/Form1.cs
index 3b5f45a..2920a4c 100644
--- a/Form1.cs
+++ b/Form1.cs
@@ -1,7 +1,12 @@
-using Windows.Devices.Bluetooth.Advertisement;
+using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
+
using LSL;
+using System.Reflection.PortableExecutable;
+using Windows.Storage.Streams;
+using System.Windows.Forms;
+
namespace PolarBLE
{
@@ -18,17 +23,19 @@ public partial class Form1 : Form
///
/// Dictionary storing detected BLE devices, indexed by their Bluetooth address.
///
- Dictionary devices = [];
+ Dictionary devices = new Dictionary();
///
- /// LSL outlet used to stream ECG data.
+ /// LSL outlet used to stream ECG and Acc data.
///
- StreamOutlet outlet;
+ StreamOutlet ecgOutlet;
+ StreamOutlet accOutlet;
///
/// Reference to the GATT characteristic used for receiving ECG data from the Polar H10.
///
GattCharacteristic ecgChar;
+ GattCharacteristic accChar;
///
/// UUID for the Polar Measurement Data (PMD) control characteristic.
@@ -43,14 +50,42 @@ public partial class Form1 : Form
///
/// Byte array command used to initialize ECG streaming on the Polar H10 sensor.
///
- private static readonly byte[] ECG_WRITE = new byte[]
+ private static readonly byte[] ECG_WRITE =
{
- 0x02, 0x00, 0x00, 0x01, 0x82, 0x00, 0x01, 0x01, 0x0E, 0x00
+ 0x02, // [0] Start measurement command
+ 0x00, 0x00, // [1-2] Reserved or unused (typically 0x0000)
+ 0x01, // [3] Measurement type: PMD (Physical Measurement Data)
+ 0x82, 0x00, // [4-5] Feature type: ECG (0x0082 in little-endian)
+ 0x01, // [6] Resolution index: 0x01 → 16-bit resolution
+ 0x01, // [7] Sample rate: 0x01 → 130 Hz
+ 0x0E, 0x00 // [8-9] Range index or frame type (0x000E is a bit mysterious; varies by firmware)
};
+
+ ///
+ /// Byte array command used to initialize ACC streaming on the Polar H10 sensor.
+ /// Range index 0x02 = ±2g
+ /// Range index 0x04 = ±4g
+ /// Range index 0x08 = ±8g
+ ///
+ private static readonly byte[] ACC_WRITE =
+ {
+ 0x02, // Start measurement
+ 0x02, 0x00, // Reserved
+ 0x01, // Measurement type: PMD (0x01)
+ 0xC8, 0x00, // Feature type: ACC (0x0083 little-endian)
+ 0x01, // Resolution index (0x01 = 16-bit)
+ 0x01, // Sample rate (0x01 = 200Hz)
+ 0x10, 0x00, // Range index (optional; this sets ±4g in some docs)
+ 0x02, 0x01, 0x08, 0x00 // Additional configuration bytes
+ };
+
+ private const string BatteryLevelCharacteristicUuid = "00002A19-0000-1000-8000-00805F9B34FB";
+
///
/// Internal counter for cycling the "Streaming" status animation dots.
///
private int dotState = 0;
+ private TextProgressBar? Battery = null;
///
/// Initializes a new instance of the class and sets up event handlers.
@@ -58,9 +93,40 @@ public partial class Form1 : Form
public Form1()
{
InitializeComponent();
+ Battery = new TextProgressBar()
+ {
+ Size = new Size(100, 23),
+ Location = new Point(223, 131),
+ Visible = false,
+ Value = 0,
+ };
+ this.Controls.Add(Battery);
listBoxDevices.SelectedIndexChanged += ListBoxDevices_SelectedIndexChanged;
}
+ private void CustomDrawProgressBar(PaintEventArgs e)
+ {
+ int progress = 60; // Your progress value
+ int max = 100;
+
+ float percent = (float)progress / max;
+ int width = (int)(this.Width * percent);
+
+ // Draw background
+ e.Graphics.FillRectangle(Brushes.Gray, 0, 0, this.Width, this.Height);
+ // Draw progress
+ e.Graphics.FillRectangle(Brushes.Green, 0, 0, width, this.Height);
+ // Draw text
+ string text = $"{progress}%";
+ var size = e.Graphics.MeasureString(text, this.Font);
+ var textPos = new PointF(
+ (this.Width - size.Width) / 2,
+ (this.Height - size.Height) / 2
+ );
+ e.Graphics.DrawString(text, this.Font, Brushes.White, textPos);
+ }
+
+
///
/// Event handler for the Scan button click event. Starts scanning for nearby Polar H10 BLE devices.
///
@@ -102,6 +168,8 @@ private async void ListBoxDevices_SelectedIndexChanged(object? sender, EventArgs
lblStatus.Text = "Connecting...";
var selected = listBoxDevices.SelectedItem?.ToString();
+ if (selected == null) return;
+
var address = ulong.Parse(selected.Split('[')[1].TrimEnd(']'));
var device = await BluetoothLEDevice.FromBluetoothAddressAsync(address);
@@ -111,21 +179,46 @@ private async void ListBoxDevices_SelectedIndexChanged(object? sender, EventArgs
var chars = await service.GetCharacteristicsAsync();
foreach (var c in chars.Characteristics)
{
- if (c.Uuid.ToString().ToUpper() == PMD_CONTROL)
+ if (c == null) continue;
+ if (c?.Uuid.ToString().ToUpper() == PMD_CONTROL)
{
- await c.ReadValueAsync();
await c.WriteValueAsync(Windows.Security.Cryptography.CryptographicBuffer.CreateFromByteArray(ECG_WRITE));
+ await c.WriteValueAsync(Windows.Security.Cryptography.CryptographicBuffer.CreateFromByteArray(ACC_WRITE));
}
- if (c.Uuid.ToString().ToUpper() == PMD_DATA)
+
+ if (c?.Uuid.ToString().ToUpper() == PMD_DATA)
{
ecgChar = c;
ecgChar.ValueChanged += EcgChar_ValueChanged;
await ecgChar.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.Notify);
+
+ accChar = c;
+ accChar.ValueChanged += AccChar_ValueChanged;
+ await accChar.WriteClientCharacteristicConfigurationDescriptorAsync(
+ GattClientCharacteristicConfigurationDescriptorValue.Notify);
+ }
+ if (c?.Uuid == new Guid(BatteryLevelCharacteristicUuid))
+ {
+ // Read the value
+ var readResult = await c?.ReadValueAsync(BluetoothCacheMode.Uncached);
+ if (readResult.Status == GattCommunicationStatus.Success)
+ {
+ var reader = DataReader.FromBuffer(readResult.Value);
+ byte[] data = new byte[readResult.Value.Length];
+ reader.ReadBytes(data);
+
+ // Use the input byte array
+ if (Battery != null)
+ {
+ Battery.Value = data[0];
+ }
+ }
}
}
}
+ if (Battery != null) Battery.Visible = true;
StartLSL(device.Name, address.ToString());
lblStatus.Text = "Wait for Streaming...";
}
@@ -141,7 +234,7 @@ private unsafe void EcgChar_ValueChanged(GattCharacteristic sender, GattValueCha
reader.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;
byte[] data = new byte[args.CharacteristicValue.Length];
reader.ReadBytes(data);
-
+
if (data[0] != 0x00) return;
int step = 3;
@@ -154,7 +247,7 @@ private unsafe void EcgChar_ValueChanged(GattCharacteristic sender, GattValueCha
if ((raw & 0x800000) != 0) // if sign bit (bit 23) is set
raw |= unchecked((int)0xFF000000); // sign-extend to 32 bits
- outlet.push_sample(new float[] { raw });
+ ecgOutlet?.push_sample(new float[] { raw });
offset += step;
}
// Animate "Streaming" label with dots to show activity
@@ -163,6 +256,38 @@ private unsafe void EcgChar_ValueChanged(GattCharacteristic sender, GattValueCha
BeginInvoke(() => lblStatus.Text = $"Streaming{dots}");
}
+ ///
+ /// Callback invoked when ACC characteristic receives new data. Parses ACC samples and pushes them to the LSL stream.
+ ///
+ /// The GATT characteristic that triggered the event.
+ /// Event arguments containing the characteristic value data.
+ private void AccChar_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
+ {
+ var reader = Windows.Storage.Streams.DataReader.FromBuffer(args.CharacteristicValue);
+ reader.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;
+ byte[] data = new byte[args.CharacteristicValue.Length];
+ reader.ReadBytes(data);
+
+ if (data.Length < 10 || data[0] != 0x02) return;
+
+ byte frame_type = data[9];
+ int resolution = (frame_type + 1) *8;
+ int bytesPerAxis = resolution / 8;
+ int bytesPerSample = 3 * bytesPerAxis;
+
+ data = data.Skip(10).ToArray(); // skip header
+
+ for (int i = 0; i + bytesPerSample <= data.Length; i += bytesPerSample)
+ {
+ short x = BitConverter.ToInt16(data, i);
+ short y = BitConverter.ToInt16(data, i + 2);
+ short z = BitConverter.ToInt16(data, i + 4);
+
+ accOutlet?.push_sample(new float[] { x, y, z });
+ }
+ BeginInvoke(() => lblStatus.ForeColor = Color.Green);
+ }
+
///
/// Initializes and starts the LSL (LabStreamingLayer) outlet stream for ECG data.
///
@@ -170,14 +295,21 @@ private unsafe void EcgChar_ValueChanged(GattCharacteristic sender, GattValueCha
/// A unique identifier for the stream based on the Bluetooth address.
private void StartLSL(string name, string id)
{
- var info = new StreamInfo(name, "ECG", 1, 130, channel_format_t.cf_float32, id);
+ var info = new StreamInfo(name + "_ecg", "ECG", 1, 130, channel_format_t.cf_float32, id = name + "_ecg");
var channels = info.desc().append_child("channels");
channels.append_child("channel")
.append_child_value("name", "ECG")
.append_child_value("unit", "microvolts")
.append_child_value("type", "ECG");
- outlet = new StreamOutlet(info, 74, 360);
+ ecgOutlet = new StreamOutlet(info, 74, 360);
+
+ var accInfo = new StreamInfo(name + "_acc", "Accelerometer", 3, 200, channel_format_t.cf_float32, id = name + "_acc");
+ var accChannels = accInfo.desc().append_child("channels");
+ accChannels.append_child("channel").append_child_value("name", "X");
+ accChannels.append_child("channel").append_child_value("name", "Y");
+ accChannels.append_child("channel").append_child_value("name", "Z");
+ accOutlet = new StreamOutlet(accInfo, 25, 360);
}
}
}
diff --git a/Form1.resx b/Form1.resx
index 8b2ff64..fcc2443 100644
--- a/Form1.resx
+++ b/Form1.resx
@@ -117,4 +117,81 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASQAAAEkAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFYAAAD7AAAA/AAA
+ AFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABWAAAA+wAA
+ AP8AAAD/AAAA/AAAAFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVgAA
+ APsAAAD/AAAA/wAAAP8AAAD/AAAA/AAAAFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AFYAAAD7AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/AAAAFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAABVAAAA+wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/AAAAFwAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAWQAAAPwAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAADxAAAAywAAAP8AAAD/AAAA/AAA
+ AFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAFkAAAD8AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAGcAAAAJAAAA8QAA
+ AP8AAAD/AAAA/AAAAFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAABZAAAA/AAAAP8AAAD/AAAA/wAAAHoAAABSAAAA/wAAAP8AAADbAAAABQAA
+ AAAAAAChAAAA/wAAAP8AAAD/AAAA+wAAAFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAVQAAAPsAAAD/AAAA/wAAAP8AAADsAAAACwAAAAAAAAC9AAAA/wAA
+ AFoAAAAkAAAARQAAAEkAAAD/AAAA/wAAAP8AAAD/AAAA/AAAAFsAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAARAAAAKoAAACqAAAAqgAAAFsAAABSAAAAXAAAAOoAAAD/AAAA/wAAAH4AAAASAAAAOgAA
+ ADoAAADRAAAAAgAAAKUAAACiAAAABAAAAOoAAAD/AAAA/wAAANEAAADMAAAAyAAAAD8AAAAzAAAAMwAA
+ ADMAAAAUAAAAAAAAAAAAAABEAAAAqgAAAKoAAACoAAAAWgAAAFUAAAAQAAAAfAAAAP8AAADzAAAAEgAA
+ AHwAAADFAAAAAAAAACIAAAAuAAAA/QAAAPIAAAAJAAAAlgAAAP8AAAC5AAAAAAAAAAAAAAAAAAAACAAA
+ ANEAAAD/AAAA/wAAAGYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEAAAD/AAAA/wAAAHkAAAAVAAAA9wAA
+ AI8AAAAJAAAA6QAAAP8AAABLAAAAAAAAALIAAAD/AAAA/wAAAFUAAAA+AAAA/wAAAHAAAAAnAAAA3QAA
+ AN0AAADdAAAAsQAAACIAAAAiAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAfAAAA/wAAAP8AAAD/AAAA4wAA
+ AAUAAACbAAAAHwAAAGsAAAD/AAAA/wAAANgAAAB/AAAA/wAAAP8AAAD/AAAArQAAAAEAAADgAAAAKwAA
+ AG0AAAD/AAAA/wAAAP8AAAD+AAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE8AAAD/AAAA/wAA
+ AP8AAAD/AAAAVwAAAAoAAAAEAAAA3QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD3AAAADwAA
+ AHIAAAABAAAAsgAAAP8AAAD/AAAA/wAAAP8AAABMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVwAA
+ AP8AAAD/AAAA/wAAAP8AAADFAAAAAAAAAFkAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA
+ AP8AAABfAAAABAAAAAUAAADyAAAA/wAAAP8AAAD/AAAA/wAAAFUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAA3AAAA/wAAAP8AAAD/AAAA/wAAAP8AAACEAAAA3AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA
+ AP8AAAD/AAAA/wAAALgAAAAAAAAAPQAAAP8AAAD/AAAA/wAAAP8AAAD/AAAANAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAMAAADnAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA
+ AP8AAAD/AAAA/wAAAP8AAAD/AAAA+wAAABkAAACGAAAA/wAAAP8AAAD/AAAA/wAAAOUAAAADAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGoAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA
+ AP8AAAD/AAAArgAAAK4AAAD/AAAA/wAAAP8AAAD/AAAA6QAAAPoAAAD/AAAA/wAAAP8AAAD/AAAAaQAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAKQAAAD/AAAA/wAAAP8AAAD/AAAA/wAA
+ AP8AAAD/AAAA/wAAAKsAAAAFAAAABQAAAKwAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA
+ AKUAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAH0AAAD0AAAA/wAA
+ AP8AAAD/AAAA/wAAAPQAAAB8AAAAAgAAAAAAAAAAAAAAAgAAAH0AAAD0AAAA/wAAAP8AAAD/AAAA/wAA
+ APQAAAB7AAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ ABIAAABdAAAAggAAAIIAAABcAAAAEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMAAABdAAAAggAA
+ AIIAAABcAAAAEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAA/////////////////////////////n////w////4H///8A///+AH///A
+ A///gAH//wAA//4AIH/8BAA/gAAAAYACAcHwAQAB4AAAB+AAAAfgQAAH4AAEB+AAAAfwAAAP8AAAD/gB
+ gB/+B+B///////////////////////////8=
+
+
\ No newline at end of file
diff --git a/PolarBLE.csproj b/PolarBLE.csproj
index 8b3eea9..1f65fa7 100644
--- a/PolarBLE.csproj
+++ b/PolarBLE.csproj
@@ -7,6 +7,17 @@
true
enable
true
+ icon.ico
+
+
+
+
+
+
+ Always
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 3fbe6a0..8575e18 100644
--- a/README.md
+++ b/README.md
@@ -3,17 +3,16 @@
**PolarBLE** is a C# .NET 8 WinForms application that connects to a **Polar H10 ECG sensor** over **Bluetooth Low Energy (BLE)** and streams raw ECG data in real-time to **LabStreamingLayer (LSL)**.
This project is a port of my previous [PolarBand2lsl](https://github.com/markspan/PolarBand2lsl) Python/Kivy implementation.
+I took info from the project [Dont-hold-your-breath](https://github.com/kieranabrennan/dont-hold-your-breath) by Kieran Brennan. Look it up!
---
## 🧠 Features
- Scan for nearby **Polar H10** devices via BLE
-- Connect and subscribe to the ECG and ACC measurement service
-- Decode and stream **130 Hz** ECG data to **LSL**
-- Decode and stream **200 Hz** ACC data to **LSL**
-- Simple UI for quick interaction
-
+- Connect and subscribe to the ECG measurement service
+- Decode and stream **130 Hz** ECG data to **LSL**
+- Decode and stream **200Hz** ACC data to **LSL**
---
## 💻 Requirements
@@ -27,9 +26,7 @@ This project is a port of my previous [PolarBand2lsl](https://github.com/markspa
## 🚀 Quickstart
-### Clone & Restore
-
-```bash
-git clone https://github.com/YOUR_USERNAME/PolarBLE.git
-
-
+Download the last Release from the GitHub repository, unzip and run.
+The Program will start looking for nearby polar bands and display their ID.
+When you click on the ID of the band you want to stream, it will start streaming.
+This can take some time (up to a minute), the programm will inform you when streaming is in progress.
diff --git a/TextProgressBar.cs b/TextProgressBar.cs
new file mode 100644
index 0000000..dccba48
--- /dev/null
+++ b/TextProgressBar.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Eventing.Reader;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PolarBLE
+{
+ public class TextProgressBar : Panel
+ {
+ private int _value = 0;
+ private int _maximum = 100;
+
+ public int Value
+ {
+ get => _value;
+ set
+ {
+ _value = Math.Min(_maximum, Math.Max(0, value));
+ this.Invalidate(); // Redraw
+ }
+ }
+
+ public int Maximum
+ {
+ get => _maximum;
+ set
+ {
+ _maximum = Math.Max(1, value);
+ this.Invalidate();
+ }
+ }
+
+ public TextProgressBar()
+ {
+ this.DoubleBuffered = true;
+ this.ResizeRedraw = true;
+ }
+
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ base.OnPaint(e);
+
+ float percent = (float)_value / _maximum;
+ int fillWidth = (int)(this.Width * percent);
+
+ // Draw progress bar
+ if (percent < .10)
+ {
+ using (Brush progressBrush = new SolidBrush(Color.Red))
+ {
+ e.Graphics.FillRectangle(progressBrush, 0, 0, fillWidth, this.Height);
+ }
+ }
+ else
+ {
+ using (Brush progressBrush = new SolidBrush(Color.Green))
+ {
+ e.Graphics.FillRectangle(progressBrush, 0, 0, fillWidth, this.Height);
+ }
+ }
+
+
+ // Draw text
+ string text = $"{_value}%";
+ SizeF textSize = e.Graphics.MeasureString(text, this.Font);
+ PointF textPos = new PointF(
+ (this.Width - textSize.Width) / 2,
+ (this.Height - textSize.Height) / 2
+ );
+
+ e.Graphics.DrawString(text, this.Font, Brushes.White, textPos);
+ }
+ }
+}
diff --git a/icon.ico b/icon.ico
new file mode 100644
index 0000000..49095f5
Binary files /dev/null and b/icon.ico differ
diff --git a/lsl.dll b/lsl.dll
new file mode 100644
index 0000000..cf5af42
Binary files /dev/null and b/lsl.dll differ