2018-07-30 12:10:10 -04:00
|
|
|
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
#
|
|
|
|
|
|
|
|
from __future__ import print_function
|
|
|
|
|
|
|
|
import platform
|
2021-01-25 21:49:01 -05:00
|
|
|
from builtins import input
|
2018-07-30 12:10:10 -04:00
|
|
|
|
|
|
|
import utils
|
2021-01-25 21:49:01 -05:00
|
|
|
from future.utils import iteritems
|
2018-07-30 12:10:10 -04:00
|
|
|
|
|
|
|
fallback = True
|
|
|
|
|
2018-12-01 08:23:34 -05:00
|
|
|
|
2018-07-30 12:10:10 -04:00
|
|
|
# Check if platform is Linux and required packages are installed
|
|
|
|
# else fallback to console mode
|
|
|
|
if platform.system() == 'Linux':
|
|
|
|
try:
|
2021-01-25 21:49:01 -05:00
|
|
|
import time
|
|
|
|
|
2018-07-30 12:10:10 -04:00
|
|
|
import dbus
|
|
|
|
import dbus.mainloop.glib
|
|
|
|
fallback = False
|
2018-12-01 08:23:34 -05:00
|
|
|
except ImportError:
|
2018-07-30 12:10:10 -04:00
|
|
|
pass
|
|
|
|
|
2018-12-01 08:23:34 -05:00
|
|
|
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
|
2018-07-30 12:10:10 -04:00
|
|
|
|
|
|
|
# BLE client (Linux Only) using Bluez and DBus
|
|
|
|
class BLE_Bluez_Client:
|
2019-05-09 17:36:56 -04:00
|
|
|
def connect(self, devname, iface, chrc_names, fallback_srv_uuid):
|
2018-07-30 12:10:10 -04:00
|
|
|
self.devname = devname
|
2019-05-09 17:36:56 -04:00
|
|
|
self.srv_uuid_fallback = fallback_srv_uuid
|
|
|
|
self.chrc_names = [name.lower() for name in chrc_names]
|
2018-07-30 12:10:10 -04:00
|
|
|
self.device = None
|
|
|
|
self.adapter = None
|
|
|
|
self.adapter_props = None
|
|
|
|
self.services = None
|
2019-05-09 17:36:56 -04:00
|
|
|
self.nu_lookup = None
|
|
|
|
self.characteristics = dict()
|
|
|
|
self.srv_uuid_adv = None
|
2018-07-30 12:10:10 -04:00
|
|
|
|
|
|
|
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
|
|
|
bus = dbus.SystemBus()
|
2021-01-25 21:49:01 -05:00
|
|
|
manager = dbus.Interface(bus.get_object('org.bluez', '/'), 'org.freedesktop.DBus.ObjectManager')
|
2018-07-30 12:10:10 -04:00
|
|
|
objects = manager.GetManagedObjects()
|
|
|
|
|
2019-05-09 17:36:56 -04:00
|
|
|
for path, interfaces in iteritems(objects):
|
2021-01-25 21:49:01 -05:00
|
|
|
adapter = interfaces.get('org.bluez.Adapter1')
|
2018-12-01 08:23:34 -05:00
|
|
|
if adapter is not None:
|
2018-07-30 12:10:10 -04:00
|
|
|
if path.endswith(iface):
|
2021-01-25 21:49:01 -05:00
|
|
|
self.adapter = dbus.Interface(bus.get_object('org.bluez', path), 'org.bluez.Adapter1')
|
|
|
|
self.adapter_props = dbus.Interface(bus.get_object('org.bluez', path), 'org.freedesktop.DBus.Properties')
|
2018-07-30 12:10:10 -04:00
|
|
|
break
|
|
|
|
|
2018-12-01 08:23:34 -05:00
|
|
|
if self.adapter is None:
|
2021-01-25 21:49:01 -05:00
|
|
|
raise RuntimeError('Bluetooth adapter not found')
|
2018-07-30 12:10:10 -04:00
|
|
|
|
2021-01-25 21:49:01 -05:00
|
|
|
self.adapter_props.Set('org.bluez.Adapter1', 'Powered', dbus.Boolean(1))
|
2018-07-30 12:10:10 -04:00
|
|
|
self.adapter.StartDiscovery()
|
|
|
|
|
|
|
|
retry = 10
|
|
|
|
while (retry > 0):
|
|
|
|
try:
|
2018-12-01 08:23:34 -05:00
|
|
|
if self.device is None:
|
2021-01-25 21:49:01 -05:00
|
|
|
print('Connecting...')
|
2018-07-30 12:10:10 -04:00
|
|
|
# Wait for device to be discovered
|
|
|
|
time.sleep(5)
|
|
|
|
self._connect_()
|
2021-01-25 21:49:01 -05:00
|
|
|
print('Connected')
|
|
|
|
print('Getting Services...')
|
2018-07-30 12:10:10 -04:00
|
|
|
# Wait for services to be discovered
|
|
|
|
time.sleep(5)
|
|
|
|
self._get_services_()
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
|
|
print(e)
|
|
|
|
retry -= 1
|
2021-01-25 21:49:01 -05:00
|
|
|
print('Retries left', retry)
|
2018-07-30 12:10:10 -04:00
|
|
|
continue
|
|
|
|
self.adapter.StopDiscovery()
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _connect_(self):
|
|
|
|
bus = dbus.SystemBus()
|
2021-01-25 21:49:01 -05:00
|
|
|
manager = dbus.Interface(bus.get_object('org.bluez', '/'), 'org.freedesktop.DBus.ObjectManager')
|
2018-07-30 12:10:10 -04:00
|
|
|
objects = manager.GetManagedObjects()
|
|
|
|
dev_path = None
|
2019-05-09 17:36:56 -04:00
|
|
|
for path, interfaces in iteritems(objects):
|
2021-01-25 21:49:01 -05:00
|
|
|
if 'org.bluez.Device1' not in interfaces:
|
2018-07-30 12:10:10 -04:00
|
|
|
continue
|
2021-01-25 21:49:01 -05:00
|
|
|
if interfaces['org.bluez.Device1'].get('Name') == self.devname:
|
2018-07-30 12:10:10 -04:00
|
|
|
dev_path = path
|
|
|
|
break
|
|
|
|
|
2018-12-01 08:23:34 -05:00
|
|
|
if dev_path is None:
|
2021-01-25 21:49:01 -05:00
|
|
|
raise RuntimeError('BLE device not found')
|
2018-07-30 12:10:10 -04:00
|
|
|
|
|
|
|
try:
|
2021-01-25 21:49:01 -05:00
|
|
|
self.device = bus.get_object('org.bluez', dev_path)
|
2019-05-09 17:36:56 -04:00
|
|
|
try:
|
|
|
|
uuids = self.device.Get('org.bluez.Device1', 'UUIDs',
|
|
|
|
dbus_interface='org.freedesktop.DBus.Properties')
|
|
|
|
# There should be 1 service UUID in advertising data
|
|
|
|
# If bluez had cached an old version of the advertisement data
|
|
|
|
# the list of uuids may be incorrect, in which case connection
|
|
|
|
# or service discovery may fail the first time. If that happens
|
|
|
|
# the cache will be refreshed before next retry
|
|
|
|
if len(uuids) == 1:
|
|
|
|
self.srv_uuid_adv = uuids[0]
|
|
|
|
except dbus.exceptions.DBusException as e:
|
|
|
|
print(e)
|
|
|
|
|
2018-07-30 12:10:10 -04:00
|
|
|
self.device.Connect(dbus_interface='org.bluez.Device1')
|
|
|
|
except Exception as e:
|
|
|
|
print(e)
|
|
|
|
self.device = None
|
2021-01-25 21:49:01 -05:00
|
|
|
raise RuntimeError('BLE device could not connect')
|
2018-07-30 12:10:10 -04:00
|
|
|
|
|
|
|
def _get_services_(self):
|
|
|
|
bus = dbus.SystemBus()
|
2021-01-25 21:49:01 -05:00
|
|
|
manager = dbus.Interface(bus.get_object('org.bluez', '/'), 'org.freedesktop.DBus.ObjectManager')
|
2018-07-30 12:10:10 -04:00
|
|
|
objects = manager.GetManagedObjects()
|
2019-05-09 17:36:56 -04:00
|
|
|
service_found = False
|
|
|
|
for srv_path, srv_interfaces in iteritems(objects):
|
2021-01-25 21:49:01 -05:00
|
|
|
if 'org.bluez.GattService1' not in srv_interfaces:
|
2019-05-09 17:36:56 -04:00
|
|
|
continue
|
|
|
|
if not srv_path.startswith(self.device.object_path):
|
2018-07-30 12:10:10 -04:00
|
|
|
continue
|
2021-01-25 21:49:01 -05:00
|
|
|
service = bus.get_object('org.bluez', srv_path)
|
2019-05-09 17:36:56 -04:00
|
|
|
srv_uuid = service.Get('org.bluez.GattService1', 'UUID',
|
2018-12-01 08:23:34 -05:00
|
|
|
dbus_interface='org.freedesktop.DBus.Properties')
|
2019-05-09 17:36:56 -04:00
|
|
|
|
|
|
|
# If service UUID doesn't match the one found in advertisement data
|
|
|
|
# then also check if it matches the fallback UUID
|
|
|
|
if srv_uuid not in [self.srv_uuid_adv, self.srv_uuid_fallback]:
|
|
|
|
continue
|
|
|
|
|
|
|
|
nu_lookup = dict()
|
|
|
|
characteristics = dict()
|
|
|
|
for chrc_path, chrc_interfaces in iteritems(objects):
|
2021-01-25 21:49:01 -05:00
|
|
|
if 'org.bluez.GattCharacteristic1' not in chrc_interfaces:
|
2019-05-09 17:36:56 -04:00
|
|
|
continue
|
|
|
|
if not chrc_path.startswith(service.object_path):
|
|
|
|
continue
|
2021-01-25 21:49:01 -05:00
|
|
|
chrc = bus.get_object('org.bluez', chrc_path)
|
2019-05-09 17:36:56 -04:00
|
|
|
uuid = chrc.Get('org.bluez.GattCharacteristic1', 'UUID',
|
|
|
|
dbus_interface='org.freedesktop.DBus.Properties')
|
|
|
|
characteristics[uuid] = chrc
|
|
|
|
for desc_path, desc_interfaces in iteritems(objects):
|
2021-01-25 21:49:01 -05:00
|
|
|
if 'org.bluez.GattDescriptor1' not in desc_interfaces:
|
2019-05-09 17:36:56 -04:00
|
|
|
continue
|
|
|
|
if not desc_path.startswith(chrc.object_path):
|
|
|
|
continue
|
2021-01-25 21:49:01 -05:00
|
|
|
desc = bus.get_object('org.bluez', desc_path)
|
2019-05-09 17:36:56 -04:00
|
|
|
desc_uuid = desc.Get('org.bluez.GattDescriptor1', 'UUID',
|
|
|
|
dbus_interface='org.freedesktop.DBus.Properties')
|
|
|
|
if desc_uuid[4:8] != '2901':
|
|
|
|
continue
|
|
|
|
try:
|
|
|
|
readval = desc.ReadValue({}, dbus_interface='org.bluez.GattDescriptor1')
|
|
|
|
except dbus.exceptions.DBusException:
|
|
|
|
break
|
|
|
|
found_name = ''.join(chr(b) for b in readval).lower()
|
|
|
|
nu_lookup[found_name] = uuid
|
2018-07-30 12:10:10 -04:00
|
|
|
break
|
|
|
|
|
2019-05-09 17:36:56 -04:00
|
|
|
match_found = True
|
|
|
|
for name in self.chrc_names:
|
|
|
|
if name not in nu_lookup:
|
|
|
|
# Endpoint name not present
|
|
|
|
match_found = False
|
|
|
|
break
|
|
|
|
|
|
|
|
# Create lookup table only if all endpoint names found
|
|
|
|
self.nu_lookup = [None, nu_lookup][match_found]
|
|
|
|
self.characteristics = characteristics
|
|
|
|
service_found = True
|
|
|
|
|
|
|
|
# If the service UUID matches that in the advertisement
|
|
|
|
# we can stop the search now. If it doesn't match, we
|
|
|
|
# have found the service corresponding to the fallback
|
|
|
|
# UUID, in which case don't break and keep searching
|
|
|
|
# for the advertised service
|
|
|
|
if srv_uuid == self.srv_uuid_adv:
|
|
|
|
break
|
|
|
|
|
|
|
|
if not service_found:
|
2018-11-08 02:44:54 -05:00
|
|
|
self.device.Disconnect(dbus_interface='org.bluez.Device1')
|
2019-05-09 17:36:56 -04:00
|
|
|
if self.adapter:
|
|
|
|
self.adapter.RemoveDevice(self.device)
|
2018-11-08 02:44:54 -05:00
|
|
|
self.device = None
|
2019-05-09 17:36:56 -04:00
|
|
|
self.nu_lookup = None
|
|
|
|
self.characteristics = dict()
|
2021-01-25 21:49:01 -05:00
|
|
|
raise RuntimeError('Provisioning service not found')
|
2018-07-30 12:10:10 -04:00
|
|
|
|
2019-05-09 17:36:56 -04:00
|
|
|
def get_nu_lookup(self):
|
|
|
|
return self.nu_lookup
|
2018-07-30 12:10:10 -04:00
|
|
|
|
|
|
|
def has_characteristic(self, uuid):
|
2019-05-09 17:36:56 -04:00
|
|
|
if uuid in self.characteristics:
|
2018-07-30 12:10:10 -04:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def disconnect(self):
|
|
|
|
if self.device:
|
|
|
|
self.device.Disconnect(dbus_interface='org.bluez.Device1')
|
|
|
|
if self.adapter:
|
|
|
|
self.adapter.RemoveDevice(self.device)
|
2018-11-08 02:44:54 -05:00
|
|
|
self.device = None
|
2019-05-09 17:36:56 -04:00
|
|
|
self.nu_lookup = None
|
|
|
|
self.characteristics = dict()
|
2018-07-30 12:10:10 -04:00
|
|
|
if self.adapter_props:
|
2021-01-25 21:49:01 -05:00
|
|
|
self.adapter_props.Set('org.bluez.Adapter1', 'Powered', dbus.Boolean(0))
|
2018-07-30 12:10:10 -04:00
|
|
|
|
|
|
|
def send_data(self, characteristic_uuid, data):
|
|
|
|
try:
|
|
|
|
path = self.characteristics[characteristic_uuid]
|
|
|
|
except KeyError:
|
2021-01-25 21:49:01 -05:00
|
|
|
raise RuntimeError('Invalid characteristic : ' + characteristic_uuid)
|
2019-03-14 07:58:26 -04:00
|
|
|
|
|
|
|
try:
|
|
|
|
path.WriteValue([ord(c) for c in data], {}, dbus_interface='org.bluez.GattCharacteristic1')
|
2020-11-10 22:31:17 -05:00
|
|
|
except TypeError: # python3 compatible
|
|
|
|
path.WriteValue([c for c in data], {}, dbus_interface='org.bluez.GattCharacteristic1')
|
2019-03-14 07:58:26 -04:00
|
|
|
except dbus.exceptions.DBusException as e:
|
2021-01-25 21:49:01 -05:00
|
|
|
raise RuntimeError('Failed to write value to characteristic ' + characteristic_uuid + ': ' + str(e))
|
2019-03-14 07:58:26 -04:00
|
|
|
|
|
|
|
try:
|
|
|
|
readval = path.ReadValue({}, dbus_interface='org.bluez.GattCharacteristic1')
|
|
|
|
except dbus.exceptions.DBusException as e:
|
2021-01-25 21:49:01 -05:00
|
|
|
raise RuntimeError('Failed to read value from characteristic ' + characteristic_uuid + ': ' + str(e))
|
2019-03-14 07:58:26 -04:00
|
|
|
return ''.join(chr(b) for b in readval)
|
2018-07-30 12:10:10 -04:00
|
|
|
|
2018-12-01 08:23:34 -05:00
|
|
|
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
|
2018-07-30 12:10:10 -04:00
|
|
|
|
|
|
|
# Console based BLE client for Cross Platform support
|
|
|
|
class BLE_Console_Client:
|
2019-05-09 17:36:56 -04:00
|
|
|
def connect(self, devname, iface, chrc_names, fallback_srv_uuid):
|
2021-01-25 21:49:01 -05:00
|
|
|
print('BLE client is running in console mode')
|
|
|
|
print('\tThis could be due to your platform not being supported or dependencies not being met')
|
|
|
|
print('\tPlease ensure all pre-requisites are met to run the full fledged client')
|
|
|
|
print('BLECLI >> Please connect to BLE device `' + devname + '` manually using your tool of choice')
|
|
|
|
resp = input('BLECLI >> Was the device connected successfully? [y/n] ')
|
2018-07-30 12:10:10 -04:00
|
|
|
if resp != 'Y' and resp != 'y':
|
|
|
|
return False
|
2021-01-25 21:49:01 -05:00
|
|
|
print('BLECLI >> List available attributes of the connected device')
|
2019-05-09 17:36:56 -04:00
|
|
|
resp = input("BLECLI >> Is the service UUID '" + fallback_srv_uuid + "' listed among available attributes? [y/n] ")
|
2018-07-30 12:10:10 -04:00
|
|
|
if resp != 'Y' and resp != 'y':
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2019-05-09 17:36:56 -04:00
|
|
|
def get_nu_lookup(self):
|
|
|
|
return None
|
|
|
|
|
2018-07-30 12:10:10 -04:00
|
|
|
def has_characteristic(self, uuid):
|
|
|
|
resp = input("BLECLI >> Is the characteristic UUID '" + uuid + "' listed among available attributes? [y/n] ")
|
|
|
|
if resp != 'Y' and resp != 'y':
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def disconnect(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def send_data(self, characteristic_uuid, data):
|
|
|
|
print("BLECLI >> Write following data to characteristic with UUID '" + characteristic_uuid + "' :")
|
2021-01-25 21:49:01 -05:00
|
|
|
print('\t>> ' + utils.str_to_hexstr(data))
|
|
|
|
print('BLECLI >> Enter data read from characteristic (in hex) :')
|
|
|
|
resp = input('\t<< ')
|
2018-07-30 12:10:10 -04:00
|
|
|
return utils.hexstr_to_str(resp)
|
|
|
|
|
2018-12-01 08:23:34 -05:00
|
|
|
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
|
2018-07-30 12:10:10 -04:00
|
|
|
|
|
|
|
# Function to get client instance depending upon platform
|
|
|
|
def get_client():
|
|
|
|
if fallback:
|
|
|
|
return BLE_Console_Client()
|
|
|
|
return BLE_Bluez_Client()
|