mirror of
https://github.com/espressif/ESP8266_RTOS_SDK.git
synced 2025-05-28 13:40:37 +08:00
feature/wifi-provisioning: Added wifi-provisioning component from idf.
Added wifi-provisioning examples and esp_prov tool.
This commit is contained in:
109
tools/esp_prov/README.md
Normal file
109
tools/esp_prov/README.md
Normal file
@ -0,0 +1,109 @@
|
||||
# ESP Provisioning Tool
|
||||
|
||||
# NAME
|
||||
`esp_prov` - A python based utility for testing the provisioning examples over a host
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
```
|
||||
python esp_prov.py --transport < mode of provisioning : softap \ ble \ console > --ssid < AP SSID > --passphrase < AP Password > --sec_ver < Security version 0 / 1 > [ Optional parameters... ]
|
||||
```
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Usage of `esp-prov` assumes that the provisioning app has specific protocomm endpoints active. These endpoints are active in the provisioning examples and accept specific protobuf data structures:
|
||||
|
||||
| Endpoint Name | URI (HTTP server on ip:port) | UUID (BLE) | Description |
|
||||
|---------------|------------------------------|--------------------------------------|-----------------------------------------------------------|
|
||||
| prov-session | http://ip:port/prov-session | 0000ff51-0000-1000-8000-00805f9b34fb | Security endpoint used for session establishment |
|
||||
| prov-config | http://ip:port/prov-config | 0000ff52-0000-1000-8000-00805f9b34fb | Endpoint used for configuring Wi-Fi credentials on device |
|
||||
| proto-ver | http://ip:port/proto-ver | 0000ff53-0000-1000-8000-00805f9b34fb | Version endpoint for checking protocol compatibility |
|
||||
| custom-config | http://ip:port/custom-config | NA | Optional endpoint for configuring custom credentials |
|
||||
|
||||
# PARAMETERS
|
||||
|
||||
* `--help`
|
||||
Print the list of options along with brief descriptions
|
||||
|
||||
* `--verbose`, `-v`
|
||||
Sets the verbosity level of output log
|
||||
|
||||
* `--transport <mode>`
|
||||
Three options are available:
|
||||
* `softap`
|
||||
For SoftAP + HTTPD based provisioning. This assumes that the device is running in Wi-Fi SoftAP mode and hosts an HTTP server supporting specific endpoint URIs. Also client needs to connect to the device softAP network before running `esp_prov`
|
||||
* `ble`
|
||||
For BLE based provisioning (Linux support only. In Windows/macOS it redirects to console). This assumes that the provisioning endpoints are active on the device with specific BLE service UUIDs
|
||||
* `console`
|
||||
For debugging via console based provisioning. The client->device commands are printed to STDOUT and device->client messages are accepted via STDIN. This is to be used when device is accepting provisioning commands on UART console.
|
||||
|
||||
* `--ssid <AP SSID>`
|
||||
For specifying the SSID of the Wi-Fi AP to which the device is to connect after provisioning
|
||||
|
||||
* `--passphrase <AP Password>`
|
||||
For specifying the password of the Wi-Fi AP to which the device is to connect after provisioning
|
||||
|
||||
* `--sec_ver <Security version number>`
|
||||
For specifying version of protocomm endpoint security to use. For now two versions are supported:
|
||||
* `0` for `protocomm_security0`
|
||||
* `1` for `protocomm_security1`
|
||||
|
||||
* `--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
|
||||
|
||||
* `--proto_ver <Provisioning application version string>` (Optional) (Default `V0.1`)
|
||||
For specifying version string for checking compatibility with provisioning app prior to starting provisioning process
|
||||
|
||||
* `--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
|
||||
|
||||
* `--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
|
||||
|
||||
* `--custom_info <some string>` (Optional) (Only use along with `--custom_config`)
|
||||
For specifying an information string to be sent to the `custom-config` endpoint during provisioning
|
||||
|
||||
* `--custom_ver <some integer>` (Optional) (Only use along with `--custom_config`)
|
||||
For specifying a version number (int) to be sent to the `custom-config` endpoint during provisioning
|
||||
|
||||
# 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)
|
||||
|
||||
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
|
||||
|
||||
This requires the following python libraries to run (included in requirements.txt):
|
||||
* `future`
|
||||
* `protobuf`
|
||||
* `cryptography`
|
||||
|
||||
Run `pip install -r $IDF_PATH/tools/esp_prov/requirements.txt`
|
||||
|
||||
Note :
|
||||
* 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
|
||||
|
||||
Please refer to the README.md files with the examples present under `$IDF_PATH/examples/provisioning/`, namely:
|
||||
|
||||
* `ble_prov`
|
||||
* `softap_prov`
|
||||
* `custom_config`
|
||||
* `console_prov`
|
||||
|
||||
Each of these examples use specific options of the `esp_prov` tool and give an overview to simple as well as advanced usage scenarios.
|
208
tools/esp_prov/esp_prov.py
Normal file
208
tools/esp_prov/esp_prov.py
Normal file
@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# 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 argparse
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
|
||||
idf_path = os.environ['IDF_PATH']
|
||||
sys.path.insert(0, idf_path + "/components/protocomm/python")
|
||||
sys.path.insert(1, idf_path + "/tools/esp_prov")
|
||||
|
||||
import security
|
||||
import transport
|
||||
import prov
|
||||
|
||||
def get_security(secver, pop=None, verbose=False):
|
||||
if secver == 1:
|
||||
return security.Security1(pop, verbose)
|
||||
elif secver == 0:
|
||||
return security.Security0(verbose)
|
||||
return None
|
||||
|
||||
def get_transport(sel_transport, softap_endpoint=None, ble_devname=None):
|
||||
try:
|
||||
tp = None
|
||||
if (sel_transport == 'softap'):
|
||||
tp = transport.Transport_Softap(softap_endpoint)
|
||||
elif (sel_transport == 'ble'):
|
||||
tp = transport.Transport_BLE(devname = ble_devname,
|
||||
service_uuid = '0000ffff-0000-1000-8000-00805f9b34fb',
|
||||
nu_lookup = {
|
||||
'prov-session': 'ff51',
|
||||
'prov-config' : 'ff52',
|
||||
'proto-ver' : 'ff53'
|
||||
})
|
||||
elif (sel_transport == 'console'):
|
||||
tp = transport.Transport_Console()
|
||||
return tp
|
||||
except RuntimeError as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
def version_match(tp, protover):
|
||||
try:
|
||||
response = tp.send_data('proto-ver', protover)
|
||||
if response != protover:
|
||||
return False
|
||||
return True
|
||||
except RuntimeError as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
def establish_session(tp, sec):
|
||||
try:
|
||||
response = None
|
||||
while True:
|
||||
request = sec.security_session(response)
|
||||
if request == None:
|
||||
break
|
||||
response = tp.send_data('prov-session', request)
|
||||
if (response == None):
|
||||
return False
|
||||
return True
|
||||
except RuntimeError as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
def custom_config(tp, sec, custom_info, custom_ver):
|
||||
try:
|
||||
message = prov.custom_config_request(sec, custom_info, custom_ver)
|
||||
response = tp.send_data('custom-config', message)
|
||||
return (prov.custom_config_response(sec, response) == 0)
|
||||
except RuntimeError as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
def send_wifi_config(tp, sec, ssid, passphrase):
|
||||
try:
|
||||
message = prov.config_set_config_request(sec, ssid, passphrase)
|
||||
response = tp.send_data('prov-config', message)
|
||||
return (prov.config_set_config_response(sec, response) == 0)
|
||||
except RuntimeError as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
def apply_wifi_config(tp, sec):
|
||||
try:
|
||||
message = prov.config_apply_config_request(sec)
|
||||
response = tp.send_data('prov-config', message)
|
||||
return (prov.config_set_config_response(sec, response) == 0)
|
||||
except RuntimeError as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
def get_wifi_config(tp, sec):
|
||||
try:
|
||||
message = prov.config_get_status_request(sec)
|
||||
response = tp.send_data('prov-config', message)
|
||||
return prov.config_get_status_response(sec, response)
|
||||
except RuntimeError as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description="Generate ESP prov payload")
|
||||
|
||||
parser.add_argument("--ssid", dest = 'ssid', type = str,
|
||||
help = "SSID of Wi-Fi Network", required = True)
|
||||
parser.add_argument("--passphrase", dest = 'passphrase', type = str,
|
||||
help = "Passphrase of Wi-Fi network", default = '')
|
||||
|
||||
parser.add_argument("--sec_ver", dest = 'secver', type = int,
|
||||
help = "Security scheme version", default = 1)
|
||||
parser.add_argument("--proto_ver", dest = 'protover', type = str,
|
||||
help = "Protocol version", default = 'V0.1')
|
||||
parser.add_argument("--pop", dest = 'pop', type = str,
|
||||
help = "Proof of possession", default = '')
|
||||
|
||||
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("--ble_devname", dest = 'ble_devname', type = str,
|
||||
help = "BLE Device Name", default = '')
|
||||
|
||||
parser.add_argument("--transport", dest = 'provmode', type = str,
|
||||
help = "provisioning mode i.e console or softap or ble", default = 'softap')
|
||||
|
||||
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("-v","--verbose", help = "increase output verbosity",
|
||||
action = "store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
print("==== Esp_Prov Version: " + args.protover + " ====")
|
||||
|
||||
security = get_security(args.secver, args.pop, args.verbose)
|
||||
if security == None:
|
||||
print("---- Invalid Security Version ----")
|
||||
exit(1)
|
||||
|
||||
transport = get_transport(args.provmode, args.softap_endpoint, args.ble_devname)
|
||||
if transport == None:
|
||||
print("---- Invalid provisioning mode ----")
|
||||
exit(2)
|
||||
|
||||
print("\n==== Verifying protocol version ====")
|
||||
if not version_match(transport, args.protover):
|
||||
print("---- Error in protocol version matching ----")
|
||||
exit(3)
|
||||
print("==== Verified protocol version successfully ====")
|
||||
|
||||
print("\n==== Starting Session ====")
|
||||
if not establish_session(transport, security):
|
||||
print("---- Error in establishing session ----")
|
||||
exit(4)
|
||||
print("==== Session Established ====")
|
||||
|
||||
if args.custom_config:
|
||||
print("\n==== Sending Custom config to esp32 ====")
|
||||
if not custom_config(transport, security, args.custom_info, args.custom_ver):
|
||||
print("---- Error in custom config ----")
|
||||
exit(5)
|
||||
print("==== Custom config sent successfully ====")
|
||||
|
||||
print("\n==== Sending Wi-Fi credential to esp32 ====")
|
||||
if not send_wifi_config(transport, security, args.ssid, args.passphrase):
|
||||
print("---- Error in send Wi-Fi config ----")
|
||||
exit(6)
|
||||
print("==== Wi-Fi Credentials sent successfully ====")
|
||||
|
||||
print("\n==== Applying config to esp32 ====")
|
||||
if not apply_wifi_config(transport, security):
|
||||
print("---- Error in apply Wi-Fi config ----")
|
||||
exit(7)
|
||||
print("==== Apply config sent successfully ====")
|
||||
|
||||
while True:
|
||||
time.sleep(5)
|
||||
print("\n==== Wi-Fi connection state ====")
|
||||
ret = get_wifi_config(transport, security)
|
||||
if (ret == 1):
|
||||
continue
|
||||
elif (ret == 0):
|
||||
print("==== Provisioning was successful ====")
|
||||
else:
|
||||
print("---- Provisioning failed ----")
|
||||
break
|
32
tools/esp_prov/proto/__init__.py
Normal file
32
tools/esp_prov/proto/__init__.py
Normal file
@ -0,0 +1,32 @@
|
||||
# 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.
|
||||
#
|
||||
|
||||
import imp
|
||||
import os
|
||||
|
||||
idf_path = os.environ['IDF_PATH']
|
||||
|
||||
# protocomm component related python files generated from .proto files
|
||||
constants_pb2 = imp.load_source("constants_pb2", idf_path + "/components/protocomm/python/constants_pb2.py")
|
||||
sec0_pb2 = imp.load_source("sec0_pb2", idf_path + "/components/protocomm/python/sec0_pb2.py")
|
||||
sec1_pb2 = imp.load_source("sec1_pb2", idf_path + "/components/protocomm/python/sec1_pb2.py")
|
||||
session_pb2 = imp.load_source("session_pb2", idf_path + "/components/protocomm/python/session_pb2.py")
|
||||
|
||||
# wifi_provisioning component related python files generated from .proto files
|
||||
wifi_constants_pb2 = imp.load_source("wifi_constants_pb2", idf_path + "/components/wifi_provisioning/python/wifi_constants_pb2.py")
|
||||
wifi_config_pb2 = imp.load_source("wifi_config_pb2", idf_path + "/components/wifi_provisioning/python/wifi_config_pb2.py")
|
||||
|
||||
# custom_provisioning component related python files generated from .proto files
|
||||
custom_config_pb2 = imp.load_source("custom_config_pb2", idf_path + "/examples/provisioning/custom_config/components/custom_provisioning/python/custom_config_pb2.py")
|
17
tools/esp_prov/prov/__init__.py
Normal file
17
tools/esp_prov/prov/__init__.py
Normal file
@ -0,0 +1,17 @@
|
||||
# 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 .wifi_prov import *
|
||||
from .custom_prov import *
|
43
tools/esp_prov/prov/custom_prov.py
Normal file
43
tools/esp_prov/prov/custom_prov.py
Normal file
@ -0,0 +1,43 @@
|
||||
# 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.
|
||||
#
|
||||
|
||||
# APIs for interpreting and creating protobuf packets for `custom-config` protocomm endpoint
|
||||
|
||||
from __future__ import print_function
|
||||
from future.utils import tobytes
|
||||
|
||||
import utils
|
||||
import proto
|
||||
|
||||
def print_verbose(security_ctx, data):
|
||||
if (security_ctx.verbose):
|
||||
print("++++ " + data + " ++++")
|
||||
|
||||
def custom_config_request(security_ctx, info, version):
|
||||
# Form protobuf request packet from custom-config data
|
||||
cmd = proto.custom_config_pb2.CustomConfigRequest()
|
||||
cmd.info = tobytes(info)
|
||||
cmd.version = version
|
||||
enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()).decode('latin-1')
|
||||
print_verbose(security_ctx, "Client -> Device (CustomConfig cmd) " + utils.str_to_hexstr(enc_cmd))
|
||||
return enc_cmd
|
||||
|
||||
def custom_config_response(security_ctx, response_data):
|
||||
# Interpret protobuf response packet
|
||||
decrypt = security_ctx.decrypt_data(tobytes(response_data))
|
||||
cmd_resp = proto.custom_config_pb2.CustomConfigResponse()
|
||||
cmd_resp.ParseFromString(decrypt)
|
||||
print_verbose(security_ctx, "CustomConfig status " + str(cmd_resp.status))
|
||||
return cmd_resp.status
|
91
tools/esp_prov/prov/wifi_prov.py
Normal file
91
tools/esp_prov/prov/wifi_prov.py
Normal file
@ -0,0 +1,91 @@
|
||||
# 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.
|
||||
#
|
||||
|
||||
# APIs for interpreting and creating protobuf packets for Wi-Fi provisioning
|
||||
|
||||
from __future__ import print_function
|
||||
from future.utils import tobytes
|
||||
|
||||
import utils
|
||||
import proto
|
||||
|
||||
def print_verbose(security_ctx, data):
|
||||
if (security_ctx.verbose):
|
||||
print("++++ " + data + " ++++")
|
||||
|
||||
def config_get_status_request(security_ctx):
|
||||
# Form protobuf request packet for GetStatus command
|
||||
cfg1 = proto.wifi_config_pb2.WiFiConfigPayload()
|
||||
cfg1.msg = proto.wifi_config_pb2.TypeCmdGetStatus
|
||||
cmd_get_status = proto.wifi_config_pb2.CmdGetStatus()
|
||||
cfg1.cmd_get_status.MergeFrom(cmd_get_status)
|
||||
encrypted_cfg = security_ctx.encrypt_data(cfg1.SerializeToString()).decode('latin-1')
|
||||
print_verbose(security_ctx, "Client -> Device (Encrypted CmdGetStatus) " + utils.str_to_hexstr(encrypted_cfg))
|
||||
return encrypted_cfg
|
||||
|
||||
def config_get_status_response(security_ctx, response_data):
|
||||
# Interpret protobuf response packet from GetStatus command
|
||||
decrypted_message = security_ctx.decrypt_data(tobytes(response_data))
|
||||
cmd_resp1 = proto.wifi_config_pb2.WiFiConfigPayload()
|
||||
cmd_resp1.ParseFromString(decrypted_message)
|
||||
print_verbose(security_ctx, "Response type " + str(cmd_resp1.msg))
|
||||
print_verbose(security_ctx, "Response status " + str(cmd_resp1.resp_get_status.status))
|
||||
if cmd_resp1.resp_get_status.sta_state == 0:
|
||||
print("++++ WiFi state: " + "connected ++++")
|
||||
elif cmd_resp1.resp_get_status.sta_state == 1:
|
||||
print("++++ WiFi state: " + "connecting... ++++")
|
||||
elif cmd_resp1.resp_get_status.sta_state == 2:
|
||||
print("++++ WiFi state: " + "disconnected ++++")
|
||||
elif cmd_resp1.resp_get_status.sta_state == 3:
|
||||
print("++++ WiFi state: " + "connection failed ++++")
|
||||
if cmd_resp1.resp_get_status.fail_reason == 0:
|
||||
print("++++ Failure reason: " + "Incorrect Password ++++")
|
||||
elif cmd_resp1.resp_get_status.fail_reason == 1:
|
||||
print("++++ Failure reason: " + "Incorrect SSID ++++")
|
||||
return cmd_resp1.resp_get_status.sta_state
|
||||
|
||||
def config_set_config_request(security_ctx, ssid, passphrase):
|
||||
# Form protobuf request packet for SetConfig command
|
||||
cmd = proto.wifi_config_pb2.WiFiConfigPayload()
|
||||
cmd.msg = proto.wifi_config_pb2.TypeCmdSetConfig
|
||||
cmd.cmd_set_config.ssid = tobytes(ssid)
|
||||
cmd.cmd_set_config.passphrase = tobytes(passphrase)
|
||||
enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()).decode('latin-1')
|
||||
print_verbose(security_ctx, "Client -> Device (SetConfig cmd) " + utils.str_to_hexstr(enc_cmd))
|
||||
return enc_cmd
|
||||
|
||||
def config_set_config_response(security_ctx, response_data):
|
||||
# Interpret protobuf response packet from SetConfig command
|
||||
decrypt = security_ctx.decrypt_data(tobytes(response_data))
|
||||
cmd_resp4 = proto.wifi_config_pb2.WiFiConfigPayload()
|
||||
cmd_resp4.ParseFromString(decrypt)
|
||||
print_verbose(security_ctx, "SetConfig status " + str(cmd_resp4.resp_set_config.status))
|
||||
return cmd_resp4.resp_set_config.status
|
||||
|
||||
def config_apply_config_request(security_ctx):
|
||||
# Form protobuf request packet for ApplyConfig command
|
||||
cmd = proto.wifi_config_pb2.WiFiConfigPayload()
|
||||
cmd.msg = proto.wifi_config_pb2.TypeCmdApplyConfig
|
||||
enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()).decode('latin-1')
|
||||
print_verbose(security_ctx, "Client -> Device (ApplyConfig cmd) " + utils.str_to_hexstr(enc_cmd))
|
||||
return enc_cmd
|
||||
|
||||
def config_apply_config_response(security_ctx, response_data):
|
||||
# Interpret protobuf response packet from ApplyConfig command
|
||||
decrypt = security_ctx.decrypt_data(tobytes(response_data))
|
||||
cmd_resp5 = proto.wifi_config_pb2.WiFiConfigPayload()
|
||||
cmd_resp5.ParseFromString(decrypt)
|
||||
print_verbose(security_ctx, "ApplyConfig status " + str(cmd_resp5.resp_apply_config.status))
|
||||
return cmd_resp5.resp_apply_config.status
|
3
tools/esp_prov/requirements.txt
Normal file
3
tools/esp_prov/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
future
|
||||
protobuf
|
||||
cryptography
|
1
tools/esp_prov/requirements_linux_extra.txt
Normal file
1
tools/esp_prov/requirements_linux_extra.txt
Normal file
@ -0,0 +1 @@
|
||||
dbus-python
|
17
tools/esp_prov/security/__init__.py
Normal file
17
tools/esp_prov/security/__init__.py
Normal file
@ -0,0 +1,17 @@
|
||||
# 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 .security0 import *
|
||||
from .security1 import *
|
21
tools/esp_prov/security/security.py
Normal file
21
tools/esp_prov/security/security.py
Normal file
@ -0,0 +1,21 @@
|
||||
# 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.
|
||||
#
|
||||
|
||||
# Base class for protocomm security
|
||||
|
||||
class Security:
|
||||
def __init__(self, security_session):
|
||||
self.security_session = security_session
|
||||
|
65
tools/esp_prov/security/security0.py
Normal file
65
tools/esp_prov/security/security0.py
Normal file
@ -0,0 +1,65 @@
|
||||
# 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.
|
||||
#
|
||||
|
||||
# APIs for interpreting and creating protobuf packets for
|
||||
# protocomm endpoint with security type protocomm_security0
|
||||
|
||||
from __future__ import print_function
|
||||
from future.utils import tobytes
|
||||
|
||||
import utils
|
||||
import proto
|
||||
from .security import *
|
||||
|
||||
class Security0(Security):
|
||||
def __init__(self, verbose):
|
||||
# Initialize state of the security1 FSM
|
||||
self.session_state = 0
|
||||
self.verbose = verbose
|
||||
Security.__init__(self, self.security0_session)
|
||||
|
||||
def security0_session(self, response_data):
|
||||
# protocomm security0 FSM which interprets/forms
|
||||
# protobuf packets according to present state of session
|
||||
if (self.session_state == 0):
|
||||
self.session_state = 1
|
||||
return self.setup0_request()
|
||||
if (self.session_state == 1):
|
||||
self.setup0_response(response_data)
|
||||
return None
|
||||
|
||||
def setup0_request(self):
|
||||
# Form protocomm security0 request packet
|
||||
setup_req = proto.session_pb2.SessionData()
|
||||
setup_req.sec_ver = 0
|
||||
session_cmd = proto.sec0_pb2.S0SessionCmd()
|
||||
setup_req.sec0.sc.MergeFrom(session_cmd)
|
||||
return setup_req.SerializeToString().decode('latin-1')
|
||||
|
||||
def setup0_response(self, response_data):
|
||||
# Interpret protocomm security0 response packet
|
||||
setup_resp = proto.session_pb2.SessionData()
|
||||
setup_resp.ParseFromString(tobytes(response_data))
|
||||
# Check if security scheme matches
|
||||
if setup_resp.sec_ver != proto.session_pb2.SecScheme0:
|
||||
print("Incorrect sec scheme")
|
||||
|
||||
def encrypt_data(self, data):
|
||||
# Passive. No encryption when security0 used
|
||||
return data
|
||||
|
||||
def decrypt_data(self, data):
|
||||
# Passive. No encryption when security0 used
|
||||
return data
|
164
tools/esp_prov/security/security1.py
Normal file
164
tools/esp_prov/security/security1.py
Normal file
@ -0,0 +1,164 @@
|
||||
# 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.
|
||||
#
|
||||
|
||||
# APIs for interpreting and creating protobuf packets for
|
||||
# protocomm endpoint with security type protocomm_security1
|
||||
|
||||
from __future__ import print_function
|
||||
from future.utils import tobytes
|
||||
|
||||
import utils
|
||||
import proto
|
||||
from .security import *
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
import session_pb2
|
||||
|
||||
# Enum for state of protocomm_security1 FSM
|
||||
class security_state:
|
||||
REQUEST1 = 0
|
||||
RESPONSE1_REQUEST2 = 1
|
||||
RESPONSE2 = 2
|
||||
FINISHED = 3
|
||||
|
||||
def xor(a, b):
|
||||
# XOR two inputs of type `bytes`
|
||||
ret = bytearray()
|
||||
# Decode the input bytes to strings
|
||||
a = a.decode('latin-1')
|
||||
b = b.decode('latin-1')
|
||||
for i in range(max(len(a), len(b))):
|
||||
# Convert the characters to corresponding 8-bit ASCII codes
|
||||
# then XOR them and store in bytearray
|
||||
ret.append(([0, ord(a[i])][i < len(a)]) ^ ([0, ord(b[i])][i < len(b)]))
|
||||
# Convert bytearray to bytes
|
||||
return bytes(ret)
|
||||
|
||||
class Security1(Security):
|
||||
def __init__(self, pop, verbose):
|
||||
# Initialize state of the security1 FSM
|
||||
self.session_state = security_state.REQUEST1
|
||||
self.pop = tobytes(pop)
|
||||
self.verbose = verbose
|
||||
Security.__init__(self, self.security1_session)
|
||||
|
||||
def security1_session(self, response_data):
|
||||
# protocomm security1 FSM which interprets/forms
|
||||
# protobuf packets according to present state of session
|
||||
if (self.session_state == security_state.REQUEST1):
|
||||
self.session_state = security_state.RESPONSE1_REQUEST2
|
||||
return self.setup0_request()
|
||||
if (self.session_state == security_state.RESPONSE1_REQUEST2):
|
||||
self.session_state = security_state.RESPONSE2
|
||||
self.setup0_response(response_data)
|
||||
return self.setup1_request()
|
||||
if (self.session_state == security_state.RESPONSE2):
|
||||
self.session_state = security_state.FINISHED
|
||||
self.setup1_response(response_data)
|
||||
return None
|
||||
else:
|
||||
print("Unexpected state")
|
||||
return None
|
||||
|
||||
def __generate_key(self):
|
||||
# Generate private and public key pair for client
|
||||
self.client_private_key = X25519PrivateKey.generate()
|
||||
self.client_public_key = self.client_private_key.public_key().public_bytes()
|
||||
|
||||
def _print_verbose(self, data):
|
||||
if (self.verbose):
|
||||
print("++++ " + data + " ++++")
|
||||
|
||||
def setup0_request(self):
|
||||
# Form SessionCmd0 request packet using client public key
|
||||
setup_req = session_pb2.SessionData()
|
||||
setup_req.sec_ver = session_pb2.SecScheme1
|
||||
self.__generate_key()
|
||||
setup_req.sec1.sc0.client_pubkey = self.client_public_key
|
||||
self._print_verbose("Client Public Key:\t" + utils.str_to_hexstr(self.client_public_key.decode('latin-1')))
|
||||
return setup_req.SerializeToString().decode('latin-1')
|
||||
|
||||
def setup0_response(self, response_data):
|
||||
# Interpret SessionResp0 response packet
|
||||
setup_resp = proto.session_pb2.SessionData()
|
||||
setup_resp.ParseFromString(tobytes(response_data))
|
||||
self._print_verbose("Security version:\t" + str(setup_resp.sec_ver))
|
||||
if setup_resp.sec_ver != session_pb2.SecScheme1:
|
||||
print("Incorrect sec scheme")
|
||||
exit(1)
|
||||
self.device_public_key = setup_resp.sec1.sr0.device_pubkey
|
||||
# Device random is the initialization vector
|
||||
device_random = setup_resp.sec1.sr0.device_random
|
||||
self._print_verbose("Device Public Key:\t" + utils.str_to_hexstr(self.device_public_key.decode('latin-1')))
|
||||
self._print_verbose("Device Random:\t" + utils.str_to_hexstr(device_random.decode('latin-1')))
|
||||
|
||||
# Calculate Curve25519 shared key using Client private key and Device public key
|
||||
sharedK = self.client_private_key.exchange(X25519PublicKey.from_public_bytes(self.device_public_key))
|
||||
self._print_verbose("Shared Key:\t" + utils.str_to_hexstr(sharedK.decode('latin-1')))
|
||||
|
||||
# If PoP is provided, XOR SHA256 of PoP with the previously
|
||||
# calculated Shared Key to form the actual Shared Key
|
||||
if len(self.pop) > 0:
|
||||
# Calculate SHA256 of PoP
|
||||
h = hashes.Hash(hashes.SHA256(), backend=default_backend())
|
||||
h.update(self.pop)
|
||||
digest = h.finalize()
|
||||
# XOR with and update Shared Key
|
||||
sharedK = xor(sharedK, digest)
|
||||
self._print_verbose("New Shared Key XORed with PoP:\t" + utils.str_to_hexstr(sharedK.decode('latin-1')))
|
||||
# Initialize the encryption engine with Shared Key and initialization vector
|
||||
cipher = Cipher(algorithms.AES(sharedK), modes.CTR(device_random), backend=default_backend())
|
||||
self.cipher = cipher.encryptor()
|
||||
|
||||
def setup1_request(self):
|
||||
# Form SessionCmd1 request packet using encrypted device public key
|
||||
setup_req = proto.session_pb2.SessionData()
|
||||
setup_req.sec_ver = session_pb2.SecScheme1
|
||||
setup_req.sec1.msg = proto.sec1_pb2.Session_Command1
|
||||
# Encrypt device public key and attach to the request packet
|
||||
client_verify = self.cipher.update(self.device_public_key)
|
||||
self._print_verbose("Client Verify:\t" + utils.str_to_hexstr(client_verify.decode('latin-1')))
|
||||
setup_req.sec1.sc1.client_verify_data = client_verify
|
||||
return setup_req.SerializeToString().decode('latin-1')
|
||||
|
||||
def setup1_response(self, response_data):
|
||||
# Interpret SessionResp1 response packet
|
||||
setup_resp = proto.session_pb2.SessionData()
|
||||
setup_resp.ParseFromString(tobytes(response_data))
|
||||
# Ensure security scheme matches
|
||||
if setup_resp.sec_ver == session_pb2.SecScheme1:
|
||||
# Read encrypyed device verify string
|
||||
device_verify = setup_resp.sec1.sr1.device_verify_data
|
||||
self._print_verbose("Device verify:\t" + utils.str_to_hexstr(device_verify.decode('latin-1')))
|
||||
# Decrypt the device verify string
|
||||
enc_client_pubkey = self.cipher.update(setup_resp.sec1.sr1.device_verify_data)
|
||||
self._print_verbose("Enc client pubkey:\t " + utils.str_to_hexstr(enc_client_pubkey.decode('latin-1')))
|
||||
# Match decryped string with client public key
|
||||
if enc_client_pubkey != self.client_public_key:
|
||||
print("Mismatch in device verify")
|
||||
return -2
|
||||
else:
|
||||
print("Unsupported security protocol")
|
||||
return -1
|
||||
|
||||
def encrypt_data(self, data):
|
||||
return self.cipher.update(data)
|
||||
|
||||
def decrypt_data(self, data):
|
||||
return self.cipher.update(data)
|
18
tools/esp_prov/transport/__init__.py
Normal file
18
tools/esp_prov/transport/__init__.py
Normal file
@ -0,0 +1,18 @@
|
||||
# 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 .transport_console import *
|
||||
from .transport_softap import *
|
||||
from .transport_ble import *
|
202
tools/esp_prov/transport/ble_cli.py
Normal file
202
tools/esp_prov/transport/ble_cli.py
Normal file
@ -0,0 +1,202 @@
|
||||
# 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
|
||||
from builtins import input
|
||||
|
||||
import platform
|
||||
|
||||
import utils
|
||||
|
||||
fallback = True
|
||||
|
||||
# Check if platform is Linux and required packages are installed
|
||||
# else fallback to console mode
|
||||
if platform.system() == 'Linux':
|
||||
try:
|
||||
import dbus
|
||||
import dbus.mainloop.glib
|
||||
import time
|
||||
fallback = False
|
||||
except:
|
||||
pass
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
# BLE client (Linux Only) using Bluez and DBus
|
||||
class BLE_Bluez_Client:
|
||||
def connect(self, devname, iface, srv_uuid):
|
||||
self.devname = devname
|
||||
self.srv_uuid = srv_uuid
|
||||
self.device = None
|
||||
self.adapter = None
|
||||
self.adapter_props = None
|
||||
self.services = None
|
||||
|
||||
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
||||
bus = dbus.SystemBus()
|
||||
manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager")
|
||||
objects = manager.GetManagedObjects()
|
||||
|
||||
for path, interfaces in objects.items():
|
||||
adapter = interfaces.get("org.bluez.Adapter1")
|
||||
if adapter != 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")
|
||||
break
|
||||
|
||||
if self.adapter == None:
|
||||
raise RuntimeError("Bluetooth adapter not found")
|
||||
|
||||
self.adapter_props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(1))
|
||||
self.adapter.StartDiscovery()
|
||||
|
||||
retry = 10
|
||||
while (retry > 0):
|
||||
try:
|
||||
if self.device == None:
|
||||
print("Connecting...")
|
||||
# Wait for device to be discovered
|
||||
time.sleep(5)
|
||||
self._connect_()
|
||||
print("Connected")
|
||||
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
|
||||
self.adapter.StopDiscovery()
|
||||
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 objects.items():
|
||||
if "org.bluez.Device1" not in interfaces.keys():
|
||||
continue
|
||||
if interfaces["org.bluez.Device1"].get("Name") == self.devname:
|
||||
dev_path = path
|
||||
break
|
||||
|
||||
if dev_path == None:
|
||||
raise RuntimeError("BLE device not found")
|
||||
|
||||
try:
|
||||
self.device = bus.get_object("org.bluez", dev_path)
|
||||
self.device.Connect(dbus_interface='org.bluez.Device1')
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.device = None
|
||||
raise RuntimeError("BLE device could not connect")
|
||||
|
||||
def _get_services_(self):
|
||||
bus = dbus.SystemBus()
|
||||
manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager")
|
||||
objects = manager.GetManagedObjects()
|
||||
srv_path = None
|
||||
for path, interfaces in objects.items():
|
||||
if "org.bluez.GattService1" not in interfaces.keys():
|
||||
continue
|
||||
if path.startswith(self.device.object_path):
|
||||
service = bus.get_object("org.bluez", path)
|
||||
uuid = service.Get('org.bluez.GattService1', 'UUID',
|
||||
dbus_interface='org.freedesktop.DBus.Properties')
|
||||
if uuid == self.srv_uuid:
|
||||
srv_path = path
|
||||
break
|
||||
|
||||
if srv_path == None:
|
||||
raise RuntimeError("Provisioning service not found")
|
||||
|
||||
self.characteristics = dict()
|
||||
for path, interfaces in objects.items():
|
||||
if "org.bluez.GattCharacteristic1" not in interfaces.keys():
|
||||
continue
|
||||
if path.startswith(srv_path):
|
||||
chrc = bus.get_object("org.bluez", path)
|
||||
uuid = chrc.Get('org.bluez.GattCharacteristic1', 'UUID',
|
||||
dbus_interface='org.freedesktop.DBus.Properties')
|
||||
self.characteristics[uuid] = chrc
|
||||
|
||||
def has_characteristic(self, uuid):
|
||||
if uuid in self.characteristics.keys():
|
||||
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)
|
||||
if self.adapter_props:
|
||||
self.adapter_props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(0))
|
||||
|
||||
def send_data(self, characteristic_uuid, data):
|
||||
try:
|
||||
path = self.characteristics[characteristic_uuid]
|
||||
except KeyError:
|
||||
raise RuntimeError("Invalid characteristic : " + characteristic_uuid)
|
||||
path.WriteValue([ord(c) for c in data], {}, dbus_interface='org.bluez.GattCharacteristic1')
|
||||
return ''.join(chr(b) for b in path.ReadValue({}, dbus_interface='org.bluez.GattCharacteristic1'))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
# Console based BLE client for Cross Platform support
|
||||
class BLE_Console_Client:
|
||||
def connect(self, devname, iface, srv_uuid):
|
||||
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] ")
|
||||
if resp != 'Y' and resp != 'y':
|
||||
return False
|
||||
print("BLECLI >> List available attributes of the connected device")
|
||||
resp = input("BLECLI >> Is the service UUID '" + srv_uuid + "' listed among available attributes? [y/n] ")
|
||||
if resp != 'Y' and resp != 'y':
|
||||
return False
|
||||
return True
|
||||
|
||||
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 + "' :")
|
||||
print("\t>> " + utils.str_to_hexstr(data))
|
||||
print("BLECLI >> Enter data read from characteristic (in hex) :")
|
||||
resp = input("\t<< ")
|
||||
return utils.hexstr_to_str(resp)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
# Function to get client instance depending upon platform
|
||||
def get_client():
|
||||
if fallback:
|
||||
return BLE_Console_Client()
|
||||
return BLE_Bluez_Client()
|
28
tools/esp_prov/transport/transport.py
Normal file
28
tools/esp_prov/transport/transport.py
Normal file
@ -0,0 +1,28 @@
|
||||
# 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.
|
||||
#
|
||||
|
||||
# Base class for protocomm transport
|
||||
|
||||
import abc
|
||||
|
||||
class Transport():
|
||||
|
||||
@abc.abstractmethod
|
||||
def send_session_data(self, data):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def send_config_data(self, data):
|
||||
pass
|
57
tools/esp_prov/transport/transport_ble.py
Normal file
57
tools/esp_prov/transport/transport_ble.py
Normal file
@ -0,0 +1,57 @@
|
||||
# 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
|
||||
|
||||
from .transport import *
|
||||
|
||||
from . import ble_cli
|
||||
|
||||
class Transport_BLE(Transport):
|
||||
def __init__(self, devname, service_uuid, nu_lookup):
|
||||
# Expect service UUID like '0000ffff-0000-1000-8000-00805f9b34fb'
|
||||
for name in nu_lookup.keys():
|
||||
# Calculate characteristic UUID for each endpoint
|
||||
nu_lookup[name] = service_uuid[:4] + '{:02x}'.format(
|
||||
int(nu_lookup[name], 16) & int(service_uuid[4:8], 16)) + service_uuid[8:]
|
||||
self.name_uuid_lookup = nu_lookup
|
||||
|
||||
# Get BLE client module
|
||||
self.cli = ble_cli.get_client()
|
||||
|
||||
# Use client to connect to BLE device and bind to service
|
||||
if not self.cli.connect(devname = devname, iface = 'hci0', srv_uuid = service_uuid):
|
||||
raise RuntimeError("Failed to initialize transport")
|
||||
|
||||
# Check if expected characteristics are provided by the service
|
||||
for name in self.name_uuid_lookup.keys():
|
||||
if not self.cli.has_characteristic(self.name_uuid_lookup[name]):
|
||||
raise RuntimeError("'" + name + "' endpoint not found")
|
||||
|
||||
def __del__(self):
|
||||
# Make sure device is disconnected before application gets closed
|
||||
try:
|
||||
self.disconnect()
|
||||
except:
|
||||
pass
|
||||
|
||||
def disconnect(self):
|
||||
self.cli.disconnect()
|
||||
|
||||
def send_data(self, ep_name, data):
|
||||
# Write (and read) data to characteristic corresponding to the endpoint
|
||||
if ep_name not in self.name_uuid_lookup.keys():
|
||||
raise RuntimeError("Invalid endpoint : " + ep_name)
|
||||
return self.cli.send_data(self.name_uuid_lookup[ep_name], data)
|
32
tools/esp_prov/transport/transport_console.py
Normal file
32
tools/esp_prov/transport/transport_console.py
Normal file
@ -0,0 +1,32 @@
|
||||
# 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
|
||||
from builtins import input
|
||||
|
||||
import utils
|
||||
|
||||
from .transport import *
|
||||
|
||||
class Transport_Console(Transport):
|
||||
|
||||
def send_data(self, path, data, session_id = 0):
|
||||
print("Client->Device msg :", path, session_id, utils.str_to_hexstr(data))
|
||||
try:
|
||||
resp = input("Enter device->client msg : ")
|
||||
except Exception as err:
|
||||
print("error:", err)
|
||||
return None
|
||||
return utils.hexstr_to_str(resp)
|
39
tools/esp_prov/transport/transport_softap.py
Normal file
39
tools/esp_prov/transport/transport_softap.py
Normal file
@ -0,0 +1,39 @@
|
||||
# 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
|
||||
from future.utils import tobytes
|
||||
|
||||
import http.client
|
||||
|
||||
from .transport import *
|
||||
|
||||
class Transport_Softap(Transport):
|
||||
def __init__(self, url):
|
||||
self.conn = http.client.HTTPConnection(url, timeout=30)
|
||||
self.headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
|
||||
|
||||
def _send_post_request(self, path, data):
|
||||
try:
|
||||
self.conn.request("POST", path, tobytes(data), self.headers)
|
||||
response = self.conn.getresponse()
|
||||
if response.status == 200:
|
||||
return response.read().decode('latin-1')
|
||||
except Exception as err:
|
||||
raise RuntimeError("Connection Failure : " + str(err))
|
||||
raise RuntimeError("Server responded with error code " + str(response.status))
|
||||
|
||||
def send_data(self, ep_name, data):
|
||||
return self._send_post_request('/'+ ep_name, data)
|
16
tools/esp_prov/utils/__init__.py
Normal file
16
tools/esp_prov/utils/__init__.py
Normal file
@ -0,0 +1,16 @@
|
||||
# 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 .convenience import *
|
29
tools/esp_prov/utils/convenience.py
Normal file
29
tools/esp_prov/utils/convenience.py
Normal file
@ -0,0 +1,29 @@
|
||||
# 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.
|
||||
#
|
||||
|
||||
# Convenience functions for commonly used data type conversions
|
||||
|
||||
def str_to_hexstr(string):
|
||||
# Form hexstr by appending ASCII codes (in hex) corresponding to
|
||||
# each character in the input string
|
||||
return ''.join('{:02x}'.format(ord(c)) for c in string)
|
||||
|
||||
def hexstr_to_str(hexstr):
|
||||
# Prepend 0 (if needed) to make the hexstr length an even number
|
||||
if len(hexstr)%2 == 1:
|
||||
hexstr = '0' + hexstr
|
||||
# Interpret consecutive pairs of hex characters as 8 bit ASCII codes
|
||||
# and append characters corresponding to each code to form the string
|
||||
return ''.join(chr(int(hexstr[2*i:2*i+2], 16)) for i in range(len(hexstr)//2))
|
Reference in New Issue
Block a user