polarofficial / polar-ble-sdk

Repository includes SDK and code examples. More info https://polar.com/en/developers
Other
485 stars 154 forks source link

no PPG data for verity sense with python #226

Closed Knusbert closed 1 year ago

Knusbert commented 2 years ago

Platform your question concerns:

Device:

Description: Hi, I try to read a ppg stream from a polar verity sense device with python 3.8.8. I looks like it behaves as expected but I don't get any data from the ppg stream. I checked the input format via

PPG_SETTING = bytearray([0x01, #get settings
                         0x01]) #type PPG
PMD_CHAR = 'FB005C81-02E7-F387-1CAD-8ACD2D8DF0C8'

def notification_handler(sender, data):
        print(', '.join('{:02x}'.format(x) for x in data))

await client.start_notify(PMD_CHAR,  notification_handler)
await client.write_gatt_char(PMD_CHAR,PPG_SETTING, response=True)
await client.stop_notify(PMD_CHAR)

> f0, 01, 01, 00, 00, 00, 01, 87, 00, 01, 01, 16, 00, 04, 01, 04

This response looks like expected and I used this settings as PPGSETTING to start the stream which response I also checked.

await client.start_notify(PMD_CHAR, notification_handler)
await client.write_gatt_char(PMD_CHAR,PPG_SETTING, response=True)
await client.stop_notify(PMD_CHAR)

> f0, 02, 01, 00, 00, 05, 01, 00, 00, 80, 3f

However, if I try to read the data stream I don't get any data.

PMD_DATA = 'FB005C82-02E7-F387-1CAD-8ACD2D8DF0C8'

  await client.start_notify(PMD_DATA, notification_handler)
  n = 135
  while n<500:
      await asyncio.sleep(1)
      n = n + 135
  await client.stop_notify(PMD_DATA)

I double checked with a polar watch and it gave me a HR reading. Any idea why this is not giving anything back with the python code?

JOikarinen commented 2 years ago

Hi @Knusbert,

I've checked what are the responses, if I use the example app to read the PPG settings and then start the PPG stream. I can confirm that your

f0, 01, 01, 00, 00, 00, 01, 87, 00, 01, 01, 16, 00, 04, 01, 04

  • the response for PPG Settings read looks okay

f0, 02, 01, 00, 00, 05, 01, 00, 00, 80, 3f

  • the response for PPG stream start looks okay

Next what shall happen are the notifications sent by Verity Sense on PMD_DATA characteristics. I am not familiar with python, so I cannot say much about you Python code snippets.

JOikarinen commented 2 years ago

Btw, here is another python solution @nikhilpareek149. I wonder may that give you any help?

Knusbert commented 2 years ago

Hi, thanks for the hint. I'm aware of this solution and took this as a guide for my code. I already compared my code which is basically the same as mine:

att_read = await client.read_gatt_char(PMD_CONTROL)
await client.write_gatt_char(PMD_CONTROL, ACC_WRITE)
await client.start_notify(PMD_DATA, data_conv)

the code from @nikhilpareek149 was written for a Polar H10 but from what I understood it should be the same for the verity sense (besides that the H10 as ECG)

JOikarinen commented 2 years ago

the code from @nikhilpareek149 was written for a Polar H10 but from what I understood it should be the same for the verity sense (besides that the H10 as ECG) Yes, start of ECG stream compared to start of PPG stream shall be very similar.

johan-sightic commented 2 years ago

Hi @Knusbert I have the same problem did you solve it?

dkang55 commented 2 years ago

Hi @EyescannerJE @Knusbert @JOikarinen was there a resolution to this problem by chance? Its seems that issue comes from await client.read_gatt_char( ) reading of PMD_CHAR = 'FB005C81-02E7-F387-1CAD-8ACD2D8DF0C8', which is not returning the correct byte-array/att_read.

johan-sightic commented 2 years ago

Hi @dkang55, unfortunately not. I figured that the verity sense needed different settings for the PPG stream than the H10. But the documentation is lacking on this.

Knusbert commented 2 years ago

Hi, I haven't succeeded sofar. I tried both the PPG and ECG commands on the verity (also ACC) but nothing worked. As mentioned above the communication with the device works and I do get a reply when I ask for the input format. Only the stream reading does not seem to work. I also disabled my watch and my mobile devices which could connect to the verity sense and block the stream.

johan-sightic commented 2 years ago

I actually got ACC data streaming to work for a short period of time but then it stopped again and I don't know why.

dkang55 commented 2 years ago

Many thanks for your responses @EyescannerJE @Knusbert. Would you guys be willing to share the code you are using to try to debug? I will also try on my end as well and can report back to see if I get a different output.

kt-eyescanner commented 2 years ago

Hi @dkang55 I work with @EyescannerJE and this is the code we use for debugging.

import asyncio
import math
import os
import signal

import sys
import time

import pandas as pd
from bleak import BleakClient
from bleak.uuids import uuid16_dict
import matplotlib.pyplot as plt
import matplotlib

""" Predefined UUID (Universal Unique Identifier) mapping are based on Heart Rate GATT service Protocol that most
Fitness/Heart Rate device manufacturer follow (Polar H10 in this case) to obtain a specific response input from 
the device acting as an API """

uuid16_dict = {v: k for k, v in uuid16_dict.items()}

## This is the device MAC ID, please update with your device ID
ADDRESS = 'A0:9E:1A:A2:DC:59'

## UUID for model number ##
MODEL_NBR_UUID = "00002a24-0000-1000-8000-00805f9b34fb"
## UUID for manufacturer name ##
MANUFACTURER_NAME_UUID = "0000{0:x}-0000-1000-8000-00805f9b34fb".format(
    uuid16_dict.get("Manufacturer Name String")
)

## UUID for battery level ##
BATTERY_LEVEL_UUID = "0000{0:x}-0000-1000-8000-00805f9b34fb".format(
    uuid16_dict.get("Battery Level")
)

## UUID for connection establsihment with device ##
PMD_SERVICE = "FB005C80-02E7-F387-1CAD-8ACD2D8DF0C8"

## UUID for Request of stream settings ##
PMD_CONTROL = "FB005C81-02E7-F387-1CAD-8ACD2D8DF0C8"

## UUID for Request of start stream ##
PMD_DATA = "FB005C82-02E7-F387-1CAD-8ACD2D8DF0C8"

## UUID for Request of PMD Stream ##
#PMD_WRITE = bytearray([0x02,    0x02,    0x00, 0x01, 0x34,    0x00,    0x01,    0x01,    0x10,    0x00,    0x02,    0x01,    0x08,    0x00, 0x04, 0x01, 0x03 ])
#PMD_WRITE = bytearray([0x02, 0x01, 0x00, 0x01, 0x34, 0x00, 0x01, 0x01, 0x10, 0x00, 0x02, 0x01, 0x08, 0x00, 0x04, 0x01, 0x03])
#PMD_WRITE = bytearray([0xF0,0x01,0x00,0x00,0x00,0x00,0x01,0x82,0x00,0x01,0x01,0x0E,0x00])

# ## UUID for Request of ACC Stream ##
ACC_WRITE = bytearray(
    [0x02,
    0x02,
    0x00,
    0x01,
    0x34,
    0x00,
    0x01,
    0x01,
    0x10,
    0x00,
    0x02,
    0x01,
    0x08,
    0x00,
    0x04,
    0x01,
    0x03
    ]
)

PPG_DATA = 0x01
ACC_DATA = 0x02

## For Plolar H10  sampling frequency ##
ACC_SAMPLING_FREQ = 200

acc_session_data = []
acc_session_time = []

## Positoning/Pinnning the real-time plot window on the screen
def move_figure(f, x, y):
    """Move figure's upper left corner to pixel (x, y)"""
    backend = matplotlib.get_backend()
    if backend == "TkAgg":
        f.canvas.manager.window.wm_geometry("+%d+%d" % (x, y))
    elif backend == "WXAgg":
        f.canvas.manager.window.SetPosition((x, y))
    else:
        # This works for QT and GTK
        # You can also use window.setGeometry
        f.canvas.manager.window.move(x, y)

## Keyboard Interrupt Handler
def keyboardInterrupt_handler(signum, frame):
    print("  key board interrupt received...")
    print("----------------Recording stopped------------------------")

## Bit conversion of the Hexadecimal stream
def data_conv(sender, data):
    print("Raw data:", data)
    if data[0] == ACC_DATA:
        timestamp = convert_to_unsigned_long(data, 1, 8)
        frame_type = data[9]
        resolution = (frame_type + 1) * 8
        step = math.ceil(resolution / 8.0)
        samples = data[10:]
        offset = 0
        while offset < len(samples):
            x = convert_array_to_signed_int(samples, offset, step)
            offset += step
            y = convert_array_to_signed_int(samples, offset, step)
            offset += step
            z = convert_array_to_signed_int(samples, offset, step)
            offset += step

            acc_session_data.extend([[x, y, z]])
            acc_session_time.extend([timestamp])
    with open("test.txt", "w") as f:
        f.write(str(data))

def convert_array_to_signed_int(data, offset, length):
    return int.from_bytes(
        bytearray(data[offset : offset + length]), byteorder="little", signed=True,
    )

def convert_to_unsigned_long(data, offset, length):
    return int.from_bytes(
        bytearray(data[offset : offset + length]), byteorder="little", signed=False,
    )

## Aynchronous task to start the data stream for ECG ##
async def run(client, debug=False):

    ## Writing chracterstic description to control point for request of UUID (defined above) ##

    await client.is_connected()
    print("---------Device connected--------------")

    model_number = await client.read_gatt_char(MODEL_NBR_UUID)
    print("Model Number: {0}".format("".join(map(chr, model_number))))

    manufacturer_name = await client.read_gatt_char(MANUFACTURER_NAME_UUID)
    print("Manufacturer Name: {0}".format("".join(map(chr, manufacturer_name))))

    battery_level = await client.read_gatt_char(BATTERY_LEVEL_UUID)
    print("Battery Level: {0}%".format(int(battery_level[0])))

    att_read = await client.read_gatt_char(PMD_CONTROL)

#    await client.write_gatt_char(PMD_CONTROL, ACC_WRITE)  #=====================
    await client.write_gatt_char(PMD_CONTROL, ACC_WRITE)  #=====================
    ## ECG stream started
    await client.start_notify(PMD_DATA, data_conv)

    print("Collecting ECG data...")

    ## Plot configurations
    plt.style.use("ggplot")
    fig = plt.figure(figsize=(15, 6))
    move_figure(fig, 2300, 0)
    ax1 = fig.add_subplot(3, 1, 1)
    ax2 = fig.add_subplot(3, 1, 2)
    ax3 = fig.add_subplot(3, 1, 3)
    fig.show()

    fig.suptitle(
        "Live Accelerometer(mg) Stream on Polar-H10", fontsize=15,
    )

    plt.xlabel(
        "\nData source: www.pareeknikhil.medium.com | " "Author: @pareeknikhil",
        fontsize=10,
    )

    n = ACC_SAMPLING_FREQ

    while True:

        ## Collecting ACC data for 1 second
        await asyncio.sleep(1)

        # plt.autoscale(enable=True, axis="y", tight=True)
        # ax1.plot(acc_session_data, color="r")
        # fig.canvas.draw()
        # ax1.set_xlim(left=n - 200, right=n)
        # ax1.set_ylabel("X axis", fontsize=10)

        # ax2.plot(acc_session_data, color="r")
        # fig.canvas.draw()
        # ax2.set_xlim(left=n - 200, right=n)
        # ax2.set_ylabel("Y axis", fontsize=10)

        # ax3.plot(acc_session_data, color="r")
        # fig.canvas.draw()
        # ax3.set_xlim(left=n - 200, right=n)
        # ax3.set_ylabel("Z axis", fontsize=10)
        print('ACCELROMETER DATA:', acc_session_data)
        print('========================================================')
        print('========================================================')
        n = n + 200
        if n > 2000:
            break

    #plt.show()

    ## Stop the stream once data is collected
    await client.stop_notify(PMD_DATA)
    print("Stopping ECG data...")
    print("[CLOSED] application closed.")

    sys.exit(0)

async def main():
    try:
        async with BleakClient(ADDRESS) as client:
            signal.signal(signal.SIGINT, keyboardInterrupt_handler)
            tasks = [
                asyncio.ensure_future(run(client, True)),
            ]

            await asyncio.gather(*tasks)
    except Exception as error:
        print(error)
        #pass

if __name__ == "__main__":
    print('statement')
    os.environ["PYTHONASYNCIODEBUG"] = str(1)
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    loop.run_until_complete(main())
sivaprasadravi commented 1 year ago

Hi @Knusbert @dkang55 I am using polar verity sense. For me I want to connect the sensor to my laptop using bluetooth and I want to get the live heart rate data in python. Is it possible for you to get a code for this?