forked from espressif/esp-idf
esp_prov: switch from bluez/dbus to bleak
to enable multiplatform ble compatibility
This commit is contained in:
@@ -2199,11 +2199,6 @@ tools/esp_prov/security/security.py
|
|||||||
tools/esp_prov/security/security0.py
|
tools/esp_prov/security/security0.py
|
||||||
tools/esp_prov/security/security1.py
|
tools/esp_prov/security/security1.py
|
||||||
tools/esp_prov/transport/__init__.py
|
tools/esp_prov/transport/__init__.py
|
||||||
tools/esp_prov/transport/ble_cli.py
|
|
||||||
tools/esp_prov/transport/transport.py
|
|
||||||
tools/esp_prov/transport/transport_ble.py
|
|
||||||
tools/esp_prov/transport/transport_console.py
|
|
||||||
tools/esp_prov/transport/transport_http.py
|
|
||||||
tools/esp_prov/utils/__init__.py
|
tools/esp_prov/utils/__init__.py
|
||||||
tools/find_apps.py
|
tools/find_apps.py
|
||||||
tools/find_build_apps/__init__.py
|
tools/find_build_apps/__init__.py
|
||||||
|
@@ -86,15 +86,12 @@ Usage of `esp-prov` assumes that the provisioning app has specific protocomm end
|
|||||||
|
|
||||||
# AVAILABILITY
|
# AVAILABILITY
|
||||||
|
|
||||||
`esp_prov` is intended as a cross-platform tool, but currently BLE communication functionality is only available on Linux (via BlueZ and DBus)
|
|
||||||
|
|
||||||
For Android, a provisioning tool along with source code is available [here](https://github.com/espressif/esp-idf-provisioning-android)
|
For Android, a provisioning tool along with source code is available [here](https://github.com/espressif/esp-idf-provisioning-android)
|
||||||
|
|
||||||
On macOS and Windows, running with `--transport ble` option falls back to console mode, ie. write data and target UUID are printed to STDOUT and read data is input through STDIN. Users are free to use their app of choice to connect to the BLE device, send the write data to the target characteristic and read from it.
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
This requires the following python libraries to run (included in requirements.txt):
|
This requires the following python libraries to run (included in requirements.txt):
|
||||||
|
* `bleak`
|
||||||
* `future`
|
* `future`
|
||||||
* `protobuf`
|
* `protobuf`
|
||||||
* `cryptography`
|
* `cryptography`
|
||||||
@@ -103,14 +100,6 @@ Run `pip install -r $IDF_PATH/tools/esp_prov/requirements.txt`
|
|||||||
|
|
||||||
Note :
|
Note :
|
||||||
* The packages listed in requirements.txt are limited only to the ones needed AFTER fully satisfying the requirements of ESP-IDF
|
* The packages listed in requirements.txt are limited only to the ones needed AFTER fully satisfying the requirements of ESP-IDF
|
||||||
* BLE communication is only supported on Linux (via Bluez and DBus), therefore, the dependencies for this have been made optional
|
|
||||||
|
|
||||||
## Optional Dependencies (Linux Only)
|
|
||||||
|
|
||||||
These dependencies are for enabling communication with BLE devices using Bluez and DBus on Linux:
|
|
||||||
* `dbus-python`
|
|
||||||
|
|
||||||
Run `pip install -r $IDF_PATH/tools/esp_prov/requirements_linux_extra.txt`
|
|
||||||
|
|
||||||
# EXAMPLE USAGE
|
# EXAMPLE USAGE
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -50,7 +51,7 @@ def get_security(secver, username, password, pop='', verbose=False):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_transport(sel_transport, service_name):
|
async def get_transport(sel_transport, service_name):
|
||||||
try:
|
try:
|
||||||
tp = None
|
tp = None
|
||||||
if (sel_transport == 'softap'):
|
if (sel_transport == 'softap'):
|
||||||
@@ -68,9 +69,9 @@ def get_transport(sel_transport, service_name):
|
|||||||
# in which case, the automated discovery will fail and the client
|
# in which case, the automated discovery will fail and the client
|
||||||
# will fallback to using the provided UUIDs instead
|
# will fallback to using the provided UUIDs instead
|
||||||
nu_lookup = {'prov-session': 'ff51', 'prov-config': 'ff52', 'proto-ver': 'ff53'}
|
nu_lookup = {'prov-session': 'ff51', 'prov-config': 'ff52', 'proto-ver': 'ff53'}
|
||||||
tp = transport.Transport_BLE(devname=service_name,
|
tp = transport.Transport_BLE(service_uuid='021a9004-0382-4aea-bff4-6b3f1c5adfb4',
|
||||||
service_uuid='021a9004-0382-4aea-bff4-6b3f1c5adfb4',
|
|
||||||
nu_lookup=nu_lookup)
|
nu_lookup=nu_lookup)
|
||||||
|
await tp.connect(devname=service_name)
|
||||||
elif (sel_transport == 'console'):
|
elif (sel_transport == 'console'):
|
||||||
tp = transport.Transport_Console()
|
tp = transport.Transport_Console()
|
||||||
return tp
|
return tp
|
||||||
@@ -79,9 +80,9 @@ def get_transport(sel_transport, service_name):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def version_match(tp, protover, verbose=False):
|
async def version_match(tp, protover, verbose=False):
|
||||||
try:
|
try:
|
||||||
response = tp.send_data('proto-ver', protover)
|
response = await tp.send_data('proto-ver', protover)
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print('proto-ver response : ', response)
|
print('proto-ver response : ', response)
|
||||||
@@ -108,11 +109,11 @@ def version_match(tp, protover, verbose=False):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def has_capability(tp, capability='none', verbose=False):
|
async def has_capability(tp, capability='none', verbose=False):
|
||||||
# Note : default value of `capability` argument cannot be empty string
|
# Note : default value of `capability` argument cannot be empty string
|
||||||
# because protocomm_httpd expects non zero content lengths
|
# because protocomm_httpd expects non zero content lengths
|
||||||
try:
|
try:
|
||||||
response = tp.send_data('proto-ver', capability)
|
response = await tp.send_data('proto-ver', capability)
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print('proto-ver response : ', response)
|
print('proto-ver response : ', response)
|
||||||
@@ -142,24 +143,24 @@ def has_capability(tp, capability='none', verbose=False):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_version(tp):
|
async def get_version(tp):
|
||||||
response = None
|
response = None
|
||||||
try:
|
try:
|
||||||
response = tp.send_data('proto-ver', '---')
|
response = await tp.send_data('proto-ver', '---')
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
on_except(e)
|
on_except(e)
|
||||||
response = ''
|
response = ''
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def establish_session(tp, sec):
|
async def establish_session(tp, sec):
|
||||||
try:
|
try:
|
||||||
response = None
|
response = None
|
||||||
while True:
|
while True:
|
||||||
request = sec.security_session(response)
|
request = sec.security_session(response)
|
||||||
if request is None:
|
if request is None:
|
||||||
break
|
break
|
||||||
response = tp.send_data('prov-session', request)
|
response = await tp.send_data('prov-session', request)
|
||||||
if (response is None):
|
if (response is None):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@@ -168,27 +169,27 @@ def establish_session(tp, sec):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def custom_config(tp, sec, custom_info, custom_ver):
|
async def custom_config(tp, sec, custom_info, custom_ver):
|
||||||
try:
|
try:
|
||||||
message = prov.custom_config_request(sec, custom_info, custom_ver)
|
message = prov.custom_config_request(sec, custom_info, custom_ver)
|
||||||
response = tp.send_data('custom-config', message)
|
response = await tp.send_data('custom-config', message)
|
||||||
return (prov.custom_config_response(sec, response) == 0)
|
return (prov.custom_config_response(sec, response) == 0)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
on_except(e)
|
on_except(e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def custom_data(tp, sec, custom_data):
|
async def custom_data(tp, sec, custom_data):
|
||||||
try:
|
try:
|
||||||
message = prov.custom_data_request(sec, custom_data)
|
message = prov.custom_data_request(sec, custom_data)
|
||||||
response = tp.send_data('custom-data', message)
|
response = await tp.send_data('custom-data', message)
|
||||||
return (prov.custom_data_response(sec, response) == 0)
|
return (prov.custom_data_response(sec, response) == 0)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
on_except(e)
|
on_except(e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def scan_wifi_APs(sel_transport, tp, sec):
|
async def scan_wifi_APs(sel_transport, tp, sec):
|
||||||
APs = []
|
APs = []
|
||||||
group_channels = 0
|
group_channels = 0
|
||||||
readlen = 100
|
readlen = 100
|
||||||
@@ -211,13 +212,13 @@ def scan_wifi_APs(sel_transport, tp, sec):
|
|||||||
try:
|
try:
|
||||||
message = prov.scan_start_request(sec, blocking=True, group_channels=group_channels)
|
message = prov.scan_start_request(sec, blocking=True, group_channels=group_channels)
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
response = tp.send_data('prov-scan', message)
|
response = await tp.send_data('prov-scan', message)
|
||||||
stop_time = time.time()
|
stop_time = time.time()
|
||||||
print('++++ Scan process executed in ' + str(stop_time - start_time) + ' sec')
|
print('++++ Scan process executed in ' + str(stop_time - start_time) + ' sec')
|
||||||
prov.scan_start_response(sec, response)
|
prov.scan_start_response(sec, response)
|
||||||
|
|
||||||
message = prov.scan_status_request(sec)
|
message = prov.scan_status_request(sec)
|
||||||
response = tp.send_data('prov-scan', message)
|
response = await tp.send_data('prov-scan', message)
|
||||||
result = prov.scan_status_response(sec, response)
|
result = prov.scan_status_response(sec, response)
|
||||||
print('++++ Scan results : ' + str(result['count']))
|
print('++++ Scan results : ' + str(result['count']))
|
||||||
if result['count'] != 0:
|
if result['count'] != 0:
|
||||||
@@ -226,7 +227,7 @@ def scan_wifi_APs(sel_transport, tp, sec):
|
|||||||
while remaining:
|
while remaining:
|
||||||
count = [remaining, readlen][remaining > readlen]
|
count = [remaining, readlen][remaining > readlen]
|
||||||
message = prov.scan_result_request(sec, index, count)
|
message = prov.scan_result_request(sec, index, count)
|
||||||
response = tp.send_data('prov-scan', message)
|
response = await tp.send_data('prov-scan', message)
|
||||||
APs += prov.scan_result_response(sec, response)
|
APs += prov.scan_result_response(sec, response)
|
||||||
remaining -= count
|
remaining -= count
|
||||||
index += count
|
index += count
|
||||||
@@ -238,37 +239,37 @@ def scan_wifi_APs(sel_transport, tp, sec):
|
|||||||
return APs
|
return APs
|
||||||
|
|
||||||
|
|
||||||
def send_wifi_config(tp, sec, ssid, passphrase):
|
async def send_wifi_config(tp, sec, ssid, passphrase):
|
||||||
try:
|
try:
|
||||||
message = prov.config_set_config_request(sec, ssid, passphrase)
|
message = prov.config_set_config_request(sec, ssid, passphrase)
|
||||||
response = tp.send_data('prov-config', message)
|
response = await tp.send_data('prov-config', message)
|
||||||
return (prov.config_set_config_response(sec, response) == 0)
|
return (prov.config_set_config_response(sec, response) == 0)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
on_except(e)
|
on_except(e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def apply_wifi_config(tp, sec):
|
async def apply_wifi_config(tp, sec):
|
||||||
try:
|
try:
|
||||||
message = prov.config_apply_config_request(sec)
|
message = prov.config_apply_config_request(sec)
|
||||||
response = tp.send_data('prov-config', message)
|
response = await tp.send_data('prov-config', message)
|
||||||
return (prov.config_apply_config_response(sec, response) == 0)
|
return (prov.config_apply_config_response(sec, response) == 0)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
on_except(e)
|
on_except(e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_wifi_config(tp, sec):
|
async def get_wifi_config(tp, sec):
|
||||||
try:
|
try:
|
||||||
message = prov.config_get_status_request(sec)
|
message = prov.config_get_status_request(sec)
|
||||||
response = tp.send_data('prov-config', message)
|
response = await tp.send_data('prov-config', message)
|
||||||
return prov.config_get_status_response(sec, response)
|
return prov.config_get_status_response(sec, response)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
on_except(e)
|
on_except(e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def wait_wifi_connected(tp, sec):
|
async def wait_wifi_connected(tp, sec):
|
||||||
"""
|
"""
|
||||||
Wait for provisioning to report Wi-Fi is connected
|
Wait for provisioning to report Wi-Fi is connected
|
||||||
|
|
||||||
@@ -280,7 +281,7 @@ def wait_wifi_connected(tp, sec):
|
|||||||
while True:
|
while True:
|
||||||
time.sleep(TIME_PER_POLL)
|
time.sleep(TIME_PER_POLL)
|
||||||
print('\n==== Wi-Fi connection state ====')
|
print('\n==== Wi-Fi connection state ====')
|
||||||
ret = get_wifi_config(tp, sec)
|
ret = await get_wifi_config(tp, sec)
|
||||||
if ret == 'connecting':
|
if ret == 'connecting':
|
||||||
continue
|
continue
|
||||||
elif ret == 'connected':
|
elif ret == 'connected':
|
||||||
@@ -301,7 +302,7 @@ def desc_format(*args):
|
|||||||
return desc
|
return desc
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
async def main():
|
||||||
parser = argparse.ArgumentParser(description=desc_format(
|
parser = argparse.ArgumentParser(description=desc_format(
|
||||||
'ESP Provisioning tool for configuring devices '
|
'ESP Provisioning tool for configuring devices '
|
||||||
'running protocomm based provisioning service.',
|
'running protocomm based provisioning service.',
|
||||||
@@ -388,24 +389,25 @@ if __name__ == '__main__':
|
|||||||
security.sec2_gen_salt_verifier(args.sec2_usr, args.sec2_pwd, args.sec2_salt_len)
|
security.sec2_gen_salt_verifier(args.sec2_usr, args.sec2_pwd, args.sec2_salt_len)
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
obj_transport = get_transport(args.mode.lower(), args.name)
|
obj_transport = await get_transport(args.mode.lower(), args.name)
|
||||||
if obj_transport is None:
|
if obj_transport is None:
|
||||||
print('---- Failed to establish connection ----')
|
print('---- Failed to establish connection ----')
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
# If security version not specified check in capabilities
|
# If security version not specified check in capabilities
|
||||||
if args.secver is None:
|
if args.secver is None:
|
||||||
# First check if capabilities are supported or not
|
# First check if capabilities are supported or not
|
||||||
if not has_capability(obj_transport):
|
if not await has_capability(obj_transport):
|
||||||
print('Security capabilities could not be determined. Please specify "--sec_ver" explicitly')
|
print('Security capabilities could not be determined. Please specify "--sec_ver" explicitly')
|
||||||
print('---- Invalid Security Version ----')
|
print('---- Invalid Security Version ----')
|
||||||
exit(2)
|
exit(2)
|
||||||
|
|
||||||
# When no_sec is present, use security 0, else security 1
|
# When no_sec is present, use security 0, else security 1
|
||||||
args.secver = int(not has_capability(obj_transport, 'no_sec'))
|
args.secver = int(not await has_capability(obj_transport, 'no_sec'))
|
||||||
print('Security scheme determined to be :', args.secver)
|
print('Security scheme determined to be :', args.secver)
|
||||||
|
|
||||||
if (args.secver != 0) and not has_capability(obj_transport, 'no_pop'):
|
if (args.secver != 0) and not await has_capability(obj_transport, 'no_pop'):
|
||||||
if len(args.sec1_pop) == 0:
|
if len(args.sec1_pop) == 0:
|
||||||
print('---- Proof of Possession argument not provided ----')
|
print('---- Proof of Possession argument not provided ----')
|
||||||
exit(2)
|
exit(2)
|
||||||
@@ -420,13 +422,13 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
if args.version != '':
|
if args.version != '':
|
||||||
print('\n==== Verifying protocol version ====')
|
print('\n==== Verifying protocol version ====')
|
||||||
if not version_match(obj_transport, args.version, args.verbose):
|
if not await version_match(obj_transport, args.version, args.verbose):
|
||||||
print('---- Error in protocol version matching ----')
|
print('---- Error in protocol version matching ----')
|
||||||
exit(3)
|
exit(3)
|
||||||
print('==== Verified protocol version successfully ====')
|
print('==== Verified protocol version successfully ====')
|
||||||
|
|
||||||
print('\n==== Starting Session ====')
|
print('\n==== Starting Session ====')
|
||||||
if not establish_session(obj_transport, obj_security):
|
if not await establish_session(obj_transport, obj_security):
|
||||||
print('Failed to establish session. Ensure that security scheme and proof of possession are correct')
|
print('Failed to establish session. Ensure that security scheme and proof of possession are correct')
|
||||||
print('---- Error in establishing session ----')
|
print('---- Error in establishing session ----')
|
||||||
exit(4)
|
exit(4)
|
||||||
@@ -434,13 +436,13 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
if args.custom_data != '':
|
if args.custom_data != '':
|
||||||
print('\n==== Sending Custom data to esp32 ====')
|
print('\n==== Sending Custom data to esp32 ====')
|
||||||
if not custom_data(obj_transport, obj_security, args.custom_data):
|
if not await custom_data(obj_transport, obj_security, args.custom_data):
|
||||||
print('---- Error in custom data ----')
|
print('---- Error in custom data ----')
|
||||||
exit(5)
|
exit(5)
|
||||||
print('==== Custom data sent successfully ====')
|
print('==== Custom data sent successfully ====')
|
||||||
|
|
||||||
if args.ssid == '':
|
if args.ssid == '':
|
||||||
if not has_capability(obj_transport, 'wifi_scan'):
|
if not await has_capability(obj_transport, 'wifi_scan'):
|
||||||
print('---- Wi-Fi Scan List is not supported by provisioning service ----')
|
print('---- Wi-Fi Scan List is not supported by provisioning service ----')
|
||||||
print('---- Rerun esp_prov with SSID and Passphrase as argument ----')
|
print('---- Rerun esp_prov with SSID and Passphrase as argument ----')
|
||||||
exit(3)
|
exit(3)
|
||||||
@@ -448,7 +450,7 @@ if __name__ == '__main__':
|
|||||||
while True:
|
while True:
|
||||||
print('\n==== Scanning Wi-Fi APs ====')
|
print('\n==== Scanning Wi-Fi APs ====')
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
APs = scan_wifi_APs(args.mode.lower(), obj_transport, obj_security)
|
APs = await scan_wifi_APs(args.mode.lower(), obj_transport, obj_security)
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
print('\n++++ Scan finished in ' + str(end_time - start_time) + ' sec')
|
print('\n++++ Scan finished in ' + str(end_time - start_time) + ' sec')
|
||||||
if APs is None:
|
if APs is None:
|
||||||
@@ -483,15 +485,20 @@ if __name__ == '__main__':
|
|||||||
args.passphrase = getpass(prompt_str)
|
args.passphrase = getpass(prompt_str)
|
||||||
|
|
||||||
print('\n==== Sending Wi-Fi credential to esp32 ====')
|
print('\n==== Sending Wi-Fi credential to esp32 ====')
|
||||||
if not send_wifi_config(obj_transport, obj_security, args.ssid, args.passphrase):
|
if not await send_wifi_config(obj_transport, obj_security, args.ssid, args.passphrase):
|
||||||
print('---- Error in send Wi-Fi config ----')
|
print('---- Error in send Wi-Fi config ----')
|
||||||
exit(6)
|
exit(6)
|
||||||
print('==== Wi-Fi Credentials sent successfully ====')
|
print('==== Wi-Fi Credentials sent successfully ====')
|
||||||
|
|
||||||
print('\n==== Applying config to esp32 ====')
|
print('\n==== Applying config to esp32 ====')
|
||||||
if not apply_wifi_config(obj_transport, obj_security):
|
if not await apply_wifi_config(obj_transport, obj_security):
|
||||||
print('---- Error in apply Wi-Fi config ----')
|
print('---- Error in apply Wi-Fi config ----')
|
||||||
exit(7)
|
exit(7)
|
||||||
print('==== Apply config sent successfully ====')
|
print('==== Apply config sent successfully ====')
|
||||||
|
|
||||||
wait_wifi_connected(obj_transport, obj_security)
|
await wait_wifi_connected(obj_transport, obj_security)
|
||||||
|
finally:
|
||||||
|
await obj_transport.disconnect()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
|
@@ -19,7 +19,7 @@ def custom_data_request(security_ctx, data):
|
|||||||
# Encrypt the custom data
|
# Encrypt the custom data
|
||||||
enc_cmd = security_ctx.encrypt_data(tobytes(data))
|
enc_cmd = security_ctx.encrypt_data(tobytes(data))
|
||||||
print_verbose(security_ctx, 'Client -> Device (CustomData cmd) ' + utils.str_to_hexstr(enc_cmd))
|
print_verbose(security_ctx, 'Client -> Device (CustomData cmd) ' + utils.str_to_hexstr(enc_cmd))
|
||||||
return enc_cmd
|
return enc_cmd.decode('latin-1')
|
||||||
|
|
||||||
|
|
||||||
def custom_data_response(security_ctx, response_data):
|
def custom_data_response(security_ctx, response_data):
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
bleak
|
||||||
future
|
future
|
||||||
cryptography
|
cryptography
|
||||||
protobuf
|
protobuf
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
dbus-python
|
|
@@ -1,16 +1,5 @@
|
|||||||
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
|
||||||
#
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# 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
|
from __future__ import print_function
|
||||||
@@ -19,19 +8,14 @@ import platform
|
|||||||
from builtins import input
|
from builtins import input
|
||||||
|
|
||||||
import utils
|
import utils
|
||||||
from future.utils import iteritems
|
|
||||||
|
|
||||||
fallback = True
|
fallback = True
|
||||||
|
|
||||||
|
|
||||||
# Check if platform is Linux and required packages are installed
|
# Check if required packages are installed
|
||||||
# else fallback to console mode
|
# else fallback to console mode
|
||||||
if platform.system() == 'Linux':
|
|
||||||
try:
|
try:
|
||||||
import time
|
import bleak
|
||||||
|
|
||||||
import dbus
|
|
||||||
import dbus.mainloop.glib
|
|
||||||
fallback = False
|
fallback = False
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
@@ -39,13 +23,11 @@ if platform.system() == 'Linux':
|
|||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
class BLE_Bleak_Client:
|
||||||
# BLE client (Linux Only) using Bluez and DBus
|
|
||||||
class BLE_Bluez_Client:
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.adapter_props = None
|
self.adapter_props = None
|
||||||
|
|
||||||
def connect(self, devname, iface, chrc_names, fallback_srv_uuid):
|
async def connect(self, devname, iface, chrc_names, fallback_srv_uuid):
|
||||||
self.devname = devname
|
self.devname = devname
|
||||||
self.srv_uuid_fallback = fallback_srv_uuid
|
self.srv_uuid_fallback = fallback_srv_uuid
|
||||||
self.chrc_names = [name.lower() for name in chrc_names]
|
self.chrc_names = [name.lower() for name in chrc_names]
|
||||||
@@ -56,117 +38,19 @@ class BLE_Bluez_Client:
|
|||||||
self.characteristics = dict()
|
self.characteristics = dict()
|
||||||
self.srv_uuid_adv = None
|
self.srv_uuid_adv = None
|
||||||
|
|
||||||
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
print('Discovering...')
|
||||||
bus = dbus.SystemBus()
|
|
||||||
manager = dbus.Interface(bus.get_object('org.bluez', '/'), 'org.freedesktop.DBus.ObjectManager')
|
|
||||||
objects = manager.GetManagedObjects()
|
|
||||||
adapter_path = None
|
|
||||||
for path, interfaces in iteritems(objects):
|
|
||||||
adapter = interfaces.get('org.bluez.Adapter1')
|
|
||||||
if adapter is not None:
|
|
||||||
if path.endswith(iface):
|
|
||||||
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')
|
|
||||||
adapter_path = path
|
|
||||||
break
|
|
||||||
|
|
||||||
if self.adapter is None:
|
|
||||||
raise RuntimeError('Bluetooth adapter not found')
|
|
||||||
|
|
||||||
# Power on bluetooth adapter
|
|
||||||
self.adapter_props.Set('org.bluez.Adapter1', 'Powered', dbus.Boolean(1))
|
|
||||||
print('checking if adapter is powered on')
|
|
||||||
for cnt in range(10, 0, -1):
|
|
||||||
time.sleep(5)
|
|
||||||
powered_on = self.adapter_props.Get('org.bluez.Adapter1', 'Powered')
|
|
||||||
if powered_on == 1:
|
|
||||||
# Set adapter props again with powered on value
|
|
||||||
self.adapter_props = dbus.Interface(bus.get_object('org.bluez', adapter_path), 'org.freedesktop.DBus.Properties')
|
|
||||||
print('bluetooth adapter powered on')
|
|
||||||
break
|
|
||||||
print('number of retries left({})'.format(cnt - 1))
|
|
||||||
if powered_on == 0:
|
|
||||||
raise RuntimeError('Failed to starte bluetooth adapter')
|
|
||||||
|
|
||||||
# Start discovery if not already discovering
|
|
||||||
started_discovery = 0
|
|
||||||
discovery_val = self.adapter_props.Get('org.bluez.Adapter1', 'Discovering')
|
|
||||||
if discovery_val == 0:
|
|
||||||
print('starting discovery')
|
|
||||||
self.adapter.StartDiscovery()
|
|
||||||
# Set as start discovery is called
|
|
||||||
started_discovery = 1
|
|
||||||
for cnt in range(10, 0, -1):
|
|
||||||
time.sleep(5)
|
|
||||||
discovery_val = self.adapter_props.Get('org.bluez.Adapter1', 'Discovering')
|
|
||||||
if discovery_val == 1:
|
|
||||||
print('start discovery successful')
|
|
||||||
break
|
|
||||||
print('number of retries left ({})'.format(cnt - 1))
|
|
||||||
|
|
||||||
if discovery_val == 0:
|
|
||||||
print('start discovery failed')
|
|
||||||
raise RuntimeError('Failed to start discovery')
|
|
||||||
|
|
||||||
retry = 10
|
|
||||||
while (retry > 0):
|
|
||||||
try:
|
try:
|
||||||
if self.device is None:
|
devices = await bleak.discover()
|
||||||
print('Connecting...')
|
except bleak.exc.BleakDBusError as e:
|
||||||
# Wait for device to be discovered
|
if str(e) == '[org.bluez.Error.NotReady] Resource Not Ready':
|
||||||
time.sleep(5)
|
raise RuntimeError('Bluetooth is not ready. Maybe try `bluetoothctl power on`?')
|
||||||
connected = self._connect_()
|
raise
|
||||||
if connected:
|
|
||||||
print('Connected')
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
print('Getting Services...')
|
|
||||||
# Wait for services to be discovered
|
|
||||||
time.sleep(5)
|
|
||||||
self._get_services_()
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
retry -= 1
|
|
||||||
print('Retries left', retry)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Call StopDiscovery() for corresponding StartDiscovery() session
|
address = None
|
||||||
if started_discovery == 1:
|
for d in devices:
|
||||||
print('stopping discovery')
|
if d.name == self.devname:
|
||||||
self.adapter.StopDiscovery()
|
address = d.address
|
||||||
for cnt in range(10, 0, -1):
|
uuids = d.metadata['uuids']
|
||||||
time.sleep(5)
|
|
||||||
discovery_val = self.adapter_props.Get('org.bluez.Adapter1', 'Discovering')
|
|
||||||
if discovery_val == 0:
|
|
||||||
print('stop discovery successful')
|
|
||||||
break
|
|
||||||
print('number of retries left ({})'.format(cnt - 1))
|
|
||||||
if discovery_val == 1:
|
|
||||||
print('stop discovery failed')
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _connect_(self):
|
|
||||||
bus = dbus.SystemBus()
|
|
||||||
manager = dbus.Interface(bus.get_object('org.bluez', '/'), 'org.freedesktop.DBus.ObjectManager')
|
|
||||||
objects = manager.GetManagedObjects()
|
|
||||||
dev_path = None
|
|
||||||
for path, interfaces in iteritems(objects):
|
|
||||||
if 'org.bluez.Device1' not in interfaces:
|
|
||||||
continue
|
|
||||||
if interfaces['org.bluez.Device1'].get('Name') == self.devname:
|
|
||||||
dev_path = path
|
|
||||||
break
|
|
||||||
|
|
||||||
if dev_path is None:
|
|
||||||
raise RuntimeError('BLE device not found')
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.device = bus.get_object('org.bluez', dev_path)
|
|
||||||
try:
|
|
||||||
uuids = self.device.Get('org.bluez.Device1', 'UUIDs',
|
|
||||||
dbus_interface='org.freedesktop.DBus.Properties')
|
|
||||||
# There should be 1 service UUID in advertising data
|
# There should be 1 service UUID in advertising data
|
||||||
# If bluez had cached an old version of the advertisement data
|
# If bluez had cached an old version of the advertisement data
|
||||||
# the list of uuids may be incorrect, in which case connection
|
# the list of uuids may be incorrect, in which case connection
|
||||||
@@ -174,79 +58,35 @@ class BLE_Bluez_Client:
|
|||||||
# the cache will be refreshed before next retry
|
# the cache will be refreshed before next retry
|
||||||
if len(uuids) == 1:
|
if len(uuids) == 1:
|
||||||
self.srv_uuid_adv = uuids[0]
|
self.srv_uuid_adv = uuids[0]
|
||||||
except dbus.exceptions.DBusException as e:
|
if not address:
|
||||||
raise RuntimeError(e)
|
raise RuntimeError('Device not found')
|
||||||
|
|
||||||
self.device.Connect(dbus_interface='org.bluez.Device1')
|
print('Connecting...')
|
||||||
# Check device is connected successfully
|
self.device = bleak.BleakClient(address)
|
||||||
for cnt in range(10, 0, -1):
|
await self.device.connect()
|
||||||
time.sleep(5)
|
# must be paired on Windows to access characteristics;
|
||||||
device_conn = self.device.Get(
|
# cannot be paired on Mac
|
||||||
'org.bluez.Device1',
|
if platform.system() == 'Windows':
|
||||||
'Connected',
|
await self.device.pair()
|
||||||
dbus_interface='org.freedesktop.DBus.Properties')
|
|
||||||
if device_conn == 1:
|
|
||||||
print('device is connected')
|
|
||||||
break
|
|
||||||
print('number of retries left ({})'.format(cnt - 1))
|
|
||||||
if device_conn == 0:
|
|
||||||
print('failed to connect device')
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
print('Getting Services...')
|
||||||
|
services = await self.device.get_services()
|
||||||
|
|
||||||
except Exception as e:
|
service = services[self.srv_uuid_adv] or services[self.srv_uuid_fallback]
|
||||||
print(e)
|
if not service:
|
||||||
|
await self.device.disconnect()
|
||||||
self.device = None
|
self.device = None
|
||||||
raise RuntimeError('BLE device could not connect')
|
raise RuntimeError('Provisioning service not found')
|
||||||
|
|
||||||
def _get_services_(self):
|
|
||||||
bus = dbus.SystemBus()
|
|
||||||
manager = dbus.Interface(bus.get_object('org.bluez', '/'), 'org.freedesktop.DBus.ObjectManager')
|
|
||||||
objects = manager.GetManagedObjects()
|
|
||||||
service_found = False
|
|
||||||
for srv_path, srv_interfaces in iteritems(objects):
|
|
||||||
if 'org.bluez.GattService1' not in srv_interfaces:
|
|
||||||
continue
|
|
||||||
if not srv_path.startswith(self.device.object_path):
|
|
||||||
continue
|
|
||||||
service = bus.get_object('org.bluez', srv_path)
|
|
||||||
srv_uuid = service.Get('org.bluez.GattService1', 'UUID',
|
|
||||||
dbus_interface='org.freedesktop.DBus.Properties')
|
|
||||||
|
|
||||||
# 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()
|
nu_lookup = dict()
|
||||||
characteristics = dict()
|
for characteristic in service.characteristics:
|
||||||
for chrc_path, chrc_interfaces in iteritems(objects):
|
for descriptor in characteristic.descriptors:
|
||||||
if 'org.bluez.GattCharacteristic1' not in chrc_interfaces:
|
if descriptor.uuid[4:8] != '2901':
|
||||||
continue
|
continue
|
||||||
if not chrc_path.startswith(service.object_path):
|
readval = await self.device.read_gatt_descriptor(descriptor.handle)
|
||||||
continue
|
|
||||||
chrc = bus.get_object('org.bluez', chrc_path)
|
|
||||||
uuid = chrc.Get('org.bluez.GattCharacteristic1', 'UUID',
|
|
||||||
dbus_interface='org.freedesktop.DBus.Properties')
|
|
||||||
characteristics[uuid] = chrc
|
|
||||||
for desc_path, desc_interfaces in iteritems(objects):
|
|
||||||
if 'org.bluez.GattDescriptor1' not in desc_interfaces:
|
|
||||||
continue
|
|
||||||
if not desc_path.startswith(chrc.object_path):
|
|
||||||
continue
|
|
||||||
desc = bus.get_object('org.bluez', desc_path)
|
|
||||||
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 as err:
|
|
||||||
raise RuntimeError('Failed to read value for descriptor while getting services - {}'.format(err))
|
|
||||||
found_name = ''.join(chr(b) for b in readval).lower()
|
found_name = ''.join(chr(b) for b in readval).lower()
|
||||||
nu_lookup[found_name] = uuid
|
nu_lookup[found_name] = characteristic.uuid
|
||||||
break
|
self.characteristics[characteristic.uuid] = characteristic
|
||||||
|
|
||||||
match_found = True
|
match_found = True
|
||||||
for name in self.chrc_names:
|
for name in self.chrc_names:
|
||||||
@@ -257,89 +97,39 @@ class BLE_Bluez_Client:
|
|||||||
|
|
||||||
# Create lookup table only if all endpoint names found
|
# Create lookup table only if all endpoint names found
|
||||||
self.nu_lookup = [None, nu_lookup][match_found]
|
self.nu_lookup = [None, nu_lookup][match_found]
|
||||||
self.characteristics = characteristics
|
|
||||||
service_found = True
|
|
||||||
|
|
||||||
# If the service UUID matches that in the advertisement
|
return True
|
||||||
# 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:
|
|
||||||
self.device.Disconnect(dbus_interface='org.bluez.Device1')
|
|
||||||
# Check if device is disconnected successfully
|
|
||||||
self._check_device_disconnected()
|
|
||||||
if self.adapter:
|
|
||||||
self.adapter.RemoveDevice(self.device)
|
|
||||||
self.device = None
|
|
||||||
self.nu_lookup = None
|
|
||||||
self.characteristics = dict()
|
|
||||||
raise RuntimeError('Provisioning service not found')
|
|
||||||
|
|
||||||
def get_nu_lookup(self):
|
def get_nu_lookup(self):
|
||||||
return self.nu_lookup
|
return self.nu_lookup
|
||||||
|
|
||||||
def has_characteristic(self, uuid):
|
def has_characteristic(self, uuid):
|
||||||
|
print('checking for characteristic ' + uuid)
|
||||||
if uuid in self.characteristics:
|
if uuid in self.characteristics:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def disconnect(self):
|
async def disconnect(self):
|
||||||
if self.device:
|
if self.device:
|
||||||
self.device.Disconnect(dbus_interface='org.bluez.Device1')
|
print('Disconnecting...')
|
||||||
# Check if device is disconnected successfully
|
if platform.system() == 'Windows':
|
||||||
self._check_device_disconnected()
|
await self.device.unpair()
|
||||||
if self.adapter:
|
await self.device.disconnect()
|
||||||
self.adapter.RemoveDevice(self.device)
|
|
||||||
self.device = None
|
self.device = None
|
||||||
self.nu_lookup = None
|
self.nu_lookup = None
|
||||||
self.characteristics = dict()
|
self.characteristics = dict()
|
||||||
if self.adapter_props:
|
|
||||||
self.adapter_props.Set('org.bluez.Adapter1', 'Powered', dbus.Boolean(0))
|
|
||||||
|
|
||||||
def _check_device_disconnected(self):
|
async def send_data(self, characteristic_uuid, data):
|
||||||
for cnt in range(10, 0, -1):
|
await self.device.write_gatt_char(characteristic_uuid, bytearray(data.encode('latin-1')), True)
|
||||||
time.sleep(5)
|
readval = await self.device.read_gatt_char(characteristic_uuid)
|
||||||
device_conn = self.device.Get(
|
|
||||||
'org.bluez.Device1',
|
|
||||||
'Connected',
|
|
||||||
dbus_interface='org.freedesktop.DBus.Properties')
|
|
||||||
if device_conn == 0:
|
|
||||||
print('device disconnected')
|
|
||||||
break
|
|
||||||
print('number of retries left ({})'.format(cnt - 1))
|
|
||||||
if device_conn == 1:
|
|
||||||
print('failed to disconnect device')
|
|
||||||
|
|
||||||
def send_data(self, characteristic_uuid, data):
|
|
||||||
try:
|
|
||||||
path = self.characteristics[characteristic_uuid]
|
|
||||||
except KeyError:
|
|
||||||
raise RuntimeError('Invalid characteristic : ' + characteristic_uuid)
|
|
||||||
|
|
||||||
try:
|
|
||||||
path.WriteValue([ord(c) for c in data], {}, dbus_interface='org.bluez.GattCharacteristic1')
|
|
||||||
except TypeError: # python3 compatible
|
|
||||||
path.WriteValue([c for c in data], {}, dbus_interface='org.bluez.GattCharacteristic1')
|
|
||||||
except dbus.exceptions.DBusException as e:
|
|
||||||
raise RuntimeError('Failed to write value to characteristic ' + characteristic_uuid + ': ' + str(e))
|
|
||||||
|
|
||||||
try:
|
|
||||||
readval = path.ReadValue({}, dbus_interface='org.bluez.GattCharacteristic1')
|
|
||||||
except dbus.exceptions.DBusException as e:
|
|
||||||
raise RuntimeError('Failed to read value from characteristic ' + characteristic_uuid + ': ' + str(e))
|
|
||||||
return ''.join(chr(b) for b in readval)
|
return ''.join(chr(b) for b in readval)
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
# Console based BLE client for Cross Platform support
|
# Console based BLE client for Cross Platform support
|
||||||
class BLE_Console_Client:
|
class BLE_Console_Client:
|
||||||
def connect(self, devname, iface, chrc_names, fallback_srv_uuid):
|
async def connect(self, devname, iface, chrc_names, fallback_srv_uuid):
|
||||||
print('BLE client is running in console mode')
|
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('\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('\tPlease ensure all pre-requisites are met to run the full fledged client')
|
||||||
@@ -362,10 +152,10 @@ class BLE_Console_Client:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def disconnect(self):
|
async def disconnect(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def send_data(self, characteristic_uuid, data):
|
async def send_data(self, characteristic_uuid, data):
|
||||||
print("BLECLI >> Write following data to characteristic with UUID '" + characteristic_uuid + "' :")
|
print("BLECLI >> Write following data to characteristic with UUID '" + characteristic_uuid + "' :")
|
||||||
print('\t>> ' + utils.str_to_hexstr(data))
|
print('\t>> ' + utils.str_to_hexstr(data))
|
||||||
print('BLECLI >> Enter data read from characteristic (in hex) :')
|
print('BLECLI >> Enter data read from characteristic (in hex) :')
|
||||||
@@ -380,4 +170,4 @@ class BLE_Console_Client:
|
|||||||
def get_client():
|
def get_client():
|
||||||
if fallback:
|
if fallback:
|
||||||
return BLE_Console_Client()
|
return BLE_Console_Client()
|
||||||
return BLE_Bluez_Client()
|
return BLE_Bleak_Client()
|
||||||
|
@@ -1,16 +1,5 @@
|
|||||||
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
|
||||||
#
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# 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.
|
|
||||||
#
|
#
|
||||||
|
|
||||||
# Base class for protocomm transport
|
# Base class for protocomm transport
|
||||||
@@ -27,3 +16,6 @@ class Transport():
|
|||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def send_config_data(self, data):
|
def send_config_data(self, data):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def disconnect(self):
|
||||||
|
pass
|
||||||
|
@@ -1,16 +1,5 @@
|
|||||||
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
|
||||||
#
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# 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
|
from __future__ import print_function
|
||||||
@@ -20,7 +9,9 @@ from .transport import Transport
|
|||||||
|
|
||||||
|
|
||||||
class Transport_BLE(Transport):
|
class Transport_BLE(Transport):
|
||||||
def __init__(self, devname, service_uuid, nu_lookup):
|
def __init__(self, service_uuid, nu_lookup):
|
||||||
|
self.nu_lookup = nu_lookup
|
||||||
|
self.service_uuid = service_uuid
|
||||||
# Expect service UUID like '0000ffff-0000-1000-8000-00805f9b34fb'
|
# Expect service UUID like '0000ffff-0000-1000-8000-00805f9b34fb'
|
||||||
for name in nu_lookup.keys():
|
for name in nu_lookup.keys():
|
||||||
# Calculate characteristic UUID for each endpoint
|
# Calculate characteristic UUID for each endpoint
|
||||||
@@ -30,10 +21,11 @@ class Transport_BLE(Transport):
|
|||||||
# Get BLE client module
|
# Get BLE client module
|
||||||
self.cli = ble_cli.get_client()
|
self.cli = ble_cli.get_client()
|
||||||
|
|
||||||
|
async def connect(self, devname):
|
||||||
# Use client to connect to BLE device and bind to service
|
# Use client to connect to BLE device and bind to service
|
||||||
if not self.cli.connect(devname=devname, iface='hci0',
|
if not await self.cli.connect(devname=devname, iface='hci0',
|
||||||
chrc_names=nu_lookup.keys(),
|
chrc_names=self.nu_lookup.keys(),
|
||||||
fallback_srv_uuid=service_uuid):
|
fallback_srv_uuid=self.service_uuid):
|
||||||
raise RuntimeError('Failed to initialize transport')
|
raise RuntimeError('Failed to initialize transport')
|
||||||
|
|
||||||
# Irrespective of provided parameters, let the client
|
# Irrespective of provided parameters, let the client
|
||||||
@@ -43,24 +35,17 @@ class Transport_BLE(Transport):
|
|||||||
|
|
||||||
# If that doesn't work, use the lookup table provided as parameter
|
# If that doesn't work, use the lookup table provided as parameter
|
||||||
if self.name_uuid_lookup is None:
|
if self.name_uuid_lookup is None:
|
||||||
self.name_uuid_lookup = nu_lookup
|
self.name_uuid_lookup = self.nu_lookup
|
||||||
# Check if expected characteristics are provided by the service
|
# Check if expected characteristics are provided by the service
|
||||||
for name in self.name_uuid_lookup.keys():
|
for name in self.name_uuid_lookup.keys():
|
||||||
if not self.cli.has_characteristic(self.name_uuid_lookup[name]):
|
if not self.cli.has_characteristic(self.name_uuid_lookup[name]):
|
||||||
raise RuntimeError("'" + name + "' endpoint not found")
|
raise RuntimeError("'" + name + "' endpoint not found")
|
||||||
|
|
||||||
def __del__(self):
|
async def disconnect(self):
|
||||||
# Make sure device is disconnected before application gets closed
|
await self.cli.disconnect()
|
||||||
try:
|
|
||||||
self.disconnect()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def disconnect(self):
|
async def send_data(self, ep_name, data):
|
||||||
self.cli.disconnect()
|
|
||||||
|
|
||||||
def send_data(self, ep_name, data):
|
|
||||||
# Write (and read) data to characteristic corresponding to the endpoint
|
# Write (and read) data to characteristic corresponding to the endpoint
|
||||||
if ep_name not in self.name_uuid_lookup.keys():
|
if ep_name not in self.name_uuid_lookup.keys():
|
||||||
raise RuntimeError('Invalid endpoint : ' + ep_name)
|
raise RuntimeError('Invalid endpoint : ' + ep_name)
|
||||||
return self.cli.send_data(self.name_uuid_lookup[ep_name], data)
|
return await self.cli.send_data(self.name_uuid_lookup[ep_name], data)
|
||||||
|
@@ -1,16 +1,5 @@
|
|||||||
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
|
||||||
#
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# 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
|
from __future__ import print_function
|
||||||
@@ -24,7 +13,7 @@ from .transport import Transport
|
|||||||
|
|
||||||
class Transport_Console(Transport):
|
class Transport_Console(Transport):
|
||||||
|
|
||||||
def send_data(self, path, data, session_id=0):
|
async def send_data(self, path, data, session_id=0):
|
||||||
print('Client->Device msg :', path, session_id, utils.str_to_hexstr(data))
|
print('Client->Device msg :', path, session_id, utils.str_to_hexstr(data))
|
||||||
try:
|
try:
|
||||||
resp = input('Enter device->client msg : ')
|
resp = input('Enter device->client msg : ')
|
||||||
|
@@ -1,16 +1,5 @@
|
|||||||
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
|
||||||
#
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# 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
|
from __future__ import print_function
|
||||||
@@ -23,7 +12,7 @@ try:
|
|||||||
from http.client import HTTPConnection, HTTPSConnection
|
from http.client import HTTPConnection, HTTPSConnection
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Python 2 fallback
|
# Python 2 fallback
|
||||||
from httplib import HTTPConnection, HTTPSConnection
|
from httplib import HTTPConnection, HTTPSConnection # type: ignore
|
||||||
|
|
||||||
from .transport import Transport
|
from .transport import Transport
|
||||||
|
|
||||||
@@ -56,5 +45,5 @@ class Transport_HTTP(Transport):
|
|||||||
raise RuntimeError('Connection Failure : ' + str(err))
|
raise RuntimeError('Connection Failure : ' + str(err))
|
||||||
raise RuntimeError('Server responded with error code ' + str(response.status))
|
raise RuntimeError('Server responded with error code ' + str(response.status))
|
||||||
|
|
||||||
def send_data(self, ep_name, data):
|
async def send_data(self, ep_name, data):
|
||||||
return self._send_post_request('/' + ep_name, data)
|
return self._send_post_request('/' + ep_name, data)
|
||||||
|
Reference in New Issue
Block a user