Black Lives Matter - Action and Equality. ... Adafruit is open and shipping.
0

Buffer too short error
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Buffer too short error

by tcooper on Wed Jul 22, 2020 9:51 am

I was testing out the CircuitPython BLE Heart Rate Zone Trainer Display on the CLUE (https://learn.adafruit.com/circuitpytho ... te-trainer). I was using it with a Myzone MZ-3 chest HR monitor. It scanned correctly, discovered the monitor (showing the brand and type), but when it tried to take a reading I got this error:
Code: Select all | TOGGLE FULL SIZE
Traceback (most recent call last):
File "code.py", line 87, in <module>
File "adafruit_ble_heart_rate.py", line 157, in measurement_values
ValueError: Buffer too short by 5612 bytes


Any suggestions?

tcooper
 
Posts: 48
Joined: Mon Apr 23, 2012 10:31 pm
Location: Minneapolis, MN

Re: Buffer too short error

by danhalbert on Wed Jul 22, 2020 4:51 pm

What version of CircuitPython are you using, and have you updated to the latest version of the libraries? Try with 5.3.1 if you are using 6.0.0.

We have only tested with the Scosche monitor, so it's possible this one is reporting data in a different way, though that error should not have to do with differences in the monitor. I'll retestst with the Scosche.

danhalbert
 
Posts: 2073
Joined: Tue Aug 08, 2017 12:37 pm

Re: Buffer too short error

by tcooper on Wed Jul 22, 2020 4:57 pm

I originally tested it with 5.3, but then updated to 6.0 and nothing changed.

I just grabbed another chest strap I had, which is a Wahoo branded monitor and oddly enough it worked. So clearly it doesn't like some of the data that Myzone is sending.

Think there is a fix to get the Myzone to work?

tcooper
 
Posts: 48
Joined: Mon Apr 23, 2012 10:31 pm
Location: Minneapolis, MN

Re: Buffer too short error

by danhalbert on Wed Jul 22, 2020 5:13 pm

Try putting this file in CIRCUITPY as adafruit_ble_heart_rate.py. It will override the one in the library. It should print the expected packet size before failing. I am getting 20 with the Scosche. The Wahoo and the Scosche I think are just rebranded versions of the same thing.

Assuming you get 20, try making the edit shown after "#REPLACE LINE BELOW WITH" and see if works with the new sensor.

Also this test is simpler, but I would expect it to fail in the same way: https://github.com/adafruit/Adafruit_Ci ... pletest.py

Code: Select all | TOGGLE FULL SIZE
# The MIT License (MIT)
#
# Copyright (c) 2020 Dan Halbert for Adafruit Industries LLC
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FI TNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""
`adafruit_ble_heart_rate`
================================================================================

BLE Heart Rate Service


* Author(s): Dan Halbert for Adafruit Industries

The Heart Rate Service is specified here:
https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Services/org.bluetooth.service.heart_rate.xml

Implementation Notes
--------------------

**Hardware:**

* Adafruit CircuitPython firmware for the supported boards:
  https://github.com/adafruit/circuitpython/releases
* Adafruit's BLE library: https://github.com/adafruit/Adafruit_CircuitPython_BLE
"""
import struct
from collections import namedtuple

import _bleio
from adafruit_ble.services import Service
from adafruit_ble.uuid import StandardUUID
from adafruit_ble.characteristics import Characteristic, ComplexCharacteristic
from adafruit_ble.characteristics.int import Uint8Characteristic

__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE_Heart_Rate.git"

HeartRateMeasurementValues = namedtuple(
    "HeartRateMeasurementValues",
    ("heart_rate", "contact", "energy_expended", "rr_intervals"),
)
"""Namedtuple for measurement values.

* `HeartRateMeasurementValues.heart_rate`

        Heart rate (int), in beats per minute.

* `HeartRateMeasurementValues.contact`

        ``True`` if device is contacting the body, ``False`` if not,
        ``None`` if device does not support contact detection.

* `HeartRateMeasurementValues.energy_expended`

        Energy expended (int), in kilo joules, or ``None`` if no value.

* `HeartRateMeasurementValues.rr_intervals`

        Sequence of RR intervals, measuring the time between
        beats. Oldest first, in ints that are units of 1024ths of a second.
        This sequence will be empty if the device does not report the intervals.
        *Caution:* inexpensive heart rate monitors may not measure this
        accurately. Do not use for diagnosis.

For example::

    bpm = svc.measurement_values.heart_rate
"""


class _HeartRateMeasurement(ComplexCharacteristic):
    """Notify-only characteristic of streaming heart rate data."""

    uuid = StandardUUID(0x2A37)

    def __init__(self):
        super().__init__(properties=Characteristic.NOTIFY)

    def bind(self, service):
        """Bind to a HeartRateService."""
        bound_characteristic = super().bind(service)
        bound_characteristic.set_cccd(notify=True)
        # Use a PacketBuffer that can store one packet to receive the HRM data.
        return _bleio.PacketBuffer(bound_characteristic, buffer_size=1)


class HeartRateService(Service):
    """Service for reading from a Heart Rate sensor."""

    # 0x180D is the standard HRM 16-bit, on top of standard base UUID
    uuid = StandardUUID(0x180D)

    # uint8: flags
    #  bit 0 = 0: Heart Rate Value is uint8
    #  bit 0 = 1: Heart Rate Value is uint16
    #  bits 2:1 = 0 or 1: Sensor Contact Feature not supported
    #  bits 2:1 = 2: Sensor Contact Feature supported, contact is not detected
    #  bits 2:1 = 3: Sensor Contact Feature supported, contacted is detected
    #  bit 3 = 0: Energy Expended field is not present
    #  bit 3 = 1: Energy Expended field is present. Units: kilo Joules
    #  bit 4 = 0: RR-Interval values are not present
    #  bit 4 = 1: One or more RR-Interval values are present
    #
    # next uint8 or uint16: Heart Rate Value
    # next uint16: Energy Expended, if present
    # next uint16 (multiple): RR-Interval values, resolution of 1/1024 second
    #   in order of oldest to newest
    #
    # Mandatory for Heart Rate Service
    heart_rate_measurement = _HeartRateMeasurement()
    # Optional for Heart Rate Service.
    body_sensor_location = Uint8Characteristic(
        uuid=StandardUUID(0x2A38), properties=Characteristic.READ
    )

    # Mandatory only if Energy Expended features is supported.
    heart_rate_control_point = Uint8Characteristic(
        uuid=StandardUUID(0x2A39), properties=Characteristic.WRITE
    )

    _BODY_LOCATIONS = ("Other", "Chest", "Wrist", "Finger", "Hand", "Ear Lobe", "Foot")

    def __init__(self, service=None):
        super().__init__(service=service)
        # Defer creating buffer until needed.
        self._measurement_buf = None

    @property
    def measurement_values(self):
        """All the measurement values, returned as a HeartRateMeasurementValues
        namedtuple.

        Return ``None`` if no packet has been read yet.
        """
        if self._measurement_buf is None:
            print("packet size:", self.heart_rate_measurement.packet_size)  # ADDED
            self._measurement_buf = bytearray(
                # REPLACE LINE BELOW WITH
                # 6000
                self.heart_rate_measurement.packet_size  # pylint: disable=no-member
            )
        buf = self._measurement_buf
        packet_length = self.heart_rate_measurement.readinto(  # pylint: disable=no-member
            buf
        )
        if packet_length == 0:
            return None
        flags = buf[0]
        next_byte = 1

        if flags & 0x1:
            bpm = struct.unpack_from("<H", buf, next_byte)[0]
            next_byte += 2
        else:
            bpm = struct.unpack_from("<B", buf, next_byte)[0]
            next_byte += 1

        if flags & 0x4:
            # True or False if Sensor Contact Feature is supported.
            contact = bool(flags & 0x2)
        else:
            # None (meaning we don't know) if Sensor Contact Feature is not supported.
            contact = None

        if flags & 0x8:
            energy_expended = struct.unpack_from("<H", buf, next_byte)[0]
            next_byte += 2
        else:
            energy_expended = None

        rr_values = []
        if flags & 0x10:
            for offset in range(next_byte, packet_length, 2):
                rr_val = struct.unpack_from("<H", buf, offset)[0]
                rr_values.append(rr_val)

        return HeartRateMeasurementValues(bpm, contact, energy_expended, rr_values)

    @property
    def location(self):
        """The location of the sensor on the human body, as a string.

        Note that the specification describes a limited number of locations.
        But the sensor manufacturer may specify using a non-standard location.
        For instance, some armbands are meant to be worn just below the inner elbow,
        but that is not a prescribed location. So the sensor will report something
        else, such as "Wrist".

        Possible values are:
        "Other", "Chest", "Wrist", "Finger", "Hand", "Ear Lobe", "Foot", and
        "InvalidLocation" (if value returned does not match the specification).
        """

        try:
            return self._BODY_LOCATIONS[self.body_sensor_location]
        except IndexError:
            return "InvalidLocation"

danhalbert
 
Posts: 2073
Joined: Tue Aug 08, 2017 12:37 pm

Re: Buffer too short error

by tcooper on Wed Jul 22, 2020 6:10 pm

Interesting... the simple heart rate test file worked. I tried your update, but it kept erroring out no matter what I put as the buffer size.

Thanks for your help Dan! I should be able to do what I was planning with the simpler version of the code.

tcooper
 
Posts: 48
Joined: Mon Apr 23, 2012 10:31 pm
Location: Minneapolis, MN

Re: Buffer too short error

by kevinjwalters on Wed Jul 29, 2020 1:04 pm

FYI, there's another mention of something that sounds like a similar Buffer too short by problem Errors running the demo.

kevinjwalters
 
Posts: 665
Joined: Sun Oct 01, 2017 3:15 pm

Please be positive and constructive with your questions and comments.