Merge branch 'bugfix/prov_tool_desc_readme' into 'master'

esp_prov : Minor refactoring in argument dependent logic and other changes

See merge request espressif/esp-idf!5430
This commit is contained in:
Mahavir Jain 2019-07-05 17:18:32 +08:00
commit 46c8497f01
11 changed files with 195 additions and 74 deletions

View File

@ -80,7 +80,7 @@ Make sure to note down the BLE device name (starting with PROV_) displayed in th
In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). Assuming default example configuration :
```
python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 1 --pop abcd1234 --transport ble --ble_devname PROV_261FCC
python esp_prov.py --transport ble --service_name PROV_261FCC --sec_ver 1 --pop abcd1234 --ssid myssid --passphrase mypassword
```
Above command will perform the provisioning steps, and the monitor log should display something like this :
@ -150,7 +150,6 @@ Or, enable `Reset Provisioning` option under `Example Configuration` under menuc
If the platform requirement, for running `esp_prov` is not satisfied, then the script execution will fallback to console mode, in which case the full process (involving user inputs) will look like this :
```
==== Esp_Prov Version: V0.1 ====
BLE client is running in console mode
This could be due to your platform not being supported or dependencies not being met
Please ensure all pre-requisites are met to run the full fledged client

View File

@ -76,7 +76,7 @@ def test_examples_provisioning_ble(env, extra_data):
raise RuntimeError("Failed to get security")
print("Getting transport")
transport = esp_prov.get_transport(provmode, None, devname)
transport = esp_prov.get_transport(provmode, devname)
if transport is None:
raise RuntimeError("Failed to get transport")

View File

@ -65,14 +65,12 @@ I (398) app_prov: Console provisioning started
In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). Assuming default example configuration, the script should be run as follows :
```
python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 1 --pop abcd1234 --transport console
python esp_prov.py --transport console --proto_ver "V0.1" --sec_ver 1 --pop abcd1234 --ssid myssid --passphrase mypassword
```
A console will open up and the `Client->Device` commands have to be copied manually to the serial monitor console prompt :
```
==== Esp_Prov Version: V0.1 ====
==== Verifying protocol version ====
Client->Device msg : proto-ver 0 56302e31
Enter device->client msg :
@ -111,8 +109,6 @@ This is helpful in understanding the provisioning process and the order in which
The full execution sequence of `esp_prov`, as seen on the console, is shown here :
```
==== Esp_Prov Version: V0.1 ====
==== Verifying protocol version ====
Client->Device msg : proto-ver 0 56302e31
Enter device->client msg : 53554343455353

View File

@ -68,7 +68,7 @@ I (519482) tcpip_adapter: softAP assign IP to station,IP is: 192.168.4.2
In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace the values corresponding to the parameters `--custom_info` and `--custom_ver` with your desired values for the custom configuration). Assuming default example configuration, the script should be run as follows :
```
python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 0 --transport softap --softap_endpoint 192.168.4.1:80 --custom_config --custom_info "some string" --custom_ver 4321
python esp_prov.py --transport softap --service_name "192.168.4.1:80" --sec_ver 0 --ssid myssid --passphrase mypassword --custom_config --custom_info "some string" --custom_ver 4321
```
Above command will perform the provisioning steps, and the monitor log should display something like this :

View File

@ -74,10 +74,10 @@ I (1045) wifi_prov_mgr: Provisioning started with service name : PROV_261FCC
Make sure to note down the BLE device name (starting with `PROV_`) displayed in the serial monitor log (eg. PROV_261FCC). This will depend on the MAC ID and will be unique for every device.
In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). Assuming default example configuration :
In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (make sure to replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). Assuming default example configuration, which uses protocomm security scheme 1 and proof of possession PoP based authentication :
```
python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 1 --pop abcd1234 --transport ble --ble_devname PROV_261FCC
python esp_prov.py --transport ble --service_name PROV_261FCC --sec_ver 1 --pop abcd1234 --ssid myssid --passphrase mypassword
```
Above command will perform the provisioning steps, and the monitor log should display something like this :
@ -109,6 +109,54 @@ I (54355) app: Hello World!
I (55355) app: Hello World!
```
### Wi-Fi Scanning
Provisioning manager also supports providing real-time Wi-Fi scan results (performed on the device) during provisioning. This allows the client side applications to choose the AP for which the device Wi-Fi station is to be configured. Various information about the visible APs is available, like signal strength (RSSI) and security type, etc. Also, the manager now provides capabilities information which can be used by client applications to determine the security type and availability of specific features (like `wifi_scan`).
When using the scan based provisioning, we don't need to specify the `--ssid` and `--passphrase` fields explicitly:
```
python esp_prov.py --transport ble --service_name PROV_261FCC --pop abcd1234
```
See below the sample output from `esp_prov` tool on running above command:
```
Connecting...
Connected
Getting Services...
Security scheme determined to be : 1
==== Starting Session ====
==== Session Established ====
==== Scanning Wi-Fi APs ====
++++ Scan process executed in 1.9967520237 sec
++++ Scan results : 5
++++ Scan finished in 2.7374596596 sec
==== Wi-Fi Scan results ====
S.N. SSID BSSID CHN RSSI AUTH
[ 1] MyHomeWiFiAP 788a20841996 1 -45 WPA2_PSK
[ 2] MobileHotspot 7a8a20841996 11 -46 WPA2_PSK
[ 3] MyHomeWiFiAP 788a208daa26 11 -54 WPA2_PSK
[ 4] NeighborsWiFiAP 8a8a20841996 6 -61 WPA2_PSK
[ 5] InsecureWiFiAP dca4caf1227c 7 -74 Open
Select AP by number (0 to rescan) : 1
Enter passphrase for MyHomeWiFiAP :
==== Sending Wi-Fi credential to esp32 ====
==== Wi-Fi Credentials sent successfully ====
==== Applying config to esp32 ====
==== Apply config sent successfully ====
==== Wi-Fi connection state ====
++++ WiFi state: connected ++++
==== Provisioning was successful ====
```
## Troubleshooting
### Provisioning failed

View File

@ -76,7 +76,7 @@ def test_examples_wifi_prov_mgr(env, extra_data):
raise RuntimeError("Failed to get security")
print("Getting transport")
transport = esp_prov.get_transport(provmode, None, devname)
transport = esp_prov.get_transport(provmode, devname)
if transport is None:
raise RuntimeError("Failed to get transport")

View File

@ -81,7 +81,7 @@ I (519482) tcpip_adapter: softAP assign IP to station,IP is: 192.168.4.2
In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). The SoftAP endpoint corresponds to the IP and port of the device on the SoftAP network, but this is usually same as the default value and may be left out. Assuming default example configuration, the script should be run as follows :
```
python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 1 --pop abcd1234 --transport softap --softap_endpoint 192.168.4.1:80
python esp_prov.py --transport softap --service_name "192.168.4.1:80" --sec_ver 1 --pop abcd1234 --ssid myssid --passphrase mypassword
```
Above command will perform the provisioning steps, and the monitor log should display something like this :

View File

@ -97,7 +97,7 @@ def test_examples_provisioning_softap(env, extra_data):
raise RuntimeError("Failed to get security")
print("Getting transport")
transport = esp_prov.get_transport(provmode, softap_endpoint, None)
transport = esp_prov.get_transport(provmode, softap_endpoint)
if transport is None:
raise RuntimeError("Failed to get transport")

View File

@ -6,7 +6,7 @@
# SYNOPSIS
```
python esp_prov.py --transport < mode of provisioning : softap \ ble \ console > --sec_ver < Security version 0 / 1 > [ Optional parameters... ]
python esp_prov.py --transport < mode of provisioning : softap \ ble \ console > [ Optional parameters... ]
```
# DESCRIPTION
@ -53,11 +53,9 @@ Usage of `esp-prov` assumes that the provisioning app has specific protocomm end
* `--pop <Proof of possession string>` (Optional)
For specifying optional Proof of Possession string to use for protocomm endpoint security version 1. This option is ignored when security version 0 is in use
* `--softap_endpoint <softap_ip:port>` (Optional) (Default `192.168.4.1:80`)
For specifying the IP and port of the HTTP server on which provisioning app is running. The client must connect to the device SoftAP prior to running `esp_prov`
* `--ble_devname <BLE device name>` (Optional)
For specifying name of the BLE device to which connection is to be established prior to starting provisioning process. This is only used when `--transport ble` is specified, else it is ignored. Since connection with BLE is supported only on Linux, so this option is again ignored for other platforms
* `--service_name <name> (Optional)
When transport mode is ble, this specifies the BLE device name to which connection is to be established for provisioned.
When transport mode is softap, this specifies the HTTP server hostname / IP which is running the provisioning service, on the SoftAP network of the device which is to be provisioned. This defaults to `192.168.4.1:80` if not specified
* `--custom_config` (Optional)
This flag assumes the provisioning app has an endpoint called `custom-config`. Use `--custom_info` and `--custom_ver` options to specify the fields accepted by this endpoint

View File

@ -18,6 +18,7 @@
from __future__ import print_function
from builtins import input
import argparse
import textwrap
import time
import os
import sys
@ -57,12 +58,16 @@ def get_security(secver, pop=None, verbose=False):
return None
def get_transport(sel_transport, softap_endpoint=None, ble_devname=None):
def get_transport(sel_transport, service_name):
try:
tp = None
if (sel_transport == 'softap'):
tp = transport.Transport_HTTP(softap_endpoint)
if service_name is None:
service_name = '192.168.4.1:80'
tp = transport.Transport_HTTP(service_name)
elif (sel_transport == 'ble'):
if service_name is None:
raise RuntimeError('"--service_name" must be specified for ble transport')
# BLE client is now capable of automatically figuring out
# the primary service from the advertisement data and the
# characteristics corresponding to each endpoint.
@ -71,7 +76,7 @@ def get_transport(sel_transport, softap_endpoint=None, ble_devname=None):
# in which case, the automated discovery will fail and the client
# will fallback to using the provided UUIDs instead
nu_lookup = {'prov-session': 'ff51', 'prov-config': 'ff52', 'proto-ver': 'ff53'}
tp = transport.Transport_BLE(devname=ble_devname,
tp = transport.Transport_BLE(devname=service_name,
service_uuid='0000ffff-0000-1000-8000-00805f9b34fb',
nu_lookup=nu_lookup)
elif (sel_transport == 'console'):
@ -93,31 +98,53 @@ def version_match(tp, protover, verbose=False):
if response.lower() == protover.lower():
return True
# Else interpret this as JSON structure containing
# information with versions and capabilities of both
# provisioning service and application
info = json.loads(response)
if info['prov']['ver'].lower() == protover.lower():
return True
try:
# Else interpret this as JSON structure containing
# information with versions and capabilities of both
# provisioning service and application
info = json.loads(response)
if info['prov']['ver'].lower() == protover.lower():
return True
except ValueError:
# If decoding as JSON fails, it means that capabilities
# are not supported
return False
return False
except Exception as e:
on_except(e)
return None
def has_capability(tp, capability, verbose=False):
def has_capability(tp, capability='none', verbose=False):
# Note : default value of `capability` argument cannot be empty string
# because protocomm_httpd expects non zero content lengths
try:
response = tp.send_data('proto-ver', capability)
if verbose:
print("proto-ver response : ", response)
info = json.loads(response)
if capability in info['prov']['cap']:
return True
try:
# Interpret this as JSON structure containing
# information with versions and capabilities of both
# provisioning service and application
info = json.loads(response)
supported_capabilities = info['prov']['cap']
if capability.lower() == 'none':
# No specific capability to check, but capabilities
# feature is present so return True
return True
elif capability in supported_capabilities:
return True
return False
except Exception as e:
except ValueError:
# If decoding as JSON fails, it means that capabilities
# are not supported
return False
except RuntimeError as e:
on_except(e)
return False
@ -239,75 +266,123 @@ def get_wifi_config(tp, sec):
return None
def desc_format(*args):
desc = ''
for arg in args:
desc += textwrap.fill(replace_whitespace=False, text=arg) + "\n"
return desc
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Generate ESP prov payload")
parser = argparse.ArgumentParser(description=desc_format(
'ESP Provisioning tool for configuring devices '
'running protocomm based provisioning service.',
'See esp-idf/examples/provisioning for sample applications'),
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument("--ssid", dest='ssid', type=str,
help="SSID of Wi-Fi Network", default='')
parser.add_argument("--passphrase", dest='passphrase', type=str,
help="Passphrase of Wi-Fi network", default='')
parser.add_argument("--transport", required=True, dest='mode', type=str,
help=desc_format(
'Mode of transport over which provisioning is to be performed.',
'This should be one of "softap", "ble" or "console"'))
parser.add_argument("--sec_ver", dest='secver', type=int,
help="Security scheme version", default=None)
parser.add_argument("--proto_ver", dest='protover', type=str,
help="Protocol version", default='')
parser.add_argument("--pop", dest='pop', type=str,
help="Proof of possession", default='')
parser.add_argument("--service_name", dest='name', type=str,
help=desc_format(
'This specifies the name of the provisioning service to connect to, '
'depending upon the mode of transport :',
'\t- transport "ble" : The BLE Device Name',
'\t- transport "softap" : HTTP Server hostname or IP',
'\t (default "192.168.4.1:80")'))
parser.add_argument("--softap_endpoint", dest='softap_endpoint', type=str,
help="<softap_ip:port>, http(s):// shouldn't be included", default='192.168.4.1:80')
parser.add_argument("--proto_ver", dest='version', type=str, default='',
help=desc_format(
'This checks the protocol version of the provisioning service running '
'on the device before initiating Wi-Fi configuration'))
parser.add_argument("--ble_devname", dest='ble_devname', type=str,
help="BLE Device Name", default='')
parser.add_argument("--sec_ver", dest='secver', type=int, default=None,
help=desc_format(
'Protocomm security scheme used by the provisioning service for secure '
'session establishment. Accepted values are :',
'\t- 0 : No security',
'\t- 1 : X25519 key exchange + AES-CTR encryption',
'\t + Authentication using Proof of Possession (PoP)',
'In case device side application uses IDF\'s provisioning manager, '
'the compatible security version is automatically determined from '
'capabilities retrieved via the version endpoint'))
parser.add_argument("--transport", dest='provmode', type=str,
help="provisioning mode i.e console or softap or ble", default='softap')
parser.add_argument("--pop", dest='pop', type=str, default='',
help=desc_format(
'This specifies the Proof of possession (PoP) when security scheme 1 '
'is used'))
parser.add_argument("--custom_config", help="Provision Custom Configuration",
action="store_true")
parser.add_argument("--custom_info", dest='custom_info', type=str,
help="Custom Config Info String", default='<some custom info string>')
parser.add_argument("--custom_ver", dest='custom_ver', type=int,
help="Custom Config Version Number", default=2)
parser.add_argument("--ssid", dest='ssid', type=str, default='',
help=desc_format(
'This configures the device to use SSID of the Wi-Fi network to which '
'we would like it to connect to permanently, once provisioning is complete. '
'If Wi-Fi scanning is supported by the provisioning service, this need not '
'be specified'))
parser.add_argument("--passphrase", dest='passphrase', type=str, default='',
help=desc_format(
'This configures the device to use Passphrase for the Wi-Fi network to which '
'we would like it to connect to permanently, once provisioning is complete. '
'If Wi-Fi scanning is supported by the provisioning service, this need not '
'be specified'))
parser.add_argument("--custom_config", action="store_true",
help=desc_format(
'This is an optional parameter, only intended for use with '
'"examples/provisioning/custom_config"'))
parser.add_argument("--custom_info", dest='custom_info', type=str, default='<some custom info string>',
help=desc_format(
'Custom Config Info String. "--custom_config" must be specified for using this'))
parser.add_argument("--custom_ver", dest='custom_ver', type=int, default=2,
help=desc_format(
'Custom Config Version Number. "--custom_config" must be specified for using this'))
parser.add_argument("-v","--verbose", help="Increase output verbosity", action="store_true")
parser.add_argument("-v","--verbose", help="increase output verbosity", action="store_true")
args = parser.parse_args()
if args.protover != '':
print("==== Esp_Prov Version: " + args.protover + " ====")
obj_transport = get_transport(args.provmode, args.softap_endpoint, args.ble_devname)
obj_transport = get_transport(args.mode.lower(), args.name)
if obj_transport is None:
print("---- Invalid provisioning mode ----")
print("---- Failed to establish connection ----")
exit(1)
# If security version not specified check in capabilities
if args.secver is None:
# First check if capabilities are supported or not
if not has_capability(obj_transport):
print('Security capabilities could not be determined. Please specify "--sec_ver" explicitly')
print("---- Invalid Security Version ----")
exit(2)
# When no_sec is present, use security 0, else security 1
args.secver = int(not has_capability(obj_transport, 'no_sec'))
print("Security scheme determined to be :", args.secver)
if (args.secver != 0) and not has_capability(obj_transport, 'no_pop'):
if len(args.pop) == 0:
print("---- Proof of Possession argument not provided ----")
exit(2)
elif len(args.pop) != 0:
print("---- Proof of Possession will be ignored ----")
args.pop = ''
if (args.secver != 0) and not has_capability(obj_transport, 'no_pop'):
if len(args.pop) == 0:
print("---- Proof of Possession argument not provided ----")
exit(2)
elif len(args.pop) != 0:
print("---- Proof of Possession will be ignored ----")
args.pop = ''
obj_security = get_security(args.secver, args.pop, args.verbose)
if obj_security is None:
print("---- Invalid Security Version ----")
exit(2)
if args.protover != '':
if args.version != '':
print("\n==== Verifying protocol version ====")
if not version_match(obj_transport, args.protover, args.verbose):
if not version_match(obj_transport, args.version, args.verbose):
print("---- Error in protocol version matching ----")
exit(3)
print("==== Verified protocol version successfully ====")
print("\n==== Starting Session ====")
if not establish_session(obj_transport, obj_security):
print("Failed to establish session. Ensure that security scheme and proof of possession are correct")
print("---- Error in establishing session ----")
exit(4)
print("==== Session Established ====")
@ -328,7 +403,7 @@ if __name__ == '__main__':
while True:
print("\n==== Scanning Wi-Fi APs ====")
start_time = time.time()
APs = scan_wifi_APs(args.provmode, obj_transport, obj_security)
APs = scan_wifi_APs(args.mode.lower(), obj_transport, obj_security)
end_time = time.time()
print("\n++++ Scan finished in " + str(end_time - start_time) + " sec")
if APs is None:

View File

@ -35,6 +35,11 @@ class Transport_HTTP(Transport):
else:
ssl_ctx = ssl.create_default_context(cafile=certfile)
self.conn = http.client.HTTPSConnection(hostname, context=ssl_ctx, timeout=30)
try:
print("Connecting to " + hostname)
self.conn.connect()
except Exception as err:
raise RuntimeError("Connection Failure : " + str(err))
self.headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
def _send_post_request(self, path, data):