Mstdlib-1.24.0
|
Typedefs | |
typedef struct M_io_ble_enum | M_io_ble_enum_t |
Enumerations | |
enum | M_io_ble_wtype_t { M_IO_BLE_WTYPE_WRITE = 0 , M_IO_BLE_WTYPE_WRITENORESP , M_IO_BLE_WTYPE_REQVAL , M_IO_BLE_WTYPE_REQRSSI , M_IO_BLE_WTYPE_REQNOTIFY } |
enum | M_io_ble_rtype_t { M_IO_BLE_RTYPE_READ = 0 , M_IO_BLE_RTYPE_RSSI , M_IO_BLE_RTYPE_NOTIFY } |
enum | M_io_ble_property_t { M_IO_BLE_PROPERTY_NONE = 0 , M_IO_BLE_PROPERTY_READ = 1 << 0 , M_IO_BLE_PROPERTY_WRITE = 1 << 1 , M_IO_BLE_PROPERTY_WRITENORESP = 1 << 2 , M_IO_BLE_PROPERTY_NOTIFY = 1 << 3 } |
Bluetooth LE (Low Energy) IO functions.
Supported OS:
BLE was designed to keep energy consumption to a minimum and allow for seamless device access. Unlike Bluetooth classic, devices are not paired to the system. Typical use is to scan for available devices, inspect their services, and connect to a device that provides services the application wants to use. A good example is a heart rate monitor.
A health app doesn't care which heart rate monitor is being used it only cares about getting heart rate data. Typically, the user will be presented with a list of suitable devices in case multiple devices are detected (for example, multiple people going on a bike ride together).
Since there is no pairing the device must be found by scanning for available devices. All devices that have been found during a scan (excluding ones that have been pruned) will be listed as part of device enumeration. This means devices may no longer be present. Such as an iPhone being seen during scanning and later the owner of the phone leaving the room. There are no OS level events to notify that this has happened.
When necessary, a scan can be initiated by trying to connect to a device. Opening a device requires specifying a device identifier or service UUID and if not found a scan will be started internally for either the duration of the timeout or until the device has been found. This can cause a delay between trying to open a device and receiving CONNECT or ERROR events.
Device identifiers will vary between OS's. macOS for example assigns a device specific UUID to devices it sees. Android returns the device's MAC address. There is no way to read a device's MAC address on macOS. Identifiers are subject to change periodically. iOS will randomly change the device's MAC address every few hours to prevent tracking.
BLE devices provide services and there can be multiple services. Services provide characteristics and there can be multiple characteristics per service. Both services and characteristics can be defined using standardized profiles. See the Bluetooth GATT specifications.
Since there are multiple, potentially, read and write end points it is required to specify the service and characteristic UUIDs. A write event must have them specified using the M_io_meta_t and associated BLE meta functions. A read will fill a provided meta object with the service and characteristic the data came from. This means only the read and write meta functions can be use with BLE. The non-meta functions will return an error.
Characteristics can have multiple properties.
BLE by default is not a stream-based protocol like serial, HID, or Bluetooth classic. Characteristics with the read property can be requested to read data. This is an async request. M_io_ble facilitates this by using M_io_write_meta with a property indicator that specifies data is not being written but a request for read is being sent.
Characteristics with the notify or indicate property can be subscribed to which will have them issue read events similar to a stream protocol. Reads will still require a meta object to specify which service and characteristic the data is from. Manual read requests may still be necessary. Notify and Indicate events are left to the device to initiate. The device may have internal rules which limit how often events are triggered. For example a heart rate monitor could notify every 2 seconds even though it's reading every 100 ms. A time service might send an event every second or it might send an event every minute.
Characteristics won't receive read events by default. They need to be subscribed to first. Subscriptions will not service a disconnect or destroy of an io object. Also, not all characteristics support this property even if it supports read. Conversely some support notify/indicate but not read.
Write will write data to the device and the OS will issue an event whether the write succeeded or failed. Mstdlib uses this to determine if there was a write error and will block subsequent writes (returns WOULDBLOCK) until an outstanding write has completed.
Write without response is a blind write. No result is requested from the OS. The state of the write is not known after it is sent.
When subscribing to a notification a write must take place with no data in order for the even to be registered. Typically, you'll want to register for events after connect (since a write is needed to initiate the registration. Once the even it is registered a M_EVENT_TYPE_READ
event will be generated with the M_IO_BLE_RTYPE_NOTIFY
meta type set. There will be no data present. This is an indicator registration was successful.
Register on connect event:
Check the BLE read type if notification registration was complete. While not pictured, you should also check the service and characteristic that generated the event if multiple notification events are being registered to determine which this belongs to.
BLE events are only delivered to the main run loop. This is a design decision by Apple. It is not possible to use an different run loop to receive events like can be done with classic Bluetooth or HID. BLE events are non-blocking so there shouldn't be any performance impact with the events being delivered. As little work as possible is performed during event processing to limit any impact of this design requirement.
A C application will need to manually start the macOS main runloop, otherwise no events will be delivered and no BLE operations will work.
typedef struct M_io_ble_enum M_io_ble_enum_t |
enum M_io_ble_wtype_t |
Meta property types used by M_io_write_meta.
Specifies how the write should function.
enum M_io_ble_rtype_t |
Meta types used by M_io_read_meta.
Specifies what type of read is being returned.
enum M_io_ble_property_t |
Characteristic properties.
This is the subset of properties currently supported and used for interaction with the device. Other types of properties, such as extended, properties or ones indicating encryption requirements are not currently included.
Enumerator | |
---|---|
M_IO_BLE_PROPERTY_NONE | |
M_IO_BLE_PROPERTY_READ | |
M_IO_BLE_PROPERTY_WRITE | |
M_IO_BLE_PROPERTY_WRITENORESP | |
M_IO_BLE_PROPERTY_NOTIFY |
M_bool M_io_ble_scan | ( | M_event_t * | event, |
M_event_callback_t | callback, | ||
void * | cb_data, | ||
M_uint64 | timeout_ms | ||
) |
Start a BLE scan.
A scan needs to take place for nearby devices to be found. Once found they will appear in an enumeration.
Opening a known device does not require explicitly scanning. Scanning will happen implicitly if the device has not been seen before.
[out] | event | Event handle to receive scan events. |
[in] | callback | User-specified callback to call when the scan finishes |
[in] | cb_data | Optional. User-specified data supplied to user-specified callback when executed. |
[in] | timeout_ms | How long the scan should run before stopping. 0 will default to 1 minute. Scanning for devices can take a long time. During testing of a simple pedometer times upwards of 50 seconds were seen before the device was detected while sitting 6 inches away. A maximum of 5 minutes is allowed. Any amount larger will be reduced to the max. |
M_io_ble_enum_t * M_io_ble_enum | ( | void | ) |
Create a ble enumeration object.
You must call M_io_ble_scan to populate the enumeration. Failing to do so will result in an empty enumeration.
Use to determine what ble devices are connected and what services are being offered. This is a list of associated devices not necessarily what's actively connected.
The enumeration is based on available services. Meaning a device may be listed multiple times if it exposes multiple services.
void M_io_ble_enum_destroy | ( | M_io_ble_enum_t * | btenum | ) |
Destroy a ble enumeration object.
[in] | btenum | Bluetooth enumeration object. |
size_t M_io_ble_enum_count | ( | const M_io_ble_enum_t * | btenum | ) |
Number of ble objects in the enumeration.
[in] | btenum | Bluetooth enumeration object. |
const char * M_io_ble_enum_identifier | ( | const M_io_ble_enum_t * | btenum, |
size_t | idx | ||
) |
UUID of ble device.
[in] | btenum | Bluetooth enumeration object. |
[in] | idx | Index in ble enumeration. |
const char * M_io_ble_enum_name | ( | const M_io_ble_enum_t * | btenum, |
size_t | idx | ||
) |
Name of ble device as reported by the device.
[in] | btenum | Bluetooth enumeration object. |
[in] | idx | Index in ble enumeration. |
M_list_str_t * M_io_ble_enum_service_uuids | ( | const M_io_ble_enum_t * | btenum, |
size_t | idx | ||
) |
UUIDs of services reported by device.
This could be empty if the device has not been opened. Some devices do not advertise this unless they are opened and interrogated.
[in] | btenum | Bluetooth enumeration object. |
[in] | idx | Index in ble enumeration. |
M_time_t M_io_ble_enum_last_seen | ( | const M_io_ble_enum_t * | btenum, |
size_t | idx | ||
) |
Last time the device was seen.
Run a scan to ensure this is up to date. Opening a device will also update the last seen time.
[in] | btenum | Bluetooth enumeration object. |
[in] | idx | Index in ble enumeration. |
M_io_error_t M_io_ble_create | ( | M_io_t ** | io_out, |
const char * | identifier, | ||
M_uint64 | timeout_ms | ||
) |
Create a ble connection.
[out] | io_out | io object for communication. |
[in] | identifier | Required identifier of the device. |
[in] | timeout_ms | If the device has not already been seen a scan will be performed. This time out is how long we should wait to search for the device. |
M_io_error_t M_io_ble_create_with_service | ( | M_io_t ** | io_out, |
const char * | service_uuid, | ||
M_uint64 | timeout_ms | ||
) |
Create a ble connection to a device that provides a given service.
This connects to the first device found exposing the requested service.
[out] | io_out | io object for communication. |
[in] | service_uuid | UUID of service. |
[in] | timeout_ms | If the device has not already been seen a scan will be performed. This time out is how long we should wait to search for the device. |
char * M_io_ble_get_identifier | ( | M_io_t * | io | ) |
Get the device identifier
[in] | io | io object. |
char * M_io_ble_get_name | ( | M_io_t * | io | ) |
Get the device name
[in] | io | io object. |
M_list_str_t * M_io_ble_get_services | ( | M_io_t * | io | ) |
Get a list of service UUIDs provided by the device.
[in] | io | io object. |
M_list_str_t * M_io_ble_get_service_characteristics | ( | M_io_t * | io, |
const char * | service_uuid | ||
) |
Get a list of characteristic UUIDs provided a service provided by the device.
[in] | io | io object. |
[in] | service_uuid | UUID of service. |
M_io_ble_property_t M_io_ble_get_characteristic_properties | ( | M_io_t * | io, |
const char * | service_uuid, | ||
const char * | characteristic_uuid | ||
) |
Get the properties for the specific characteristic.
[in] | io | io object. |
[in] | service_uuid | UUID of service. |
[in] | characteristic_uuid | UUID of characteristic. |
void M_io_ble_get_max_write_sizes | ( | M_io_t * | io, |
size_t * | with_response, | ||
size_t * | without_response | ||
) |
Get the maximum write sizes from an io object.
Queries the highest BLE layer in the stack, if there are more than one.
[in] | io | io object. |
[out] | with_response | The maximum size that will receive a response. |
[out] | without_response | The maximum size that will not receive a response. |
M_bool M_io_ble_get_last_write_characteristic | ( | M_io_t * | io, |
char ** | service_uuid, | ||
char ** | characteristic_uuid | ||
) |
Get the last service and characteristic uuids that generated a write event.
Write with response will generate a write event when the data is written. This allows getting the last service and characteristic uuid that generated a write event in order to differentiate writes to multiple characteristics. Due to write events all being on the same event loop, calling this function in a write event will always correspond to the service and characteristic that generated the event.
Once called the information will be removed so the next call will be information for the next write event. Calling multiple times or not calling in a write event can cause the event vs data from this function to become out of sync.
Use of this function is optional and is not be necessary if only writing once service and one characteristic.
[in] | io | io object. |
[out] | service_uuid | UUID of service. |
[out] | characteristic_uuid | UUID of characteristic. |
const char * M_io_ble_meta_get_service | ( | M_io_t * | io, |
M_io_meta_t * | meta | ||
) |
Get the service associated with a meta object.
[in] | io | io object. |
[in] | meta | Meta. |
const char * M_io_ble_meta_get_characteristic | ( | M_io_t * | io, |
M_io_meta_t * | meta | ||
) |
Get the characteristic associated with a meta object.
[in] | io | io object. |
[in] | meta | Meta. |
M_io_ble_wtype_t M_io_ble_meta_get_write_type | ( | M_io_t * | io, |
M_io_meta_t * | meta | ||
) |
Get the write type.
[in] | io | io object. |
[in] | meta | Meta. |
M_io_ble_rtype_t M_io_ble_meta_get_read_type | ( | M_io_t * | io, |
M_io_meta_t * | meta | ||
) |
Get the read type.
[in] | io | io object. |
[in] | meta | Meta. |
M_bool M_io_ble_meta_get_rssi | ( | M_io_t * | io, |
M_io_meta_t * | meta, | ||
M_int64 * | rssi | ||
) |
Get the RSSI value from an RSSI read.
RSSI value is in decibels.
[in] | io | io object. |
[in] | meta | Meta. |
[out] | rssi | RSSI value. |
void M_io_ble_meta_set_service | ( | M_io_t * | io, |
M_io_meta_t * | meta, | ||
const char * | service_uuid | ||
) |
Set the service associated with a meta object.
[in] | io | io object. |
[in] | meta | Meta. |
[in] | service_uuid | UUID of service. |
void M_io_ble_meta_set_characteristic | ( | M_io_t * | io, |
M_io_meta_t * | meta, | ||
const char * | characteristic_uuid | ||
) |
Set the characteristic associated with a meta object.
[in] | io | io object. |
[in] | meta | Meta. |
[in] | characteristic_uuid | UUID of characteristic. |
void M_io_ble_meta_set_notify | ( | M_io_t * | io, |
M_io_meta_t * | meta, | ||
M_bool | enable | ||
) |
Set whether to receive notifications for characteristic data changes
If not called the default is to enable notifications.
Not all characteristic's support notifications. If not supported polling with M_io_write_meta using M_IO_BLE_WTYPE_REQVAL is the only way to retrieve the current value.
[in] | io | io object. |
[in] | meta | Meta. |
[in] | enable | Enable or disable receiving notifications. |
void M_io_ble_meta_set_write_type | ( | M_io_t * | io, |
M_io_meta_t * | meta, | ||
M_io_ble_wtype_t | type | ||
) |
Set whether a write should be blind.
If the type is not set, the default is to have writes wait for confirmation response before subsequent writes will be allowed.
[in] | io | io object. |
[in] | meta | Meta. |
[in] | type | Property controlling |