Code Monkey home page Code Monkey logo

pybgapi's Introduction

PyBGAPI

This package provides a Python interface for the BGAPI binary protocol. It reads the BGAPI definition file and dynamically generates a parser for it.

Getting Started

To get started with Silicon Labs Bluetooth software, see QSG169: Bluetooth® SDK v3.x Quick Start Guide.

In the NCP context, the application runs on a host MCU or a PC, which is the NCP Host, while the Bluetooth stack runs on an EFR32, which is the NCP Target.

The NCP Host and Target communicate via a serial interface (UART). The communication between the NCP Host and Target is defined in the Silicon Labs proprietary protocol, BGAPI. pyBGAPI is the reference implementation of the BGAPI protocol in Python for the NCP Host.

AN1259: Using the v3.x Silicon Labs Bluetooth® Stack in Network CoProcessor Mode provides a detailed description how NCP works and how to configure it for custom hardware.

For latest BGAPI documentation, see docs.silabs.com.

For pyBGAPI example applications, see github.com/SiliconLabs/pybgapi-examples.

Usage

First, create an instance of the BGLib class, which is the main component of the package. BGLib class provides functions for sending BGAPI commands and returning responses and ways to receive asynchronous BGAPI events. The BGLib constructor takes a connector, which is the transport between BGLib and the device, and a list of BGAPI definition files. The currently supported connectors are bgapi.SerialConnector and bgapi.SocketConnector. bgapi.CpcConnector is designed for multi-protocol scenarios and is available only on Linux. For more details about CPC, see github.com/SiliconLabs/cpc-daemon. bgapi.RobustConnector is a special intermediate layer above regular connectors like bgapi.SerialConnector to eliminate common errors in the transport layer.

Start by importing the bgapi package and creating a BGLib object with the Bluetooth API and a serial port connector. The SerialConnector takes the serial port as an argument, which is a device file on Linux OS and macOS, e.g., '/dev/tty.usbmodem1421', or a COM port on windows, e.g., 'COM1'. Remember to change the path to sl_bt.xapi which can be found for each SDK version in the Bluetooth SDK under /path/to/sdks/gecko_sdk_suite/v3.x/protocol/bluetooth/api.

>>> import bgapi
>>> l = bgapi.BGLib(
...         bgapi.SerialConnector('/dev/tty.usbmodem1421'),
...         '/path/to/SDK/protocol/bluetooth/api/sl_bt.xapi')
>>> l.open()

The BGLib constructor has an event_handler parameter too. Its default value is None, which means that all received events go to a queue for later retrieval. Alternatively, an event handler function may be passed, which is useful in interactive mode for printing the received events, as follows:

>>> def event_handler(evt):
...     print("Received event: {}".format(evt))

Start calling BGAPI commands, as follows:

>>> l.bt.system.hello()
rsp_system_hello(result=0)

The command functions are blocking, where the return value is the command's response. The commands are in an attribute named after the device name that the API is for, bt in this example. Then, they are grouped by the class name.

The response objects behave like a Python namedtuple, i.e., the response fields can be accessed as attributes (the dot notation) or like a tuple by their index. The attribute access is usually the preferred option.

>>> response = l.bt.system.get_counters(0)
>>> print(response)
rsp_system_get_counters(result=0, tx_packets=543, rx_packets=2000, crc_errors=195, failures=0)

>>> print(response.crc_errors)
195

>>> print(response[3])
195

>>> address, = l.bt.system.get_identity_address()
>>> print(address)
00:0b:57:49:2b:47

If a command fails and reports a non-zero result code, an exception is thrown, as follows:

>>> try:
...     l.bt.advertiser.start(0, 0, 0)
... except bgapi.bglib.CommandFailedError as e:
...     print("Error 0x{:x} received, "
...           "try to create an advertising set first."
...           .format(e.errorcode))
Error 0x21 received, try to create an advertising set first.

The received events are stored in an event queue, which can be accessed by functions, such as gen_events(). This function is a generator, which yields events from the queue as they are received. With the default parameters, it is non-blocking and stops as soon as no more events are received. Usually, to receive a single event, you'll set a timeout, the timeout parameter, and the maximum time the generator will run altogether, which is the max_time parameter. The following example resets the device and waits for a boot event for one second.

>>> l.bt.system.reset(0)
>>> for e in l.gen_events(timeout=None, max_time=1):
...     print("Received event: {}".format(e))
...     if e == 'bt_evt_system_boot':
...         print("Bluetooth stack booted: v{major}.{minor}.{patch}-b{build}".format(**vars(e)))
...         break
Received event: bt_evt_system_boot(major=3, minor=2, patch=0, build=169, bootloader=17563648, hw=1, hash=3698707457)
Bluetooth stack booted: v3.2.0-b169

Event object fields are accessed the same way as the response objects.

pybgapi's People

Contributors

peter-kerekes avatar silabs-beta avatar

Stargazers

JP Hutchins avatar Helmsman avatar Zhenlin An avatar

Watchers

 avatar Tabi Parker avatar Matthew Fine avatar  avatar  avatar  avatar

Forkers

anplus

pybgapi's Issues

How to request multiple characteristic values?

Issue Summary

I am facing a problem that I am unable to use the read_multiple_characteristic_values function from PYBGAPI. The documentation is rough and I couldn't find any examples of usage.

Environment Information

  • PYBGAPI Version: 1.2.0
  • Python Version: 3.9.6

Relevant Code Snippet

For simplicity I just use connection_handle=1.
As I understand the argument characteristic_list_len would be the just the length of the characteristic_list
and

characteristic_list = [self.conn_properties[1][CHARACTERISTICS_1_HANDLE], self.conn_properties[1][CHARACTERISTICS_2_HANDLE]]
# Characteristic list will be: [35, 40]
        self.lib.bt.gatt.read_multiple_characteristic_values(1, len(characteristic_list), characteristic_list)

The API call does fail with an IndexError: list index out of range. However I also tried to make sure that the characteristic_handle is formatted as little_endian with

characteristic_list = [struct.pack('H', char_handle) for char_handle in characteristic_list]

before calling self.lib.bt.gatt.read_multiple_characteristic_values(1, len(characteristic_list), characteristic_list)

The error is still the same. I also tried to set the length to zero and a couple of other things but the error was always the same.

I would appreciate any help to resolve the issue

Thank you and Best Regards

Unreliable

Hi

I use this library together with a thunderboard which I have installed the correct firmware to. I have a very simple applicaiton that should scan for a specific peripheral, connect to it, find a specific service / characteristic and write to it.

It works sometimes, but commonly for example when I connect and try to retrieve services, I dont get a response. I power cycle the thunderboard and it may work, and then stops working again. Why is it so unreliable?

import bgapi
import time

SERVICE_UUID = bytes.fromhex("708ec9bd-a35d-4f81-9474-49276f0be635".replace("-", ""))[::-1]
CHARACTERISTIC_UUID = bytes.fromhex("a31afb58-0cef-4656-b187-2eb62249d7b7".replace("-", ""))[::-1]

l = bgapi.BGLib(bgapi.SerialConnector('/dev/tty.usbmodem0004402316081'), 'sl_bt.xapi')
l.open()


reboot = l.bt.system.reset(0)

for e in l.gen_events(timeout=None, max_time=5):
    if e == 'bt_evt_system_boot':
        print (e)

address = l.bt.system.get_identity_address()
print(address)


scanner = l.bt.scanner.start(l.bt.scanner.SCAN_PHY_SCAN_PHY_1M, l.bt.scanner.DISCOVER_MODE_DISCOVER_OBSERVATION)
print (scanner)
time.sleep(5)
l.bt.scanner.stop()

devices = {}

address = None
address_type = None
for e in l.gen_events(timeout=None, max_time=1):
    if e == 'bt_evt_scanner_legacy_advertisement_report':
        if e.data.startswith(b"\x0c\xff\xfa\x00"):
            if e.address not in devices:
                devices[e.address] = e
                devices[e.address].scans = 1
                devices[e.address].sumrssi = e.rssi

            else:
                devices[e.address]
                devices[e.address].scans += 1
                devices[e.address].sumrssi += e.rssi

    else:
        print("Received event: {}".format(e))



selected_device = None
print ("Devices found: " + str(len(devices)))
for mac, device in devices.items():
    if device.sumrssi/device.scans <= -18: #too far away
        continue
    #TODO Check if it is in mac pending mode
    print (mac, device, device.sumrssi/device.scans)
    if selected_device == None or (selected_device.sumrssi/selected_device.scans<device.sumrssi/device.scans):
        selected_device = device
        address = e.address
        address_type = e.address_type

print ("selected device: " + str(selected_device))

if address == None:
    print ("Peripheral not found")
    quit()

print (address, address_type)

l.gen_events(timeout=None, max_time=5)
l.bt.connection.open(address, address_type, l.bt.gap.PHY_PHY_1M)

connection = None
for e in l.gen_events(timeout=None, max_time=1):
    if e == 'bt_evt_connection_opened':
        print ("bt_evt_connection_opened")
        connection = e.connection
        print (connection)
        break;

time.sleep(3)


service = None
while service == None:
    try:
        print ("Finding service...")
        l.gen_events(timeout=None, max_time=5)
        l.bt.gatt.discover_primary_services_by_uuid(connection, SERVICE_UUID)
        for e in l.gen_events(timeout=None, max_time=3):
            print ('bt_evt_gatt_service?=', e)
            if e == 'bt_evt_gatt_service':
                service = e.service
            if e == 'bt_evt_gatt_procedure_completed' and service != None:
                break;
        print ("service " + str(service))
    except:
        pass
        time.sleep(3)


characteristic = None
while characteristic == None:
    try:
        print ("Finding charactersitic...")
        l.gen_events(timeout=None, max_time=1)
        find_characteristic = l.bt.gatt.discover_characteristics_by_uuid(connection, service, CHARACTERISTIC_UUID)
        print ("find_characteristic " + str(find_characteristic))
        for e in l.gen_events(timeout=None, max_time=1):
            print("bt_evt_gatt_characteristic")
            if e == 'bt_evt_gatt_characteristic':
                characteristic = e.characteristic
                print (e)
                #break;
            if e == 'bt_evt_gatt_procedure_completed' and characteristic != None:
                break;
    except:
        pass

print ("characteristic " + str(characteristic))

Here is some sample output:

bt_evt_system_boot(major=6, minor=1, patch=0, build=176, bootloader=0, hw=258, hash=1218182300)
bt_rsp_system_get_identity_address(result=0, address='60:a4:23:c9:8c:43', type=0)
bt_rsp_scanner_start(result=0)
Devices found: 1
d0:16:f0:90:00:2e bt_evt_scanner_legacy_advertisement_report(event_flags=3, address='d0:16:f0:90:00:2e', address_type=0, bonding=255, rssi=-16, channel=38, target_address='00:00:00:00:00:00', target_address_type=0, data=b'\x0c\xff\xfa\x00\x04\x90\x00.\x00\x00\x00\x00\xef\x11\x07\xbc\xc8_\xcdB\xef\x85\xa8e@]\x83&\xabv\xa9') -16.02173913043478
selected device: bt_evt_scanner_legacy_advertisement_report(event_flags=3, address='d0:16:f0:90:00:2e', address_type=0, bonding=255, rssi=-16, channel=38, target_address='00:00:00:00:00:00', target_address_type=0, data=b'\x0c\xff\xfa\x00\x04\x90\x00.\x00\x00\x00\x00\xef\x11\x07\xbc\xc8_\xcdB\xef\x85\xa8e@]\x83&\xabv\xa9')
70:b8:f6:1f:26:6e 0
bt_evt_connection_opened
1
Finding service...
bt_evt_gatt_service?= bt_evt_connection_parameters(connection=1, interval=20, latency=0, timeout=100, security_mode=0, txsize=27)
bt_evt_gatt_service?= bt_evt_connection_phy_status(connection=1, phy=1)
bt_evt_gatt_service?= bt_evt_gatt_mtu_exchanged(connection=1, mtu=247)
bt_evt_gatt_service?= bt_evt_connection_remote_used_features(connection=1, features=b'/\x00\x00\x00\x00\x00\x00\x00')
bt_evt_gatt_service?= bt_evt_connection_data_length(connection=1, tx_data_len=251, tx_time_us=2120, rx_data_len=251, rx_time_us=2120)
bt_evt_gatt_service?= bt_evt_connection_parameters(connection=1, interval=36, latency=0, timeout=400, security_mode=0, txsize=251)
bt_evt_gatt_service?= bt_evt_gatt_procedure_completed(connection=1, result=0)
service None
Finding service...
bt_evt_gatt_service?= bt_evt_gatt_procedure_completed(connection=1, result=0)
service None
Finding service...
bt_evt_gatt_service?= bt_evt_gatt_procedure_completed(connection=1, result=0)
service None
Finding service...
bt_evt_gatt_service?= bt_evt_gatt_procedure_completed(connection=1, result=0)
service None
Finding service...
bt_evt_gatt_service?= bt_evt_gatt_procedure_completed(connection=1, result=0)
service None
Finding service...
bt_evt_gatt_service?= bt_evt_gatt_procedure_completed(connection=1, result=0)
service None
Finding service...
bt_evt_gatt_service?= bt_evt_gatt_procedure_completed(connection=1, result=0)
service None
Finding service...
bt_evt_gatt_service?= bt_evt_gatt_procedure_completed(connection=1, result=0)

How to request data from connected Server?

Issue Summary

I am facing a problem with my BLE Client's data request to a BLE Server. The issue is related to the pybgapi library, and I need assistance in resolving it.

[This issue was originally posted in the bgapi repository. However I think it is better suited at this repository because I am using the example code of pybgapi]

Here is the relevant code snippet:

if self.conn_state == "enable_indication":
    # ...
    self.lib.bt.gatt.read_characteristic_value(
        evt.connection, SENSOR_DATA_CHARACTERISTIC)

The Client is requesting data from the connected Server via a gatt client functions from the Bluetooth API Reference Guide.

I expect that a BLE stack event will be triggered on the client and I can extract the data from the event.

The SENSOR_DATA_CHARACTERISTIC variable is defined as:

#UUID = 34850bc9-95e5-4701-956c-656ce1afdc66
SENSOR_DATA_CHARACTERISTIC = b"\x66\xdc\xaf\xe1\x6c\x65\x6c\x95\x01\x47\xe5\x95\xc9\x0b\x85\x34"

I am encountering the following error:

File "/Users/username/Documents/GitHub/pybgapi-examples/lib/python3.11/site-packages/bgapi/apiparser.py", line 30, in toInt
    return int(s)
ValueError: invalid literal for int() with base 10: b'f\xdc\xaf\xe1lel\x95\x01G\xe5\x95\xc9\x0b\x854'

I also tried another variant where I casted the byte to an integer value, but I guess this was not a good idea neither.

    self.lib.bt.gatt.read_characteristic_value(
        evt.connection, int.from_bytes(SENSOR_DATA_CHARACTERISTIC, byteorder='little'))

Thank you for any advice in solving this issue and let me know if you need more information.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.