admin管理员组

文章数量:1312806

I have a raspberry pi pico W, which is connected to a hall switch so it can monitor revolutions of a wheel. I am trying to broadcast this information over BLE, so I can pick it up with a Garmin watch. I am transmitting the data using CSC, and have set all the required flags I think.

In nRF connect, it shows up perfectly, all the information is there, it is connectable and it shows the time difference between wheel rotations. It also works fine in Zwift. But when connecting to garmin, it shows up, but when pairing, the connection fails. This is the bluetooth part of my code, written in mircopython:

_ADV_TYPE_FLAGS = const(0x01)
_ADV_TYPE_NAME = const(0x09)
_ADV_TYPE_UUID16_COMPLETE = const(0x3)
_ADV_TYPE_UUID32_COMPLETE = const(0x5)
_ADV_TYPE_UUID128_COMPLETE = const(0x7)
_ADV_TYPE_UUID16_MORE = const(0x2)
_ADV_TYPE_UUID32_MORE = const(0x4)
_ADV_TYPE_UUID128_MORE = const(0x6)
_ADV_TYPE_APPEARANCE = const(0x19)


# Generate a payload to be passed to gap_advertise(adv_data=...).
def advertising_payload(limited_disc=False, br_edr=False, name=None, services=None, appearance=0):
    payload = bytearray()

    def _append(adv_type, value):
        nonlocal payload
        payload += struct.pack("BB", len(value) + 1, adv_type) + value

    _append(
        _ADV_TYPE_FLAGS,
        struct.pack("B", (0x01 if limited_disc else 0x02) + (0x18 if br_edr else 0x04)),
    )

    if name:
        _append(_ADV_TYPE_NAME, name)

    if services:
        for uuid in services:
            b = bytes(uuid)
            if len(b) == 2:
                _append(_ADV_TYPE_UUID16_COMPLETE, b)
            elif len(b) == 4:
                _append(_ADV_TYPE_UUID32_COMPLETE, b)
            elif len(b) == 16:
                _append(_ADV_TYPE_UUID128_COMPLETE, b)

    # See .bluetooth.characteristic.gap.appearance.xml
    if appearance:
        _append(_ADV_TYPE_APPEARANCE, struct.pack("<h", appearance))

    return payload

_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_INDICATE_DONE = const(20)

_FLAG_READ = const(0x0002)
_FLAG_NOTIFY = const(0x0010)
_FLAG_INDICATE = const(0x0020)

# .bluetooth.service.environmental_sensing
_ENV_SENSE_UUID = bluetooth.UUID(0x1816)
# Custom UUID for speed characteristic 
_SPEED_CHAR = (
    bluetooth.UUID(0x2A5B),  # Correct UUID for Cycling Speed
    _FLAG_READ | _FLAG_NOTIFY | _FLAG_INDICATE,
)
# Add the Location characteristic UUID (Cycling Speed and Cadence Service)
_CSC_SENSOR_LOCATION_UUID = bluetooth.UUID(0x2A5D)  # Sensor Location characteristic UUID
_SENSOR_LOCATION_CHAR = (
    _CSC_SENSOR_LOCATION_UUID,
    _FLAG_READ,
)

# Add the CSC Feature characteristic UUID (Cycling Speed and Cadence Service)
_CSC_FEATURE_UUID = bluetooth.UUID(0x2A5C)  # CSC Feature characteristic UUID
_CSC_FEATURE_CHAR = (
    _CSC_FEATURE_UUID,
    _FLAG_READ,
)

# Update the service to include the CSC Feature characteristic
_ENV_SENSE_SERVICE = (
    _ENV_SENSE_UUID,
    (_SPEED_CHAR, _SENSOR_LOCATION_CHAR, _CSC_FEATURE_CHAR),
)

class BLESpeed:
    def __init__(self, ble, name=""):
        self._ble = ble
        self._ble.active(True)
        self._ble.irq(self._irq)
        ((self._handle, self._location_handle, self._csc_feature_handle),) = self._ble.gatts_register_services((_ENV_SENSE_SERVICE,))
        self._connections = set()
        if len(name) == 0:
            name = 'Pico Speed'
        print('Sensor name %s' % name)
        self._payload = advertising_payload(
            name=name, services=[_ENV_SENSE_UUID],
            appearance=1157
        )
        self._advertise()
        # Set the initial sensor location to "front wheel"
        self.set_sensor_location()
        self.set_csc_feature()


    def _irq(self, event, data):
        if event == _IRQ_CENTRAL_CONNECT:
            conn_handle, _, _ = data
            self._connections.add(conn_handle)
        elif event == _IRQ_CENTRAL_DISCONNECT:
            conn_handle, _, _ = data
            self._connections.remove(conn_handle)
            self._advertise()
        elif event == _IRQ_GATTS_INDICATE_DONE:
            conn_handle, value_handle, status = data

    def set_sensor_location(self, notify=False, indicate=False):
        """Set the location of the sensor (e.g., 'front_wheel', 'rear_wheel', etc.)"""
        location_value = SENSOR_LOCATIONS.get("front_wheel", SENSOR_LOCATIONS["other"])  # Default to 'other' if not found
        self._ble.gatts_write(self._location_handle, bytes([location_value]))  # Write the location value

        # Notify or indicate the change to connected devices
        if notify or indicate:
            for conn_handle in self._connections:
                if notify:
                    self._ble.gatts_notify(conn_handle, self._location_handle)
                if indicate:
                    self._ble.gatts_indicate(conn_handle, self._location_handle)

    def set_csc_feature(self, wheel_rev_data=True, crank_rev_data=False, multi_sensor_loc=False, notify=False, indicate=False):
        """Set the CSC feature bitfield"""
        # Construct the 16-bit feature bitfield
        csc_feature = 0
        if wheel_rev_data:
            csc_feature |= (1 << 0)  # Wheel Revolution Data Supported
        if crank_rev_data:
            csc_feature |= (1 << 1)  # Crank Revolution Data Supported
        if multi_sensor_loc:
            csc_feature |= (1 << 2)  # Multiple Sensor Locations Supported

        # Pack the 16-bit field into bytes
        csc_feature_bytes = bytes([csc_feature & 0xFF, (csc_feature >> 8) & 0xFF])

        # Write the data to the CSC feature characteristic handle
        self._ble.gatts_write(self._csc_feature_handle, csc_feature_bytes)

        # Notify or indicate the change to connected devices
        if notify or indicate:
            for conn_handle in self._connections:
                if notify:
                    self._ble.gatts_notify(conn_handle, self._csc_feature_handle)
                if indicate:
                    self._ble.gatts_indicate(conn_handle, self._csc_feature_handle)

    def update_speed(self, wheel_revolution, last_wheel_event_time_sec, notify=False, indicate=False):
        flags = 0x01  # Wheel revolution data present (bit 0 = 1, others = 0)

        # Convert last wheel event time to the 1/1024 resolution and pack into uint16
        last_wheel_event_time_1024ths = int(last_wheel_event_time_sec * 1024)

        # Pack the data in little-endian format
        data = bytes([
            flags,  # Flags (1 byte)
            wheel_revolution & 0xFF, (wheel_revolution >> 8) & 0xFF, (wheel_revolution >> 16) & 0xFF, (wheel_revolution >> 24) & 0xFF,  # Cumulative Wheel Revolutions (4 bytes)
            last_wheel_event_time_1024ths & 0xFF, (last_wheel_event_time_1024ths >> 8) & 0xFF  # Last Wheel Event Time (2 bytes)
        ])

        print(data.hex())  # Output the packed data in hex format

        # Write the data to the speed characteristic handle
        self._ble.gatts_write(self._handle, data)

        if notify or indicate:
            for conn_handle in self._connections:
                if notify:
                    self._ble.gatts_notify(conn_handle, self._handle)
                if indicate:
                    self._ble.gatts_indicate(conn_handle, self._handle)
        

    def _advertise(self, interval_us=100000):
        self._ble.gap_advertise(interval_us, adv_data=self._payload)

Does anyone have any idea what is wrong? Or any possible reasons the connection could be failing. Reportedly, this similar code works fine with Garmin, but I can't spot the difference:
.ino

Edit: I've tried changing to a public MAC address, and that did not work. Checking my advertisement hex, it is: 0201060b095069636f2053706565640303161803198504 Which seems correct?

本文标签: bluetoothGarmin watch won39t connect to pico W BLE sensorStack Overflow