I have a BLE code in c#. And I have a TCP Client code as well, which is in a new project. However, I am looking for a way to add the client code in an existing BLE code to get the data on TCP server. I have a heart rate monitor which is connected with Bluetooth. I want to get those data (marked in red) on the TCP server. Besides adding libraries where should I add the TCP Client code in BLE? Any help is appreciated. Thanks
(BLE code is basically a sample from GitHub:
Link:: https://github.com/Microsoft/Windows-universal-
samples/tree/master/Samples/BluetoothLE/cs
image: https://ibb.co/m2tqrp
BLE CODE:
namespace SDKTemplate
{
// This scenario connects to the device selected in the "Discover
// GATT Servers" scenario and communicates with it.
// Note that this scenario is rather artificial because it communicates
// with an unknown service with unknown characteristics.
// In practice, your app will be interested in a specific service with
// a specific characteristic.
public sealed partial class Scenario2_Client : Page
{
private MainPage rootPage = MainPage.Current;
private ObservableCollection<BluetoothLEAttributeDisplay>
ServiceCollection = new ObservableCollection<BluetoothLEAttributeDisplay>
();
private ObservableCollection<BluetoothLEAttributeDisplay>
CharacteristicCollection = new
ObservableCollection<BluetoothLEAttributeDisplay>();
private BluetoothLEDevice bluetoothLeDevice = null;
private GattCharacteristic selectedCharacteristic;
// Only one registered characteristic at a time.
private GattCharacteristic registeredCharacteristic;
private GattPresentationFormat presentationFormat;
#region Error Codes
readonly int E_BLUETOOTH_ATT_WRITE_NOT_PERMITTED =
unchecked((int)0x80650003);
readonly int E_BLUETOOTH_ATT_INVALID_PDU = unchecked((int)0x80650004);
readonly int E_ACCESSDENIED = unchecked((int)0x80070005);
readonly int E_DEVICE_NOT_AVAILABLE = unchecked((int)0x800710df); //
HRESULT_FROM_WIN32(ERROR_DEVICE_NOT_AVAILABLE)
#endregion
#region UI Code
public Scenario2_Client()
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (string.IsNullOrEmpty(rootPage.SelectedBleDeviceId))
{
ConnectButton.IsEnabled = false;
}
}
protected override async void OnNavigatedFrom(NavigationEventArgs e)
{
var success = await ClearBluetoothLEDeviceAsync();
if (!success)
{
rootPage.NotifyUser("Error: Unable to reset app state",
NotifyType.ErrorMessage);
}
}
#endregion
#region Enumerating Services
private async Task<bool> ClearBluetoothLEDeviceAsync()
{
if (subscribedForNotifications)
{
// Need to clear the CCCD from the remote device so we stop
receiving notifications
var result = awai registeredCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.None);
if (result != GattCommunicationStatus.Success)
{
return false;
}
else
{
selectedCharacteristic.ValueChanged -=
Characteristic_ValueChanged;
subscribedForNotifications = false;
}
}
bluetoothLeDevice?.Dispose();
bluetoothLeDevice = null;
return true;
}
private async void ConnectButton_Click()
{
ConnectButton.IsEnabled = false;
if (!await ClearBluetoothLEDeviceAsync())
{
rootPage.NotifyUser("Error: Unable to reset state, try again.", NotifyType.ErrorMessage);
ConnectButton.IsEnabled = false;
return;
}
try
{
// BT_Code: BluetoothLEDevice.FromIdAsync must be called from a UI thread because it may prompt for consent.
bluetoothLeDevice = await BluetoothLEDevice.FromIdAsync(rootPage.SelectedBleDeviceId);
if (bluetoothLeDevice == null)
{
rootPage.NotifyUser("Failed to connect to device.", NotifyType.ErrorMessage);
}
}
catch (Exception ex) when (ex.HResult == E_DEVICE_NOT_AVAILABLE)
{
rootPage.NotifyUser("Bluetooth radio is not on.", NotifyType.ErrorMessage);
}
if (bluetoothLeDevice != null)
{
// Note: BluetoothLEDevice.GattServices property will return an
empty list for unpaired devices. For all uses we recommend using
the GetGattServicesAsync method.
// BT_Code: GetGattServicesAsync returns a list of all the
supported services of the device (even if it's not paired to the
system).
// If the services supported by the device are expected to change
during BT usage, subscribe to the GattServicesChanged event.
GattDeviceServicesResult result = await
bluetoothLeDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached);
if (result.Status == GattCommunicationStatus.Success)
{
var services = result.Services;
rootPage.NotifyUser(String.Format("Found {0} services",
services.Count), NotifyType.StatusMessage);
foreach (var service in services)
{
ServiceCollection.Add(new
BluetoothLEAttributeDisplay(service));
}
ConnectButton.Visibility = Visibility.Collapsed;
ServiceList.Visibility = Visibility.Visible;
}
else
{
rootPage.NotifyUser("Device unreachable",
NotifyType.ErrorMessage);
}
}
ConnectButton.IsEnabled = true;
}
#endregion
#region Enumerating Characteristics
private async void ServiceList_SelectionChanged()
{
var attributeInfoDisp = (BluetoothLEAttributeDisplay)ServiceList.SelectedItem;
CharacteristicCollection.Clear();
RemoveValueChangedHandler();
IReadOnlyList<GattCharacteristic> characteristics = null;
try
{
// Ensure we have access to the device.
var accessStatus = await attributeInfoDisp.service.RequestAccessAsync();
if (accessStatus == DeviceAccessStatus.Allowed)
{
// BT_Code: Get all the child characteristics of a service. Use the cache mode to specify uncached characterstics only
// and the new Async functions to get the characteristics of unpaired devices as well.
var result = await attributeInfoDisp.service.GetCharacteristicsAsync(BluetoothCacheMode.Uncached);
if (result.Status == GattCommunicationStatus.Success)
{
characteristics = result.Characteristics;
}
else
{
rootPage.NotifyUser("Error accessing service.", NotifyType.ErrorMessage);
// On error, act as if there are no characteristics.
characteristics = new List<GattCharacteristic>();
}
}
else
{
// Not granted access
rootPage.NotifyUser("Error accessing service.", NotifyType.ErrorMessage);
// On error, act as if there are no characteristics.
characteristics = new List<GattCharacteristic>();
}
}
catch (Exception ex)
{
rootPage.NotifyUser("Restricted service. Can't read characteristics: " + ex.Message,
NotifyType.ErrorMessage);
// On error, act as if there are no characteristics.
characteristics = new List<GattCharacteristic>();
}
foreach (GattCharacteristic c in characteristics)
{
CharacteristicCollection.Add(new BluetoothLEAttributeDisplay(c));
}
CharacteristicList.Visibility = Visibility.Visible;
}
#endregion
private void AddValueChangedHandler()
{
ValueChangedSubscribeToggle.Content = "Unsubscribe from value changes";
if (!subscribedForNotifications)
{
registeredCharacteristic = selectedCharacteristic;
registeredCharacteristic.ValueChanged += Characteristic_ValueChanged;
subscribedForNotifications = true;
}
}
private void RemoveValueChangedHandler()
{
ValueChangedSubscribeToggle.Content = "Subscribe to value changes";
if (subscribedForNotifications)
{
registeredCharacteristic.ValueChanged -= Characteristic_ValueChanged;
registeredCharacteristic = null;
subscribedForNotifications = false;
}
}
private async void CharacteristicList_SelectionChanged()
{
selectedCharacteristic = null;
var attributeInfoDisp = (BluetoothLEAttributeDisplay)CharacteristicList.SelectedItem;
if (attributeInfoDisp == null)
{
EnableCharacteristicPanels(GattCharacteristicProperties.None);
return;
}
selectedCharacteristic = attributeInfoDisp.characteristic;
if (selectedCharacteristic == null)
{
rootPage.NotifyUser("No characteristic selected", NotifyType.ErrorMessage);
return;
}
// Get all the child descriptors of a characteristics. Use the cache mode to specify uncached descriptors only
// and the new Async functions to get the descriptors of unpaired devices as well.
var result = await selectedCharacteristic.GetDescriptorsAsync(BluetoothCacheMode.Uncached);
if (result.Status != GattCommunicationStatus.Success)
{
rootPage.NotifyUser("Descriptor read failure: " + result.Status.ToString(), NotifyType.ErrorMessage);
}
// BT_Code: There's no need to access presentation format unless there's at least one.
presentationFormat = null;
if (selectedCharacteristic.PresentationFormats.Count > 0)
{
if (selectedCharacteristic.PresentationFormats.Count.Equals(1))
{
// Get the presentation format since there's only one way of presenting it
presentationFormat = selectedCharacteristic.PresentationFormats[0];
}
else
{
// It's difficult to figure out how to split up a characteristic and encode its different parts properly.
// In this case, we'll just encode the whole thing to a string to make it easy to print out.
}
}
// Enable/disable operations based on the GattCharacteristicProperties.
EnableCharacteristicPanels(selectedCharacteristic.CharacteristicProperties);
}
private void SetVisibility(UIElement element, bool visible)
{
element.Visibility = visible ? Visibility.Visible : Visibility.Collapsed;
}
private void EnableCharacteristicPanels(GattCharacteristicProperties properties)
{
// BT_Code: Hide the controls which do not apply to this characteristic.
SetVisibility(CharacteristicReadButton, properties.HasFlag(GattCharacteristicProperties.Read));
SetVisibility(CharacteristicWritePanel,
properties.HasFlag(GattCharacteristicProperties.Write) ||
properties.HasFlag(GattCharacteristicProperties.WriteWithoutResponse));
CharacteristicWriteValue.Text = "";
SetVisibility(ValueChangedSubscribeToggle, properties.HasFlag(GattCharacteristicProperties.Indicate) ||
properties.HasFlag(GattCharacteristicProperties.Notify));
}
private async void CharacteristicReadButton_Click()
{
// BT_Code: Read the actual value from the device by using Uncached.
GattReadResult result = await selectedCharacteristic.ReadValueAsync(BluetoothCacheMode.Uncached);
if (result.Status == GattCommunicationStatus.Success)
{
string formattedResult = FormatValueByPresentation(result.Value, presentationFormat);
rootPage.NotifyUser($"Read result: {formattedResult}", NotifyType.StatusMessage);
}
else
{
rootPage.NotifyUser($"Read failed: {result.Status}", NotifyType.ErrorMessage);
}
}
private async void CharacteristicWriteButton_Click()
{
if (!String.IsNullOrEmpty(CharacteristicWriteValue.Text))
{
var writeBuffer = CryptographicBuffer.ConvertStringToBinary(CharacteristicWriteValue.Text,
BinaryStringEncoding.Utf8);
var writeSuccessful = await WriteBufferToSelectedCharacteristicAsync(writeBuffer);
}
else
{
rootPage.NotifyUser("No data to write to device", NotifyType.ErrorMessage);
}
}
private async void CharacteristicWriteButtonInt_Click()
{
if (!String.IsNullOrEmpty(CharacteristicWriteValue.Text))
{
var isValidValue = Int32.TryParse(CharacteristicWriteValue.Text, out int readValue);
if (isValidValue)
{
var writer = new DataWriter();
writer.ByteOrder = ByteOrder.LittleEndian;
writer.WriteInt32(readValue);
var writeSuccessful = await WriteBufferToSelectedCharacteristicAsync(writer.DetachBuffer());
}
else
{
rootPage.NotifyUser("Data to write has to be an int32", NotifyType.ErrorMessage);
}
}
else
{
rootPage.NotifyUser("No data to write to device", NotifyType.ErrorMessage);
}
}
private async Task<bool> WriteBufferToSelectedCharacteristicAsync(IBuffer buffer)
{
try
{
// BT_Code: Writes the value from the buffer to the characteristic.
var result = await selectedCharacteristic.WriteValueWithResultAsync(buffer);
if (result.Status == GattCommunicationStatus.Success)
{
rootPage.NotifyUser("Successfully wrote value to device", NotifyType.StatusMessage);
return true;
}
else
{
rootPage.NotifyUser($"Write failed: {result.Status}", NotifyType.ErrorMessage);
return false;
}
}
catch (Exception ex) when (ex.HResult == E_BLUETOOTH_ATT_INVALID_PDU)
{
rootPage.NotifyUser(ex.Message, NotifyType.ErrorMessage);
return false;
}
catch (Exception ex) when (ex.HResult == E_BLUETOOTH_ATT_WRITE_NOT_PERMITTED || ex.HResult == E_ACCESSDENIED)
{
// This usually happens when a device reports that it support writing, but it actually doesn't.
rootPage.NotifyUser(ex.Message, NotifyType.ErrorMessage);
return false;
}
}
private bool subscribedForNotifications = false;
private async void ValueChangedSubscribeToggle_Click()
{
if (!subscribedForNotifications)
{
// initialize status
GattCommunicationStatus status = GattCommunicationStatus.Unreachable;
var cccdValue = GattClientCharacteristicConfigurationDescriptorValue.None;
if (selectedCharacteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Indicate))
{
cccdValue = GattClientCharacteristicConfigurationDescriptorValue.Indicate;
}
else if (selectedCharacteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify))
{
cccdValue = GattClientCharacteristicConfigurationDescriptorValue.Notify;
}
try
{
// BT_Code: Must write the CCCD in order for server to send indications.
// We receive them in the ValueChanged event handler.
status = await selectedCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(cccdValue);
if (status == GattCommunicationStatus.Success)
{
AddValueChangedHandler();
rootPage.NotifyUser("Successfully subscribed for value changes", NotifyType.StatusMessage);
}
else
{
rootPage.NotifyUser($"Error registering for value changes: {status}", NotifyType.ErrorMessage);
}
}
catch (UnauthorizedAccessException ex)
{
// This usually happens when a device reports that it support indicate, but it actually doesn't.
rootPage.NotifyUser(ex.Message, NotifyType.ErrorMessage);
}
}
else
{
try
{
// BT_Code: Must write the CCCD in order for server to send notifications.
// We receive them in the ValueChanged event handler.
// Note that this sample configures either Indicate or Notify, but not both.
var result = await
selectedCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.None);
if (result == GattCommunicationStatus.Success)
{
subscribedForNotifications = false;
RemoveValueChangedHandler();
rootPage.NotifyUser("Successfully un-registered for notifications", NotifyType.StatusMessage);
//GattServiceUuids.Battery
}
else
{
rootPage.NotifyUser($"Error un-registering for notifications: {result}", NotifyType.ErrorMessage);
}
}
catch (UnauthorizedAccessException ex)
{
// This usually happens when a device reports that it support notify, but it actually doesn't.
rootPage.NotifyUser(ex.Message, NotifyType.ErrorMessage);
}
}
}
private async void Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
// BT_Code: An Indicate or Notify reported that the value has changed.
// Display the new value with a timestamp.
// sender.Uuid.Equals(GattCharacteristicUuids.HeartRateMeasurement)
var newValue = FormatValueByPresentation(args.CharacteristicValue, presentationFormat);
var message = $"Value at {DateTime.Now:hh:mm:ss.FFF}: {newValue}";
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => CharacteristicLatestValue.Text = message);
}
private string FormatValueByPresentation(IBuffer buffer, GattPresentationFormat format)
{
// BT_Code: For the purpose of this sample, this function converts only UInt32 and
// UTF-8 buffers to readable text. It can be extended to support other formats if your app needs them.
byte[] data;
CryptographicBuffer.CopyToByteArray(buffer, out data);
if (format != null)
{
if (format.FormatType == GattPresentationFormatTypes.UInt32 && data.Length >= 4)
{
return BitConverter.ToInt32(data, 0).ToString();
}
else if (format.FormatType == GattPresentationFormatTypes.Utf8)
{
try
{
return Encoding.UTF8.GetString(data);
}
catch (ArgumentException)
{
return "(error: Invalid UTF-8 string)";
}
}
else
{
// Add support for other format types as needed.
return "Unsupported format: " + CryptographicBuffer.EncodeToHexString(buffer);
}
}
else if (data != null)
{
// We don't know what format to use. Let's try some well-known profiles, or default back to UTF-8.
if (selectedCharacteristic.Uuid.Equals(GattCharacteristicUuids.HeartRateMeasurement))
{
try
{
string b = ParseHeartRateValue(data).ToString();
TrySend(b);
return "Heart Rate: " + b;
}
catch (ArgumentException)
{
return "Heart Rate: (unable to parse)";
}
}
else if (selectedCharacteristic.Uuid.Equals(GattCharacteristicUuids.BatteryLevel))
{
try
{
// battery level is encoded as a percentage value in the first byte according to
// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.battery_level.xml
return "Battery Level: " + data[0].ToString() + "%";
}
catch (ArgumentException)
{
return "Battery Level: (unable to parse)";
}
}
// This is our custom calc service Result UUID. Format it like an Int
else if (selectedCharacteristic.Uuid.Equals(Constants.ResultCharacteristicUuid))
{
return BitConverter.ToInt32(data, 0).ToString();
}
// No guarantees on if a characteristic is registered for notifications.
else if (registeredCharacteristic != null)
{
// This is our custom calc service Result UUID. Format it like an Int
if (registeredCharacteristic.Uuid.Equals(Constants.ResultCharacteristicUuid))
{
return BitConverter.ToInt32(data, 0).ToString();
}
}
else
{
try
{
return "Unknown format: " + Encoding.UTF8.GetString(data);
}
catch (ArgumentException)
{
return "Unknown format";
}
}
}
else
{
return "Empty data received";
}
return "Unknown format";
}
private async void TrySend(string data)
{
try
{
// Create the StreamSocket and establish a connection to the echo server.
using (var streamSocket = new Windows.Networking.Sockets.StreamSocket())
{
//The server hostname that we will be establishing a connection to. In this example, the server and client are in the same process.
await streamSocket.ConnectAsync((new Windows.Networking.HostName("localhost")), "9999");
// Send a request to the echo server.
using (Stream outputStream = streamSocket.OutputStream.AsStreamForWrite())
{
using (var streamWriter = new StreamWriter(outputStream))
{
await streamWriter.WriteLineAsync(data);
await streamWriter.FlushAsync();
}
}
}
}
catch (Exception)
{
}
}
/// <summary>
/// Process the raw data received from the device into application usable data,
/// according the the Bluetooth Heart Rate Profile.
/// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.heart_rate_measurement.xml&u=org.bluetooth.characteristic.heart_rate_measurement.xml
/// This function throws an exception if the data cannot be parsed.
/// </summary>
/// <param name="data">Raw data received from the heart rate monitor.</param>
/// <returns>The heart rate measurement value.</returns>
private static ushort ParseHeartRateValue(byte[] data)
{
// Heart Rate profile defined flag values
const byte heartRateValueFormat = 0x01;
byte flags = data[0];
bool isHeartRateValueSizeLong = ((flags & heartRateValueFormat) != 0);
if (isHeartRateValueSizeLong)
{
return BitConverter.ToUInt16(data, 1);
}
else
{
return data[1];
}
}
}
}
TCP Client :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Net;
using System.Net.Sockets;
public class EchoClient {
public static void Main() {
try {
TcpClient client = new TcpClient("139.169.63.130", 9999);
StreamReader reader = new StreamReader(client.GetStream());
StreamWriter writer = new StreamWriter(client.GetStream());
string s = string.Empty;
while(!s.Equals("Exit")) {
Console.WriteLine("TCP Client connected....");
Console.Write("Enter a string or number to send to the server:
");
s = Console.ReadLine();
Console.WriteLine();
writer.WriteLine(s);
writer.Flush();
string server_string = reader.ReadLine();
Console.WriteLine(server_string);
}
reader.Close();
writer.Close();
client.Close();
} catch(Exception e) {
Console.WriteLine(e);
}
}
}
User contributions licensed under CC BY-SA 3.0