Examples : Added provisioning examples, tests and client tool.

* BLE and softAP provisioning examples added along with tests.
* An application specific example added for demonstrating custom provisioning scheme.
* tools/esp_prov/esp_prov.py can be used as a provisioning client for SoftAP, BLE and CLI based transports.

Co-Authored-By: Amey Inamdar <amey@espressif.com>
Co-Authored-By: Anurag Kar <anurag.kar@espressif.com>
This commit is contained in:
Anurag Kar 2018-07-30 21:40:10 +05:30
parent 2b52465416
commit 84f094453b
80 changed files with 6016 additions and 4 deletions

View File

@ -42,10 +42,10 @@ variables:
# are removed.
.git_clean_stale_submodules: &git_clean_stale_submodules >
find . -name '.git' -not -path './.git' -printf '%P\n'
| sed 's|/.git||'
| sed 's|/.git||'
| xargs -I {} sh -c '
grep -q {} .gitmodules
|| (echo "Removing {}, has .git directory but not in .gitmodules file"
grep -q {} .gitmodules
|| (echo "Removing {}, has .git directory but not in .gitmodules file"
&& rm -rf {});'
# before each job, we need to check if this job is filtered by bot stage/job filter
@ -326,7 +326,7 @@ verify_cmake_style:
tags:
- host_test
dependencies: []
test_nvs_on_host:
<<: *host_test_template
script:
@ -822,6 +822,12 @@ example_test_004_01:
- ESP32
- Example_CAN
example_test_005_01:
<<: *example_test_template
tags:
- ESP32
- Example_WIFI_BT
UT_001_01:
<<: *unit_test_template
tags:

View File

@ -0,0 +1,13 @@
# Protobuf files for implementing protocol communication packets
Protocomm uses Google Protobuf for language, transport and architecture agnostic protocol communication. These proto files define the protocomm packet structure, separated across multiple files:
* contants.proto - Defines the "Status" structure for conveying the success or failure of a single protocomm transaction
* sec0.proto - Defines the Security0 Command and Response packet structures
* sec1.proto - Defines the Security1 Command and Response packet structures
* session.proto - Defines the protocomm transacion packets for session establishment which internally has a Security packet as payload
Note : These proto files are not automatically compiled during the build process.
Run "make" (Optional) to generate the respective C and Python files. The generated C files are used by protocomm itself to create, delete and manipulate transaction packets. The generated Python files can be used by python based applications for implementing client side interface to protocomm layer.
Compilation requires protoc (Protobuf Compiler) and protoc-c (Protobuf C Compiler) installed. Since the generated files are to remain the same, as long as the proto files are not modified, therefore the generated files are already available under "protocomm/proto-c" and "protocomm/python" directories, and thus running make (and installing the Protobuf compilers) is optional.

View File

@ -134,6 +134,10 @@ static void protocomm_console_task(void *arg)
static int common_cmd_handler(int argc, char** argv)
{
if (argc < 3) {
return ESP_ERR_INVALID_ARG;
}
int i, ret;
uint32_t cur_session_id = atoi(argv[1]);

View File

@ -0,0 +1,7 @@
# Protobuf files for defining Wi-Fi config-data packet structures
Note : These proto files are not automatically compiled during the build process.
Run "make" (Optional) to generate the respective C and Python files. The generated C files are used by protocomm itself to create, delete and manipulate transaction packets. The generated Python files can be used by python based applications for implementing client side interface to protocomm layer.
Compilation requires protoc (Protobuf Compiler) and protoc-c (Protobuf C Compiler) installed. Since the generated files are to remain the same, as long as the proto files are not modified, therefore the generated files are already available under "protocomm/proto-c" and "protocomm/python" directories, and thus running make (and installing the Protobuf compilers) is optional.

View File

@ -0,0 +1,19 @@
# Provisioning Application Examples
These consist of the following examples :
* softap_prov
Provisioning involves Wi-Fi station configuration via an HTTP server running on the device, which is initially configured to be in SoftAP mode. After provisioning, device runs in Wi-Fi station mode only and connects to the AP whose credentials were provided during provisioning.
* ble_prov
Provisioning involves Wi-Fi station configuration via BLE service endpoints running on the device initially. After provisioning, BLE is turned off and device runs in Wi-Fi station mode, connecting to the AP whose credentials were provided during provisioning.
* console_prov
Provisioning involves Wi-Fi station configuration via UART console. This is intended for debugging protocomm and provisioning related features.
* custom_config
Similar to softap_prov examples, but allows for configuration of custom (device-local) information during provisioning. This is intended as an example for implementing custom provisioning schemes.
A python based utility is provided under "$IDF_PATH/tools/esp_prov" for testing the examples over a host.
Refer to the README.md files in each example directory for more information.

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ble_prov)

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := ble_prov
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,196 @@
# BLE based Provisioning Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
`ble_prov` example demonstrates the implementation and integration of various IDF components for building a provisioning application.
For this example BLE is chosen as the mode of transport, over which the provisioning related communication is to take place, between the device (to be provisioned) and the client (owner of the device).
In the provisioning process the device is configured as a Wi-Fi station with specified credentials. Once configured, the device will retain the Wi-Fi configuration, until a flash erase is performed.
Right after provisioning is complete, BLE is turned off and disabled to free the memory used by the BLE stack. Though, that is specific to this example, and the user can choose to keep BLE on in their own application.
`ble_prov` uses the following components :
* `wifi_provisioning` : provides data structures and protocomm endpoint handlers for Wi-Fi configuration
* `protocomm` : for protocol based communication and secure session establishment
* `protobuf` : Google's protocol buffer library for serialization of protocomm data structures
* `bt` : ESP32 BLE stack for transport of protobuf packets
This example can be used, as it is, for adding a provisioning service to any application intended for IoT.
## How to use example
### Hardware Required
Example should be able to run on any commonly available ESP32 development board.
### Application Required
To provision the device running this example, the `esp_prov.py` script needs to be run (found under `$IDF_PATH/tools/esp_prov`). Make sure to satisfy all the dependencies prior to running the script.
Presently, `esp_prov` supports BLE transport only for Linux platform. For Windows/macOS it falls back to console mode and requires another application (for BLE) through which the communication can take place.
There are various applications, specific to Windows and macOS platform which can be used. The `esp_prov` console will guide you through the provisioning process of locating the correct BLE GATT services and characteristics, the values to write, and input read values.
For android, a provisioning tool along with source code is available [here](https://github.com/espressif/esp-idf-provisioning-android). This offers a simpler and more close to actual user experience.
### Configure the project
```
make menuconfig
```
* Set serial port under Serial Flasher Options.
* Under Example Configuration set the following :
* Security Version (default 1)
* Proof of Possession (default "abcd1234")
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
make -j4 flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
I (550) app: Starting BLE provisioning
I (1130) app_prov: Provisioning started with BLE devname : 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 :
```
python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 1 --pop abcd1234 --transport ble --ble_devname PROV_261FCC
```
Above command will perform the provisioning steps, and the monitor log should display something like this :
```
I (682950) app_prov_handler: WiFi Credentials Received :
ssid : myssid
password : mypassword
.
.
.
I (683130) app_prov: STA Start
I (683130) app_prov_handler: WiFi Credentials Applied
.
.
.
I (688270) app_prov_handler: Connecting state
.
.
.
I (688390) app_prov: STA Got IP
I (688390) app: got ip:192.168.43.220
I (693410) app_prov_handler: Connected state
```
After sometime the provisioning app will exit and BLE will be turned off
```
I (718390) app_prov: Stopping provisioning
I (718670) app_prov: Provisioning stopped
```
## Troubleshooting
### Provisioning failed
It is possible that the Wi-Fi credentials provided were incorrect, or the device was not able to establish connection to the network, in which the the `esp_prov` script will notify failure (with reason) and the provisioning app will continue running, allowing the user to retry the process. Serial monitor log will display the failure along with disconnect reason :
```
E (39291) app_prov: STA Disconnected
E (39291) app_prov: Disconnect reason : 201
I (39291) app_prov: STA AP Not found
I (42021) app_prov_handler: Disconnected state
```
### Provisioning does not start
If the serial monitor log is different, as shown below :
```
I (539) app_prov: Found ssid myssid
I (539) app_prov: Found password mypassword
I (549) app: Starting WiFi station
```
It means the Wi-Fi credentials were already set by some other application flashed previously to your device. To erase these credentials either do full erase and then flash the example
```
make erase_flash
make -j4 flash monitor
```
Or, enable `Reset Provisioning` option under `Example Configuration` under menuconfig. But this will erase the saved Wi-Fi credentials every time the device boots, so this is not the preferred solution.
### Unsupported platform
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
BLECLI >> Please connect to BLE device `PROV_261FCC` manually using your tool of choice
BLECLI >> Was the device connected successfully? [y/n] y
BLECLI >> List available attributes of the connected device
BLECLI >> Is the service UUID '0000ffff-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y
BLECLI >> Is the characteristic UUID '0000ff53-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y
BLECLI >> Is the characteristic UUID '0000ff51-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y
BLECLI >> Is the characteristic UUID '0000ff52-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y
==== Verifying protocol version ====
BLECLI >> Write following data to characteristic with UUID '0000ff53-0000-1000-8000-00805f9b34fb' :
>> 56302e31
BLECLI >> Enter data read from characteristic (in hex) :
<< 53554343455353
==== Verified protocol version successfully ====
==== Starting Session ====
BLECLI >> Write following data to characteristic with UUID '0000ff51-0000-1000-8000-00805f9b34fb' :
>> 10015a25a201220a20ae6d9d5d1029f8c366892252d2d5a0ffa7ce1ee5829312545dd5f2aba057294d
BLECLI >> Enter data read from characteristic (in hex) :
<< 10015a390801aa0134122048008bfc365fad4753dc75912e0c764d60749cb26dd609595b6fbc72e12614031a1089733af233c7448e7d7fb7963682c6d8
BLECLI >> Write following data to characteristic with UUID '0000ff51-0000-1000-8000-00805f9b34fb' :
>> 10015a270802b2012212204051088dc294fe4621fac934a8ea22e948fcc3e8ac458aac088ce705c65dbfb9
BLECLI >> Enter data read from characteristic (in hex) :
<< 10015a270803ba01221a20c8d38059d5206a3d92642973ac6ba8ac2f6ecf2b7a3632964eb35a0f20133adb
==== Session Established ====
==== Sending Wifi credential to esp32 ====
BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' :
>> 98471ac4019a46765c28d87df8c8ae71c1ae6cfe0bc9c615bc6d2c
BLECLI >> Enter data read from characteristic (in hex) :
<< 3271f39a
==== Wifi Credentials sent successfully ====
==== Applying config to esp32 ====
BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' :
>> 5355
BLECLI >> Enter data read from characteristic (in hex) :
<< 1664db24
==== Apply config sent successfully ====
==== Wifi connection state ====
BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' :
>> 290d
BLECLI >> Enter data read from characteristic (in hex) :
<< 505f72a9f8521025c1964d7789c4d7edc56aedebd144e1b667bc7c0975757b80cc091aa9f3e95b06eaefbc30290fa1
++++ WiFi state: connected ++++
==== Provisioning was successful ====
```
The write data is to be copied from the console output ```>>``` to the platform specific application and the data read from the application is to be pasted at the user input prompt ```<<``` of the console, in the format (hex) indicated in above sample log.

View File

@ -0,0 +1,113 @@
#!/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 imp
import re
import os
import sys
import time
# This environment variable is expected on the host machine
test_fw_path = os.getenv("TEST_FW_PATH")
if test_fw_path and test_fw_path not in sys.path:
sys.path.insert(0, test_fw_path)
# When running on local machine execute the following before running this script
# > export TEST_FW_PATH='~/esp/esp-idf/tools/tiny-test-fw'
# > make print_flash_cmd | tail -n 1 > build/download.config
# > make app bootloader
import TinyFW
import IDF
# Import esp_prov tool
idf_path = os.environ['IDF_PATH']
esp_prov = imp.load_source("esp_prov", idf_path + "/tools/esp_prov/esp_prov.py")
@IDF.idf_example_test(env_tag="Example_WIFI_BT")
def test_examples_provisioning_ble(env, extra_data):
# Acquire DUT
dut1 = env.get_dut("ble_prov", "examples/provisioning/ble_prov")
# Get binary file
binary_file = os.path.join(dut1.app.binary_path, "ble_prov.bin")
bin_size = os.path.getsize(binary_file)
IDF.log_performance("ble_prov_bin_size", "{}KB".format(bin_size//1024))
IDF.check_performance("ble_prov_bin_size", bin_size//1024)
# Upload binary and start testing
dut1.start_app()
# Parse BLE devname
devname = dut1.expect(re.compile(r"(?:[\s\S]*) Provisioning started with BLE devname : (PROV_\S\S\S\S\S\S)"))[0]
print("BLE Device Alias for DUT :", devname)
# Match additional headers sent in the request
dut1.expect("BLE Provisioning started")
print("Starting Provisioning")
verbose = False
protover = "V0.1"
secver = 1
pop = "abcd1234"
provmode = "ble"
ap_ssid = "myssid"
ap_password = "mypassword"
print("Getting security")
security = esp_prov.get_security(secver, pop, verbose)
if security == None:
raise RuntimeError("Failed to get security")
print("Getting transport")
transport = esp_prov.get_transport(provmode, None, devname)
if transport == None:
raise RuntimeError("Failed to get transport")
print("Verifying protocol version")
if not esp_prov.version_match(transport, protover):
raise RuntimeError("Mismatch in protocol version")
print("Starting Session")
if not esp_prov.establish_session(transport, security):
raise RuntimeError("Failed to start session")
print("Sending Wifi credential to DUT")
if not esp_prov.send_wifi_config(transport, security, ap_ssid, ap_password):
raise RuntimeError("Failed to send Wi-Fi config")
print("Applying config")
if not esp_prov.apply_wifi_config(transport, security):
raise RuntimeError("Failed to send apply config")
success = False
while True:
time.sleep(5)
print("Wi-Fi connection state")
ret = esp_prov.get_wifi_config(transport, security)
if (ret == 1):
continue
elif (ret == 0):
print("Provisioning was successful")
success = True
break
if not success:
raise RuntimeError("Provisioning failed")
if __name__ == '__main__':
test_examples_provisioning_ble()

View File

@ -0,0 +1,6 @@
set(COMPONENT_SRCS "app_main.c"
"app_prov.c"
"app_prov_handlers.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View File

@ -0,0 +1,34 @@
menu "Example Configuration"
config USE_SEC_1
bool
default y
prompt "Use Security Version 1"
help
Security version 1 used Curve25519 key exchange for establishing
secure session between device and client during provisioning
config USE_POP
bool
depends on USE_SEC_1
default y
prompt "Use proof-of-possession"
help
Proof-of-possession can be optionally used to prove that the device is indeed
in possession of the user who is provisioning the device. This proof-of-possession
is internally used to generate the shared secret through key exchange.
config POP
string "Proof-of-possession"
default "abcd1234"
depends on USE_POP
config RESET_PROVISIONED
bool
default n
prompt "Reset provisioned status of the device"
help
This erases the NVS to reset provisioned status of the device on every reboot.
Provisioned status is determined by the WiFi STA configuration, saved on the NVS.
endmenu

View File

@ -0,0 +1,114 @@
/* BLE based Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_system.h>
#include <esp_wifi.h>
#include <esp_event_loop.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <lwip/err.h>
#include <lwip/sys.h>
#include "app_prov.h"
static const char *TAG = "app";
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
/* Invoke Provisioning event handler first */
app_prov_event_handler(ctx, event);
switch(event->event_id) {
case SYSTEM_EVENT_AP_START:
ESP_LOGI(TAG, "SoftAP started");
break;
case SYSTEM_EVENT_AP_STOP:
ESP_LOGI(TAG, "SoftAP stopped");
break;
case SYSTEM_EVENT_STA_START:
esp_wifi_connect();
break;
case SYSTEM_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, "got ip:%s",
ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
break;
case SYSTEM_EVENT_AP_STACONNECTED:
ESP_LOGI(TAG, "station:"MACSTR" join, AID=%d",
MAC2STR(event->event_info.sta_connected.mac),
event->event_info.sta_connected.aid);
break;
case SYSTEM_EVENT_AP_STADISCONNECTED:
ESP_LOGI(TAG, "station:"MACSTR"leave, AID=%d",
MAC2STR(event->event_info.sta_disconnected.mac),
event->event_info.sta_disconnected.aid);
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
break;
default:
break;
}
return ESP_OK;
}
static void wifi_init_sta()
{
/* Start wifi in station mode with credentials set during provisioning */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_start() );
}
void app_main()
{
/* Security version */
int security = 0;
/* Proof of possession */
const protocomm_security_pop_t *pop = NULL;
#ifdef CONFIG_USE_SEC_1
security = 1;
#endif
/* Having proof of possession is optional */
#ifdef CONFIG_USE_POP
const static protocomm_security_pop_t app_pop = {
.data = (uint8_t *) CONFIG_POP,
.len = (sizeof(CONFIG_POP)-1)
};
pop = &app_pop;
#endif
/* Initialize networking stack */
tcpip_adapter_init();
/* Set our event handling */
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
/* Check if device is provisioned */
bool provisioned;
if (app_prov_is_provisioned(&provisioned) != ESP_OK) {
ESP_LOGE(TAG, "Error getting device provisioning state");
return;
}
if (provisioned == false) {
/* If not provisioned, start provisioning via BLE */
ESP_LOGI(TAG, "Starting BLE provisioning");
app_prov_start_ble_provisioning(security, pop);
} else {
/* Else start as station with credentials set during provisioning */
ESP_LOGI(TAG, "Starting WiFi station");
wifi_init_sta(NULL);
}
}

View File

@ -0,0 +1,370 @@
/* BLE based Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_wifi.h>
#include <nvs_flash.h>
#include <nvs.h>
#include <esp_bt.h>
#include <protocomm.h>
#include <protocomm_ble.h>
#include <protocomm_security0.h>
#include <protocomm_security1.h>
#include <wifi_provisioning/wifi_config.h>
#include "app_prov.h"
static const char *TAG = "app_prov";
static const char *ssid_prefix = "PROV_";
/* Handlers for wifi_config provisioning endpoint */
extern wifi_prov_config_handlers_t wifi_prov_handlers;
/**
* @brief Data relevant to provisioning application
*/
struct app_prov_data {
protocomm_t *pc; /*!< Protocomm handler */
int security; /*!< Type of security to use with protocomm */
const protocomm_security_pop_t *pop; /*!< Pointer to proof of possession */
esp_timer_handle_t timer; /*!< Handle to timer */
/* State of WiFi Station */
wifi_prov_sta_state_t wifi_state;
/* Code for WiFi station disconnection (if disconnected) */
wifi_prov_sta_fail_reason_t wifi_disconnect_reason;
};
/* Pointer to provisioning application data */
static struct app_prov_data *g_prov;
static esp_err_t app_prov_start_service(void)
{
/* Create new protocomm instance */
g_prov->pc = protocomm_new();
if (g_prov->pc == NULL) {
ESP_LOGE(TAG, "Failed to create new protocomm instance");
return ESP_FAIL;
}
/* Endpoint UUIDs */
protocomm_ble_name_uuid_t nu_lookup_table[] = {
{"prov-session", 0xFF51},
{"prov-config", 0xFF52},
{"proto-ver", 0xFF53},
};
/* Config for protocomm_ble_start() */
protocomm_ble_config_t config = {
.service_uuid = {
/* LSB <---------------------------------------
* ---------------------------------------> MSB */
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80,
0x00, 0x10, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
},
.nu_lookup_count = sizeof(nu_lookup_table)/sizeof(nu_lookup_table[0]),
.nu_lookup = nu_lookup_table
};
uint8_t eth_mac[6];
esp_wifi_get_mac(WIFI_IF_STA, eth_mac);
snprintf(config.device_name, sizeof(config.device_name), "%s%02X%02X%02X",
ssid_prefix, eth_mac[3], eth_mac[4], eth_mac[5]);
/* Release BT memory, as we need only BLE */
esp_err_t err = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
if (err) {
ESP_LOGE(TAG, "bt_controller_mem_release failed %d", err);
if (err != ESP_ERR_INVALID_STATE) {
return err;
}
}
/* Start protocomm layer on top of BLE */
if (protocomm_ble_start(g_prov->pc, &config) != ESP_OK) {
ESP_LOGE(TAG, "Failed to start BLE provisioning");
return ESP_FAIL;
}
/* Set protocomm version verification endpoint for protocol */
protocomm_set_version(g_prov->pc, "proto-ver", "V0.1");
/* Set protocomm security type for endpoint */
if (g_prov->security == 0) {
protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security0, NULL);
} else if (g_prov->security == 1) {
protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security1, g_prov->pop);
}
/* Add endpoint for provisioning to set wifi station config */
if (protocomm_add_endpoint(g_prov->pc, "prov-config",
wifi_prov_config_data_handler,
(void *) &wifi_prov_handlers) != ESP_OK) {
ESP_LOGE(TAG, "Failed to set provisioning endpoint");
protocomm_ble_stop(g_prov->pc);
return ESP_FAIL;
}
ESP_LOGI(TAG, "Provisioning started with BLE devname : %s", config.device_name);
return ESP_OK;
}
static void app_prov_stop_service(void)
{
/* Remove provisioning endpoint */
protocomm_remove_endpoint(g_prov->pc, "prov-config");
/* Unset provisioning security */
protocomm_unset_security(g_prov->pc, "prov-session");
/* Unset provisioning version endpoint */
protocomm_unset_version(g_prov->pc, "proto-ver");
/* Stop protocomm ble service */
protocomm_ble_stop(g_prov->pc);
/* Delete protocomm instance */
protocomm_delete(g_prov->pc);
/* Release memory used by BT stack */
esp_bt_mem_release(ESP_BT_MODE_BTDM);
}
/* Task spawned by timer callback */
static void stop_prov_task(void * arg)
{
ESP_LOGI(TAG, "Stopping provisioning");
app_prov_stop_service();
/* Timer not needed anymore */
esp_timer_handle_t timer = g_prov->timer;
esp_timer_delete(timer);
g_prov->timer = NULL;
/* Free provisioning process data */
free(g_prov);
g_prov = NULL;
ESP_LOGI(TAG, "Provisioning stopped");
vTaskDelete(NULL);
}
/* Callback to be invoked by timer */
static void _stop_prov_cb(void * arg)
{
xTaskCreate(&stop_prov_task, "stop_prov", 2048, NULL, tskIDLE_PRIORITY, NULL);
}
/* Event handler for starting/stopping provisioning.
* To be called from within the context of the main
* event handler.
*/
esp_err_t app_prov_event_handler(void *ctx, system_event_t *event)
{
/* For accessing reason codes in case of disconnection */
system_event_info_t *info = &event->event_info;
/* If pointer to provisioning application data is NULL
* then provisioning is not running, therefore return without
* error */
if (!g_prov) {
return ESP_OK;
}
switch(event->event_id) {
case SYSTEM_EVENT_STA_START:
ESP_LOGI(TAG, "STA Start");
/* Once configuration is received through protocomm,
* device is started as station. Once station starts,
* wait for connection to establish with configured
* host SSID and password */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
break;
case SYSTEM_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, "STA Got IP");
/* Station got IP. That means configuraion is successful.
* Schedule timer to stop provisioning app after 30 seconds. */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTED;
if (g_prov && g_prov->timer) {
esp_timer_start_once(g_prov->timer, 30000*1000U);
}
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
ESP_LOGE(TAG, "STA Disconnected");
/* Station couldn't connect to configured host SSID */
g_prov->wifi_state = WIFI_PROV_STA_DISCONNECTED;
ESP_LOGE(TAG, "Disconnect reason : %d", info->disconnected.reason);
/* Set code corresponding to the reason for disconnection */
switch (info->disconnected.reason) {
case WIFI_REASON_AUTH_EXPIRE:
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT:
case WIFI_REASON_BEACON_TIMEOUT:
case WIFI_REASON_AUTH_FAIL:
case WIFI_REASON_ASSOC_FAIL:
case WIFI_REASON_HANDSHAKE_TIMEOUT:
ESP_LOGI(TAG, "STA Auth Error");
g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AUTH_ERROR;
break;
case WIFI_REASON_NO_AP_FOUND:
ESP_LOGI(TAG, "STA AP Not found");
g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AP_NOT_FOUND;
break;
default:
/* If none of the expected reasons,
* retry connecting to host SSID */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
esp_wifi_connect();
}
break;
default:
break;
}
return ESP_OK;
}
esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state)
{
if (g_prov == NULL || state == NULL) {
return ESP_FAIL;
}
*state = g_prov->wifi_state;
return ESP_OK;
}
esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason)
{
if (g_prov == NULL || reason == NULL) {
return ESP_FAIL;
}
if (g_prov->wifi_state != WIFI_PROV_STA_DISCONNECTED) {
return ESP_FAIL;
}
*reason = g_prov->wifi_disconnect_reason;
return ESP_OK;
}
esp_err_t app_prov_is_provisioned(bool *provisioned)
{
*provisioned = false;
#ifdef CONFIG_RESET_PROVISIONED
nvs_flash_erase();
#endif
if (nvs_flash_init() != ESP_OK) {
ESP_LOGE(TAG, "Failed to init NVS");
return ESP_FAIL;
}
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
if (esp_wifi_init(&cfg) != ESP_OK) {
ESP_LOGE(TAG, "Failed to init wifi");
return ESP_FAIL;
}
/* Get WiFi Station configuration */
wifi_config_t wifi_cfg;
if (esp_wifi_get_config(ESP_IF_WIFI_STA, &wifi_cfg) != ESP_OK) {
return ESP_FAIL;
}
if (strlen((const char*) wifi_cfg.sta.ssid)) {
*provisioned = true;
ESP_LOGI(TAG, "Found ssid %s", (const char*) wifi_cfg.sta.ssid);
ESP_LOGI(TAG, "Found password %s", (const char*) wifi_cfg.sta.password);
}
return ESP_OK;
}
esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg)
{
/* Initialize WiFi with default config */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
if (esp_wifi_init(&cfg) != ESP_OK) {
ESP_LOGE(TAG, "Failed to init WiFi");
return ESP_FAIL;
}
/* Configure WiFi as station */
if (esp_wifi_set_mode(WIFI_MODE_STA) != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi mode");
return ESP_FAIL;
}
/* Configure WiFi station with host credentials
* provided during provisioning */
if (esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_cfg) != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi configuration");
return ESP_FAIL;
}
/* Start WiFi */
if (esp_wifi_start() != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi configuration");
return ESP_FAIL;
}
/* Connect to AP */
if (esp_wifi_connect() != ESP_OK) {
ESP_LOGE(TAG, "Failed to connect WiFi");
return ESP_FAIL;
}
if (g_prov) {
/* Reset wifi station state for provisioning app */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
}
return ESP_OK;
}
esp_err_t app_prov_start_ble_provisioning(int security, const protocomm_security_pop_t *pop)
{
/* If provisioning app data present,
* means provisioning app is already running */
if (g_prov) {
ESP_LOGI(TAG, "Invalid provisioning state");
return ESP_FAIL;
}
/* Allocate memory for provisioning app data */
g_prov = (struct app_prov_data *) calloc(1, sizeof(struct app_prov_data));
if (!g_prov) {
ESP_LOGI(TAG, "Unable to allocate prov data");
return ESP_ERR_NO_MEM;
}
/* Initialise app data */
g_prov->pop = pop;
g_prov->security = security;
/* Create timer object as a member of app data */
esp_timer_create_args_t timer_conf = {
.callback = _stop_prov_cb,
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "stop_ble_tm"
};
esp_err_t err = esp_timer_create(&timer_conf, &g_prov->timer);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to create timer");
return err;
}
/* Start provisioning service through BLE */
err = app_prov_start_service();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Provisioning failed to start");
return err;
}
ESP_LOGI(TAG, "BLE Provisioning started");
return ESP_OK;
}

View File

@ -0,0 +1,96 @@
/* BLE based Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#include <esp_event_loop.h>
#include <protocomm_security.h>
#include <wifi_provisioning/wifi_config.h>
/**
* @brief Get state of WiFi Station during provisioning
*
* @note WiFi is initially configured as AP, when
* provisioning starts. After provisioning data
* is provided by user, the WiFi is reconfigured
* to run as both AP and Station.
*
* @param[out] state Pointer to wifi_prov_sta_state_t variable to be filled
*
* @return
* - ESP_OK : Successfully retrieved wifi state
* - ESP_FAIL : Provisioning app not running
*/
esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state);
/**
* @brief Get reason code in case of WiFi station
* disconnection during provisioning
*
* @param[out] reason Pointer to wifi_prov_sta_fail_reason_t variable to be filled
*
* @return
* - ESP_OK : Successfully retrieved wifi disconnect reason
* - ESP_FAIL : Provisioning app not running
*/
esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason);
/**
* @brief Event handler for provisioning app
*
* This is called from the main event handler and controls the
* provisioning application, depeding on WiFi events
*
* @param[in] ctx Event context data
* @param[in] event Event info
*
* @return
* - ESP_OK : Event handled successfully
* - ESP_FAIL : Failed to start server on event AP start
*/
esp_err_t app_prov_event_handler(void *ctx, system_event_t *event);
/**
* @brief Checks if device is provisioned
* *
* @param[out] provisioned True if provisioned, else false
*
* @return
* - ESP_OK : Retrieved provision state successfully
* - ESP_FAIL : Failed to retrieve provision state
*/
esp_err_t app_prov_is_provisioned(bool *provisioned);
/**
* @brief Runs WiFi as Station
*
* Configures the WiFi station mode to connect to the
* SSID and password specified in config structure,
* and starts WiFi to run as station
*
* @param[in] wifi_cfg Pointer to WiFi cofiguration structure
*
* @return
* - ESP_OK : WiFi configured and started successfully
* - ESP_FAIL : Failed to set configuration
*/
esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg);
/**
* @brief Start provisioning via Bluetooth
*
* @param[in] security Security mode
* @param[in] pop Pointer to proof of possession (NULL if not present)
*
* @return
* - ESP_OK : Provisioning started successfully
* - ESP_FAIL : Failed to start
*/
esp_err_t app_prov_start_ble_provisioning(int security, const protocomm_security_pop_t *pop);

View File

@ -0,0 +1,106 @@
/* BLE based Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/* This file is mostly a boiler-plate code that applications can use without much change */
#include <stdio.h>
#include <string.h>
#include <esp_err.h>
#include <esp_log.h>
#include <esp_wifi.h>
#include <tcpip_adapter.h>
#include <wifi_provisioning/wifi_config.h>
#include "app_prov.h"
static const char* TAG = "app_prov_handler";
static esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data)
{
/* Initialise to zero */
memset(resp_data, 0, sizeof(wifi_prov_config_get_data_t));
if (app_prov_get_wifi_state(&resp_data->wifi_state) != ESP_OK) {
ESP_LOGW(TAG, "Prov app not running");
return ESP_FAIL;
}
if (resp_data->wifi_state == WIFI_PROV_STA_CONNECTED) {
ESP_LOGI(TAG, "Connected state");
/* IP Addr assigned to STA */
tcpip_adapter_ip_info_t ip_info;
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info);
char *ip_addr = ip4addr_ntoa(&ip_info.ip);
strcpy(resp_data->conn_info.ip_addr, ip_addr);
/* AP information to which STA is connected */
wifi_ap_record_t ap_info;
esp_wifi_sta_get_ap_info(&ap_info);
memcpy(resp_data->conn_info.bssid, (char *)ap_info.bssid, sizeof(ap_info.bssid));
memcpy(resp_data->conn_info.ssid, (char *)ap_info.ssid, sizeof(ap_info.ssid));
resp_data->conn_info.channel = ap_info.primary;
resp_data->conn_info.auth_mode = ap_info.authmode;
} else if (resp_data->wifi_state == WIFI_PROV_STA_DISCONNECTED) {
ESP_LOGI(TAG, "Disconnected state");
/* If disconnected, convey reason */
app_prov_get_wifi_disconnect_reason(&resp_data->fail_reason);
} else {
ESP_LOGI(TAG, "Connecting state");
}
return ESP_OK;
}
static wifi_config_t *wifi_cfg;
static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data)
{
if (wifi_cfg) {
free(wifi_cfg);
wifi_cfg = NULL;
}
wifi_cfg = (wifi_config_t *) calloc(1, sizeof(wifi_config_t));
if (!wifi_cfg) {
ESP_LOGE(TAG, "Unable to alloc wifi config");
return ESP_FAIL;
}
ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s",
req_data->ssid, req_data->password);
memcpy((char *) wifi_cfg->sta.ssid, req_data->ssid,
strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid)));
memcpy((char *) wifi_cfg->sta.password, req_data->password,
strnlen(req_data->password, sizeof(wifi_cfg->sta.password)));
return ESP_OK;
}
static esp_err_t apply_config_handler(void)
{
if (!wifi_cfg) {
ESP_LOGE(TAG, "WiFi config not set");
return ESP_FAIL;
}
app_prov_configure_sta(wifi_cfg);
ESP_LOGI(TAG, "WiFi Credentials Applied");
free(wifi_cfg);
wifi_cfg = NULL;
return ESP_OK;
}
wifi_prov_config_handlers_t wifi_prov_handlers = {
.get_status_handler = get_status_handler,
.set_config_handler = set_config_handler,
.apply_config_handler = apply_config_handler,
};

View File

@ -0,0 +1,8 @@
#
# Main component makefile.
#
# This Makefile can be left empty. By default, it will take the sources in the
# src/ directory, compile them and link them into lib(subdirectory_name).a
# in the build directory. This behaviour is entirely configurable,
# please read the ESP-IDF documents if you need to do this.
#

View File

@ -0,0 +1,5 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1200000,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1200000,

View File

@ -0,0 +1,10 @@
# Override some defaults so BT stack is enabled and
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY=y
CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY=
CONFIG_BTDM_CONTROLLER_MODE_BTDM=
# Binary is larger than default size
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(console_prov)

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := console_prov
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,212 @@
# Console based Provisioning Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
`console_prov` example demonstrates the implementation and integration of various IDF components for building a console based provisioning application.
For this example UART console is chosen as the mode of transport, over which the provisioning related communication is to take place, between the device (to be provisioned) and the client (owner of the device).
In the provisioning process the device is configured as a Wi-Fi station with specified credentials. Once configured, the device will retain the Wi-Fi configuration, until a flash erase is performed.
Right after provisioning is complete, the UART console is deactivated.
`console_prov` uses the following components :
* `wifi_provisioning` : provides data structures and protocomm endpoint handlers for Wi-Fi configuration
* `protocomm` : for protocol based communication and secure session establishment
* `protobuf` : Google's protocol buffer library for serialization of protocomm data structures
This example can be used, as it is, for adding a provisioning service to any application intended for IoT. But it is more suitable for debugging protocomm and provisioning related components and feature additions.
## How to use example
### Hardware Required
Example should be able to run on any commonly available ESP32 development board.
### Application Required
To provision the device running this example, the `esp_prov.py` script needs to be run (found under `$IDF_PATH/tools/esp_prov`). This feature of `esp_prov` should work on all platforms as long long as all Python dependencies are satisfied.
### Configure the project
```
make menuconfig
```
* Set serial port under Serial Flasher Options.
* Under Example Configuration set the following :
* Security Version (default 1)
* Proof of Possession (default "abcd1234")
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
make -j4 flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
I (388) app: Starting console provisioning
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
```
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 :
```
On pasting the command on the serial monitor console, a `Device->Client` message will appear for each command :
```
>> proto-ver 0 56302e31
53554343455353
```
Copy this message back to the `esp_prov` console for proceeding to the next command :
```
==== Verifying protocol version ====
Client->Device msg : proto-ver 0 56302e31
Enter device->client msg : 53554343455353
==== Verified protocol version successfully ====
==== Starting Session ====
Client->Device msg : prov-session 0 10015a25a201220a20677106cc2f5b2acb5d8da26f0ad443df006daa1cd5bb3d75a8324d81ec5ef970
Enter device->client msg :
```
This process keeps on till the device gets provisioned.
Note that the commands are in the following format :
```
<endpoint name> <session id> <hex message>
```
This is helpful in understanding the provisioning process and the order in which the endpoints are communicated with.
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
==== Verified protocol version successfully ====
==== Starting Session ====
Client->Device msg : prov-session 0 10015a25a201220a20677106cc2f5b2acb5d8da26f0ad443df006daa1cd5bb3d75a8324d81ec5ef970
Enter device->client msg : 10015a390801aa013412207566f4de191f600ea42de5c2b1df73f1f16685c2edb43d7c3ffc83d6b81ff61b1a103db6476536a88db10b7e0a172d4adef8
Client->Device msg : prov-session 0 10015a270802b20122122084ca311e51c904a94f8a249c049f7aed33b39671cc11f0b92b15b299ef5653b7
Enter device->client msg : 10015a270803ba01221a203246230190d5c1f5d94c01b56ac8cace1086cfb2d937a4a46cb6c79db7a35a8b
==== Session Established ====
==== Sending Wifi credential to esp32 ====
Client->Device msg : prov-config 0 8f0c8cb6f2d53c4cc53b29be8ba1aac3edbb1dead39117c34687d6
Enter device->client msg : 2e1f0eb0
==== Wifi Credentials sent successfully ====
==== Applying config to esp32 ====
Client->Device msg : prov-config 0 e8df
Enter device->client msg : 245c83f0
==== Apply config sent successfully ====
==== Wifi connection state ====
Client->Device msg : prov-config 0 2d36
Enter device->client msg : 1b38a7411b6e2608aae50a6571807e04a6e90520b3b1e3c1e5b38cea4b9022e56485b92ff84289df218311972a42eb
++++ WiFi state: connected ++++
==== Provisioning was successful ====
```
The serial monitor console, for above sequence of commands, would look like :
```
>> proto-ver 0 56302e31
53554343455353
>> prov-session 0 10015a25a201220a20677106cc2f5b2acb5d8da26f0ad443df006daa1cd5bb3d75a8324d81ec5ef970
10015a390801aa013412207566f4de191f600ea42de5c2b1df73f1f16685c2edb43d7c3ffc83d6b81ff61b1a103db6476536a88db10b7e0a172d4adef8
>> prov-session 0 10015a270802b20122122084ca311e51c904a94f8a249c049f7aed33b39671cc11f0b92b15b299ef5653b7
10015a270803ba01221a203246230190d5c1f5d94c01b56ac8cace1086cfb2d937a4a46cb6c79db7a35a8b
>> prov-config 0 8f0c8cb6f2d53c4cc53b29be8ba1aac3edbb1dead39117c34687d6
I (1073738) app_prov_handler: WiFi Credentials Received :
ssid : myssid
password : mypassword
2e1f0eb0
>> prov-config 0 e8df
I (1084218) app_prov_handler: WiFi Credentials Applied
245c83f0
>> prov-config 0 2d36
I (1089728) app_prov: STA Got IP
I (1089728) app: got ip:192.168.43.220
I (1099698) app_prov_handler: Connected state
1b38a7411b6e2608aae50a6571807e04a6e90520b3b1e3c1e5b38cea4b9022e56485b92ff84289df218311972a42eb
>>
```
After sometime the provisioning app will exit and UART console will be stopped
```
I (1119728) app_prov: Stopping provisioning
I (1119728) protocomm_console: Stopping console...
I (1119728) app_prov: Provisioning stopped
I (1119748) protocomm_console: Console stopped
```
## Troubleshooting
### Provisioning failed
It is possible that the Wi-Fi credentials provided were incorrect, or the device was not able to establish connection to the network, in which the the `esp_prov` script will notify failure (with reason) and the provisioning app will continue running, allowing the user to retry the process. Serial monitor log will display the failure along with disconnect reason :
```
E (39291) app_prov: STA Disconnected
E (39291) app_prov: Disconnect reason : 201
I (39291) app_prov: STA AP Not found
I (42021) app_prov_handler: Disconnected state
```
### Provisioning does not start
If the serial monitor log is different, as shown below :
```
I (539) app_prov: Found ssid myssid
I (539) app_prov: Found password mypassword
I (549) app: Starting WiFi station
```
It means the Wi-Fi credentials were already set by some other application flashed previously to your device. To erase these credentials either do full erase and then flash the example
```
make erase_flash
make -j4 flash monitor
```
Or, enable `Reset Provisioning` option under `Example Configuration` under menuconfig. But this will erase the saved Wi-Fi credentials every time the device boots, so this is not the preferred solution.

View File

@ -0,0 +1,6 @@
set(COMPONENT_SRCS "app_main.c"
"app_prov.c"
"app_prov_handlers.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View File

@ -0,0 +1,34 @@
menu "Example Configuration"
config USE_SEC_1
bool
default y
prompt "Use Security Version 1"
help
Security version 1 used Curve25519 key exchange for establishing
secure session between device and client during provisioning
config USE_POP
bool
depends on USE_SEC_1
default y
prompt "Use proof-of-possession"
help
Proof-of-possession can be optionally used to prove that the device is indeed
in possession of the user who is provisioning the device. This proof-of-possession
is internally used to generate the shared secret through key exchange.
config POP
string "Proof-of-possession"
default "abcd1234"
depends on USE_POP
config RESET_PROVISIONED
bool
default n
prompt "Reset provisioned status of the device"
help
This erases the NVS to reset provisioned status of the device on every reboot.
Provisioned status is determined by the WiFi STA configuration, saved on the NVS.
endmenu

View File

@ -0,0 +1,114 @@
/* Console based Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_system.h>
#include <esp_wifi.h>
#include <esp_event_loop.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <lwip/err.h>
#include <lwip/sys.h>
#include "app_prov.h"
static const char *TAG = "app";
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
/* Invoke Provisioning event handler first */
app_prov_event_handler(ctx, event);
switch(event->event_id) {
case SYSTEM_EVENT_AP_START:
ESP_LOGI(TAG, "SoftAP started");
break;
case SYSTEM_EVENT_AP_STOP:
ESP_LOGI(TAG, "SoftAP stopped");
break;
case SYSTEM_EVENT_STA_START:
esp_wifi_connect();
break;
case SYSTEM_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, "got ip:%s",
ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
break;
case SYSTEM_EVENT_AP_STACONNECTED:
ESP_LOGI(TAG, "station:"MACSTR" join, AID=%d",
MAC2STR(event->event_info.sta_connected.mac),
event->event_info.sta_connected.aid);
break;
case SYSTEM_EVENT_AP_STADISCONNECTED:
ESP_LOGI(TAG, "station:"MACSTR"leave, AID=%d",
MAC2STR(event->event_info.sta_disconnected.mac),
event->event_info.sta_disconnected.aid);
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
break;
default:
break;
}
return ESP_OK;
}
static void wifi_init_sta()
{
/* Start wifi in station mode with credentials set during provisioning */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_start() );
}
void app_main()
{
/* Security version */
int security = 0;
/* Proof of possession */
const protocomm_security_pop_t *pop = NULL;
#ifdef CONFIG_USE_SEC_1
security = 1;
#endif
/* Having proof of possession is optional */
#ifdef CONFIG_USE_POP
const static protocomm_security_pop_t app_pop = {
.data = (uint8_t *) CONFIG_POP,
.len = (sizeof(CONFIG_POP)-1)
};
pop = &app_pop;
#endif
/* Initialize networking stack */
tcpip_adapter_init();
/* Set our event handling */
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
/* Check if device is provisioned */
bool provisioned;
if (app_prov_is_provisioned(&provisioned) != ESP_OK) {
ESP_LOGE(TAG, "Error getting device provisioning state");
return;
}
if (provisioned == false) {
/* If not provisioned, start provisioning via console */
ESP_LOGI(TAG, "Starting console provisioning");
app_prov_start_console_provisioning(security, pop);
} else {
/* Else start as station with credentials set during provisioning */
ESP_LOGI(TAG, "Starting WiFi station");
wifi_init_sta(NULL);
}
}

View File

@ -0,0 +1,328 @@
/* Console based Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_wifi.h>
#include <nvs_flash.h>
#include <nvs.h>
#include <protocomm.h>
#include <protocomm_console.h>
#include <protocomm_security0.h>
#include <protocomm_security1.h>
#include <wifi_provisioning/wifi_config.h>
#include "app_prov.h"
static const char *TAG = "app_prov";
/* Handlers for wifi_config provisioning endpoint */
extern wifi_prov_config_handlers_t wifi_prov_handlers;
/**
* @brief Data relevant to provisioning application
*/
struct app_prov_data {
protocomm_t *pc; /*!< Protocomm handler */
int security; /*!< Type of security to use with protocomm */
const protocomm_security_pop_t *pop; /*!< Pointer to proof of possession */
esp_timer_handle_t timer; /*!< Handle to timer */
/* State of WiFi Station */
wifi_prov_sta_state_t wifi_state;
/* Code for WiFi station disconnection (if disconnected) */
wifi_prov_sta_fail_reason_t wifi_disconnect_reason;
};
/* Pointer to provisioning application data */
static struct app_prov_data *g_prov;
static esp_err_t app_prov_start_service(void)
{
/* Create new protocomm instance */
g_prov->pc = protocomm_new();
if (g_prov->pc == NULL) {
ESP_LOGE(TAG, "Failed to create new protocomm instance");
return ESP_FAIL;
}
/* Config for protocomm_console_start() */
protocomm_console_config_t config = PROTOCOMM_CONSOLE_DEFAULT_CONFIG();
/* Start protocomm using console */
if (protocomm_console_start(g_prov->pc, &config) != ESP_OK) {
ESP_LOGE(TAG, "Failed to start console provisioning");
return ESP_FAIL;
}
/* Set protocomm version verification endpoint for protocol */
protocomm_set_version(g_prov->pc, "proto-ver", "V0.1");
/* Set protocomm security type for endpoint */
if (g_prov->security == 0) {
protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security0, NULL);
} else if (g_prov->security == 1) {
protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security1, g_prov->pop);
}
/* Add endpoint for provisioning to set wifi station config */
if (protocomm_add_endpoint(g_prov->pc, "prov-config",
wifi_prov_config_data_handler,
(void *) &wifi_prov_handlers) != ESP_OK) {
ESP_LOGE(TAG, "Failed to set provisioning endpoint");
protocomm_console_stop(g_prov->pc);
return ESP_FAIL;
}
ESP_LOGI(TAG, "Provisioning started");
return ESP_OK;
}
static void app_prov_stop_service(void)
{
/* Remove provisioning endpoint */
protocomm_remove_endpoint(g_prov->pc, "prov-config");
/* Unset provisioning security */
protocomm_unset_security(g_prov->pc, "prov-session");
/* Unset provisioning version endpoint */
protocomm_unset_version(g_prov->pc, "proto-ver");
/* Stop protocomm console service */
protocomm_console_stop(g_prov->pc);
/* Delete protocomm instance */
protocomm_delete(g_prov->pc);
}
/* Callback to be invoked by timer */
static void _stop_prov_cb(void * arg)
{
ESP_LOGI(TAG, "Stopping provisioning");
app_prov_stop_service();
/* Timer not needed anymore */
esp_timer_handle_t timer = g_prov->timer;
esp_timer_delete(timer);
g_prov->timer = NULL;
/* Free provisioning process data */
free(g_prov);
g_prov = NULL;
ESP_LOGI(TAG, "Provisioning stopped");
}
/* Event handler for starting/stopping provisioning.
* To be called from within the context of the main
* event handler.
*/
esp_err_t app_prov_event_handler(void *ctx, system_event_t *event)
{
/* For accessing reason codes in case of disconnection */
system_event_info_t *info = &event->event_info;
/* If pointer to provisioning application data is NULL
* then provisioning is not running, therefore return without
* error */
if (!g_prov) {
return ESP_OK;
}
switch(event->event_id) {
case SYSTEM_EVENT_STA_START:
ESP_LOGI(TAG, "STA Start");
/* Once configuration is received through protocomm,
* device is started as station. Once station starts,
* wait for connection to establish with configured
* host SSID and password */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
break;
case SYSTEM_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, "STA Got IP");
/* Station got IP. That means configuraion is successful.
* Schedule timer to stop provisioning app after 30 seconds. */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTED;
if (g_prov && g_prov->timer) {
esp_timer_start_once(g_prov->timer, 30000*1000U);
}
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
ESP_LOGE(TAG, "STA Disconnected");
/* Station couldn't connect to configured host SSID */
g_prov->wifi_state = WIFI_PROV_STA_DISCONNECTED;
ESP_LOGE(TAG, "Disconnect reason : %d", info->disconnected.reason);
/* Set code corresponding to the reason for disconnection */
switch (info->disconnected.reason) {
case WIFI_REASON_AUTH_EXPIRE:
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT:
case WIFI_REASON_BEACON_TIMEOUT:
case WIFI_REASON_AUTH_FAIL:
case WIFI_REASON_ASSOC_FAIL:
case WIFI_REASON_HANDSHAKE_TIMEOUT:
ESP_LOGI(TAG, "STA Auth Error");
g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AUTH_ERROR;
break;
case WIFI_REASON_NO_AP_FOUND:
ESP_LOGI(TAG, "STA AP Not found");
g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AP_NOT_FOUND;
break;
default:
/* If none of the expected reasons,
* retry connecting to host SSID */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
esp_wifi_connect();
}
break;
default:
break;
}
return ESP_OK;
}
esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state)
{
if (g_prov == NULL || state == NULL) {
return ESP_FAIL;
}
*state = g_prov->wifi_state;
return ESP_OK;
}
esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason)
{
if (g_prov == NULL || reason == NULL) {
return ESP_FAIL;
}
if (g_prov->wifi_state != WIFI_PROV_STA_DISCONNECTED) {
return ESP_FAIL;
}
*reason = g_prov->wifi_disconnect_reason;
return ESP_OK;
}
esp_err_t app_prov_is_provisioned(bool *provisioned)
{
*provisioned = false;
#ifdef CONFIG_RESET_PROVISIONED
nvs_flash_erase();
#endif
if (nvs_flash_init() != ESP_OK) {
ESP_LOGE(TAG, "Failed to init NVS");
return ESP_FAIL;
}
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
if (esp_wifi_init(&cfg) != ESP_OK) {
ESP_LOGE(TAG, "Failed to init wifi");
return ESP_FAIL;
}
/* Get WiFi Station configuration */
wifi_config_t wifi_cfg;
if (esp_wifi_get_config(ESP_IF_WIFI_STA, &wifi_cfg) != ESP_OK) {
return ESP_FAIL;
}
if (strlen((const char*) wifi_cfg.sta.ssid)) {
*provisioned = true;
ESP_LOGI(TAG, "Found ssid %s", (const char*) wifi_cfg.sta.ssid);
ESP_LOGI(TAG, "Found password %s", (const char*) wifi_cfg.sta.password);
}
return ESP_OK;
}
esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg)
{
/* Initialize WiFi with default config */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
if (esp_wifi_init(&cfg) != ESP_OK) {
ESP_LOGE(TAG, "Failed to init WiFi");
return ESP_FAIL;
}
/* Configure WiFi as station */
if (esp_wifi_set_mode(WIFI_MODE_STA) != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi mode");
return ESP_FAIL;
}
/* Configure WiFi station with host credentials
* provided during provisioning */
if (esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_cfg) != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi configuration");
return ESP_FAIL;
}
/* Start WiFi */
if (esp_wifi_start() != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi configuration");
return ESP_FAIL;
}
/* Connect to AP */
if (esp_wifi_connect() != ESP_OK) {
ESP_LOGE(TAG, "Failed to connect WiFi");
return ESP_FAIL;
}
if (g_prov) {
/* Reset wifi station state for provisioning app */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
}
return ESP_OK;
}
esp_err_t app_prov_start_console_provisioning(int security, const protocomm_security_pop_t *pop)
{
/* If provisioning app data present,
* means provisioning app is already running */
if (g_prov) {
ESP_LOGI(TAG, "Invalid provisioning state");
return ESP_FAIL;
}
/* Allocate memory for provisioning app data */
g_prov = (struct app_prov_data *) calloc(1, sizeof(struct app_prov_data));
if (!g_prov) {
ESP_LOGI(TAG, "Unable to allocate prov data");
return ESP_ERR_NO_MEM;
}
/* Initialise app data */
g_prov->pop = pop;
g_prov->security = security;
/* Create timer object as a member of app data */
esp_timer_create_args_t timer_conf = {
.callback = _stop_prov_cb,
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "stop_softap_tm"
};
esp_err_t err = esp_timer_create(&timer_conf, &g_prov->timer);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to create timer");
return err;
}
/* Start provisioning service through console */
err = app_prov_start_service();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Provisioning failed to start");
return err;
}
ESP_LOGI(TAG, "Console provisioning started");
return ESP_OK;
}

View File

@ -0,0 +1,96 @@
/* Console based Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#include <esp_event_loop.h>
#include <protocomm_security.h>
#include <wifi_provisioning/wifi_config.h>
/**
* @brief Get state of WiFi Station during provisioning
*
* @note WiFi is initially configured as AP, when
* provisioning starts. After provisioning data
* is provided by user, the WiFi is reconfigured
* to run as both AP and Station.
*
* @param[out] state Pointer to wifi_prov_sta_state_t variable to be filled
*
* @return
* - ESP_OK : Successfully retrieved wifi state
* - ESP_FAIL : Provisioning app not running
*/
esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state);
/**
* @brief Get reason code in case of WiFi station
* disconnection during provisioning
*
* @param[out] reason Pointer to wifi_prov_sta_fail_reason_t variable to be filled
*
* @return
* - ESP_OK : Successfully retrieved wifi disconnect reason
* - ESP_FAIL : Provisioning app not running
*/
esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason);
/**
* @brief Event handler for provisioning app
*
* This is called from the main event handler and controls the
* provisioning application, depeding on WiFi events
*
* @param[in] ctx Event context data
* @param[in] event Event info
*
* @return
* - ESP_OK : Event handled successfully
* - ESP_FAIL : Failed to start server on event AP start
*/
esp_err_t app_prov_event_handler(void *ctx, system_event_t *event);
/**
* @brief Checks if device is provisioned
* *
* @param[out] provisioned True if provisioned, else false
*
* @return
* - ESP_OK : Retrieved provision state successfully
* - ESP_FAIL : Failed to retrieve provision state
*/
esp_err_t app_prov_is_provisioned(bool *provisioned);
/**
* @brief Runs WiFi as Station
*
* Configures the WiFi station mode to connect to the
* SSID and password specified in config structure,
* and starts WiFi to run as station
*
* @param[in] wifi_cfg Pointer to WiFi cofiguration structure
*
* @return
* - ESP_OK : WiFi configured and started successfully
* - ESP_FAIL : Failed to set configuration
*/
esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg);
/**
* @brief Start provisioning via Console
*
* @param[in] security Security mode
* @param[in] pop Pointer to proof of possession (NULL if not present)
*
* @return
* - ESP_OK : Provisioning started successfully
* - ESP_FAIL : Failed to start
*/
esp_err_t app_prov_start_console_provisioning(int security, const protocomm_security_pop_t *pop);

View File

@ -0,0 +1,106 @@
/* Console based Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/* This file is mostly a boiler-plate code that applications can use without much change */
#include <stdio.h>
#include <string.h>
#include <esp_err.h>
#include <esp_log.h>
#include <esp_wifi.h>
#include <tcpip_adapter.h>
#include <wifi_provisioning/wifi_config.h>
#include "app_prov.h"
static const char* TAG = "app_prov_handler";
static esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data)
{
/* Initialise to zero */
memset(resp_data, 0, sizeof(wifi_prov_config_get_data_t));
if (app_prov_get_wifi_state(&resp_data->wifi_state) != ESP_OK) {
ESP_LOGW(TAG, "Prov app not running");
return ESP_FAIL;
}
if (resp_data->wifi_state == WIFI_PROV_STA_CONNECTED) {
ESP_LOGI(TAG, "Connected state");
/* IP Addr assigned to STA */
tcpip_adapter_ip_info_t ip_info;
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info);
char *ip_addr = ip4addr_ntoa(&ip_info.ip);
strcpy(resp_data->conn_info.ip_addr, ip_addr);
/* AP information to which STA is connected */
wifi_ap_record_t ap_info;
esp_wifi_sta_get_ap_info(&ap_info);
memcpy(resp_data->conn_info.bssid, (char *)ap_info.bssid, sizeof(ap_info.bssid));
memcpy(resp_data->conn_info.ssid, (char *)ap_info.ssid, sizeof(ap_info.ssid));
resp_data->conn_info.channel = ap_info.primary;
resp_data->conn_info.auth_mode = ap_info.authmode;
} else if (resp_data->wifi_state == WIFI_PROV_STA_DISCONNECTED) {
ESP_LOGI(TAG, "Disconnected state");
/* If disconnected, convey reason */
app_prov_get_wifi_disconnect_reason(&resp_data->fail_reason);
} else {
ESP_LOGI(TAG, "Connecting state");
}
return ESP_OK;
}
static wifi_config_t *wifi_cfg;
static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data)
{
if (wifi_cfg) {
free(wifi_cfg);
wifi_cfg = NULL;
}
wifi_cfg = (wifi_config_t *) calloc(1, sizeof(wifi_config_t));
if (!wifi_cfg) {
ESP_LOGE(TAG, "Unable to alloc wifi config");
return ESP_FAIL;
}
ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s",
req_data->ssid, req_data->password);
memcpy((char *) wifi_cfg->sta.ssid, req_data->ssid,
strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid)));
memcpy((char *) wifi_cfg->sta.password, req_data->password,
strnlen(req_data->password, sizeof(wifi_cfg->sta.password)));
return ESP_OK;
}
static esp_err_t apply_config_handler(void)
{
if (!wifi_cfg) {
ESP_LOGE(TAG, "WiFi config not set");
return ESP_FAIL;
}
app_prov_configure_sta(wifi_cfg);
ESP_LOGI(TAG, "WiFi Credentials Applied");
free(wifi_cfg);
wifi_cfg = NULL;
return ESP_OK;
}
wifi_prov_config_handlers_t wifi_prov_handlers = {
.get_status_handler = get_status_handler,
.set_config_handler = set_config_handler,
.apply_config_handler = apply_config_handler,
};

View File

@ -0,0 +1,8 @@
#
# Main component makefile.
#
# This Makefile can be left empty. By default, it will take the sources in the
# src/ directory, compile them and link them into lib(subdirectory_name).a
# in the build directory. This behaviour is entirely configurable,
# please read the ESP-IDF documents if you need to do this.
#

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(custom_config)

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := custom_config
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,144 @@
# SoftAP + HTTPD based Provisioning Example featuring Custom configuration
(See the README.md file in the upper level 'examples' directory for more information about examples.)
(Please see the README.md under `softap_prov` example before this.)
`custom_config` example demonstrates the implementation and integration of various IDF components for building a provisioning application.
This is same as `softap_prov` example, with added feature for configuration of some custom data (just like Wi-Fi configuration) during provisioning. The custom data provided during provisioning is simply printed on the serial monitor. The rest of the program functions just like `softap_prov`, ie. the device is configured as Wi-Fi station with supplied AP credentials.
`custom_config` uses the following components :
* `wifi_provisioning` : provides data structures and protocomm endpoint handlers for Wi-Fi configuration
* `protocomm` : for protocol based communication and secure session establishment
* `protobuf` : Google's protocol buffer library for serialization of protocomm data structures
Also, it uses a component provided with this example `custom_provisioning` which provides data structures and protocomm endpoint handlers for custom data configuration
## How to use example
### Hardware Required
Example should be able to run on any commonly available ESP32 development board.
### Application Required
To provision the device running this example, the `esp_prov.py` script needs to be run (found under `$IDF_PATH/tools/esp_prov`). This feature of `esp_prov` should work on all platforms as long long as all Python dependencies are satisfied.
### Configure the project
```
make menuconfig
```
* Set serial port under Serial Flasher Options.
* Under Example Configuration set the following :
* SoftAP SSID (Defaults to PROV_<MACID>)
* SoftAP Password (Defaults to PROV_PASS)
* Security Version (default 0)
* Proof of Possession (by default not needed for security version 0)
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
make -j4 flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
I (1562) app: SoftAP started
I (1572) app_prov: SoftAP Provisioning started with SSID 'PROV_261FCC', Password 'PROV_PASS'
```
Make sure to connect the client computer to the SoftAP network, whose SSID and Password are displayed in the serial monitor log. On successful connection the monitor log will show :
```
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
```
Above command will perform the provisioning steps, and the monitor log should display something like this :
```
I (92734) app_prov_handler: Custom config received :
Info : some string
Version : 4321
.
.
.
I (634572) app_prov_handler: WiFi Credentials Received :
ssid : myssid
password : mypassword
.
.
.
I (634652) app_prov_handler: WiFi Credentials Applied
I (634652) app_prov: STA Start
.
.
.
I (688270) app_prov_handler: Connecting state
.
.
.
I (637732) app_prov: STA Got IP
I (637732) app: got ip:192.168.43.220
.
.
.
I (654562) app_prov_handler: Connected state
```
After sometime the provisioning app will exit, SoftAP will be turned off and HTTP server will be stopped
```
I (667732) app_prov: Stopping provisioning
I (668732) app_prov: Provisioning stopped
I (668742) app: SoftAP stopped
```
## Troubleshooting
### Provisioning failed
It is possible that the Wi-Fi credentials provided were incorrect, or the device was not able to establish connection to the network, in which the the `esp_prov` script will notify failure (with reason) and the provisioning app will continue running, allowing the user to retry the process. Serial monitor log will display the failure along with disconnect reason :
```
E (39291) app_prov: STA Disconnected
E (39291) app_prov: Disconnect reason : 201
I (39291) app_prov: STA AP Not found
I (42021) app_prov_handler: Disconnected state
```
### Provisioning does not start
If the serial monitor log is different, as shown below :
```
I (539) app_prov: Found ssid myssid
I (539) app_prov: Found password mypassword
I (549) app: Starting WiFi station
```
It means the Wi-Fi credentials were already set by some other application flashed previously to your device. To erase these credentials either do full erase and then flash the example
```
make erase_flash
make -j4 flash monitor
```
Or, enable `Reset Provisioning` option under `Example Configuration` under menuconfig. But this will erase the saved Wi-Fi credentials every time the device boots, so this is not the preferred solution.

View File

@ -0,0 +1,8 @@
set(COMPONENT_ADD_INCLUDEDIRS include)
set(COMPONENT_PRIV_INCLUDEDIRS proto-c)
set(COMPONENT_SRCS "src/custom_config.c"
"proto-c/custom_config.pb-c.c")
set(COMPONENT_PRIV_REQUIRES protobuf-c)
register_component()

View File

@ -0,0 +1,3 @@
COMPONENT_SRCDIRS := src proto-c
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_PRIV_INCLUDEDIRS := proto-c

View File

@ -0,0 +1,44 @@
// 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.
#ifndef _CUSTOM_PROV_CONFIG_H_
#define _CUSTOM_PROV_CONFIG_H_
/**
* @brief Custom config data received by device
*/
typedef struct {
char info[128];
int version;
} custom_config_t;
/**
* @brief Internal handler for receiving and responding to protocomm
* requests from master
*
* This is to be passed as priv_data for protocomm request handler
* (refer to `custom_prov_config_data_handler()`) when calling `protocomm_add_endpoint()`.
*/
typedef esp_err_t (*custom_prov_config_handler_t) (const custom_config_t *config);
/**
* @brief Handler for receiving and responding to requests from master
*
* This is to be registered as the `wifi_config` endpoint handler
* (protocomm `protocomm_req_handler_t`) using `protocomm_add_endpoint()`
*/
esp_err_t custom_prov_config_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen,
uint8_t **outbuf, ssize_t *outlen, void *priv_data);
#endif

View File

@ -0,0 +1,229 @@
/* Generated by the protocol buffer compiler. DO NOT EDIT! */
/* Generated from: custom_config.proto */
/* Do not generate deprecated warnings for self */
#ifndef PROTOBUF_C__NO_DEPRECATED
#define PROTOBUF_C__NO_DEPRECATED
#endif
#include "custom_config.pb-c.h"
void custom_config_request__init
(CustomConfigRequest *message)
{
static const CustomConfigRequest init_value = CUSTOM_CONFIG_REQUEST__INIT;
*message = init_value;
}
size_t custom_config_request__get_packed_size
(const CustomConfigRequest *message)
{
assert(message->base.descriptor == &custom_config_request__descriptor);
return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
}
size_t custom_config_request__pack
(const CustomConfigRequest *message,
uint8_t *out)
{
assert(message->base.descriptor == &custom_config_request__descriptor);
return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
}
size_t custom_config_request__pack_to_buffer
(const CustomConfigRequest *message,
ProtobufCBuffer *buffer)
{
assert(message->base.descriptor == &custom_config_request__descriptor);
return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
}
CustomConfigRequest *
custom_config_request__unpack
(ProtobufCAllocator *allocator,
size_t len,
const uint8_t *data)
{
return (CustomConfigRequest *)
protobuf_c_message_unpack (&custom_config_request__descriptor,
allocator, len, data);
}
void custom_config_request__free_unpacked
(CustomConfigRequest *message,
ProtobufCAllocator *allocator)
{
if(!message)
return;
assert(message->base.descriptor == &custom_config_request__descriptor);
protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
}
void custom_config_response__init
(CustomConfigResponse *message)
{
static const CustomConfigResponse init_value = CUSTOM_CONFIG_RESPONSE__INIT;
*message = init_value;
}
size_t custom_config_response__get_packed_size
(const CustomConfigResponse *message)
{
assert(message->base.descriptor == &custom_config_response__descriptor);
return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
}
size_t custom_config_response__pack
(const CustomConfigResponse *message,
uint8_t *out)
{
assert(message->base.descriptor == &custom_config_response__descriptor);
return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
}
size_t custom_config_response__pack_to_buffer
(const CustomConfigResponse *message,
ProtobufCBuffer *buffer)
{
assert(message->base.descriptor == &custom_config_response__descriptor);
return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
}
CustomConfigResponse *
custom_config_response__unpack
(ProtobufCAllocator *allocator,
size_t len,
const uint8_t *data)
{
return (CustomConfigResponse *)
protobuf_c_message_unpack (&custom_config_response__descriptor,
allocator, len, data);
}
void custom_config_response__free_unpacked
(CustomConfigResponse *message,
ProtobufCAllocator *allocator)
{
if(!message)
return;
assert(message->base.descriptor == &custom_config_response__descriptor);
protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
}
static const ProtobufCFieldDescriptor custom_config_request__field_descriptors[2] =
{
{
"info",
1,
PROTOBUF_C_LABEL_NONE,
PROTOBUF_C_TYPE_STRING,
0, /* quantifier_offset */
offsetof(CustomConfigRequest, info),
NULL,
&protobuf_c_empty_string,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
{
"version",
2,
PROTOBUF_C_LABEL_NONE,
PROTOBUF_C_TYPE_INT32,
0, /* quantifier_offset */
offsetof(CustomConfigRequest, version),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
};
static const unsigned custom_config_request__field_indices_by_name[] = {
0, /* field[0] = info */
1, /* field[1] = version */
};
static const ProtobufCIntRange custom_config_request__number_ranges[1 + 1] =
{
{ 1, 0 },
{ 0, 2 }
};
const ProtobufCMessageDescriptor custom_config_request__descriptor =
{
PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
"CustomConfigRequest",
"CustomConfigRequest",
"CustomConfigRequest",
"",
sizeof(CustomConfigRequest),
2,
custom_config_request__field_descriptors,
custom_config_request__field_indices_by_name,
1, custom_config_request__number_ranges,
(ProtobufCMessageInit) custom_config_request__init,
NULL,NULL,NULL /* reserved[123] */
};
static const ProtobufCFieldDescriptor custom_config_response__field_descriptors[2] =
{
{
"status",
1,
PROTOBUF_C_LABEL_NONE,
PROTOBUF_C_TYPE_ENUM,
0, /* quantifier_offset */
offsetof(CustomConfigResponse, status),
&custom_config_status__descriptor,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
{
"dummy",
2,
PROTOBUF_C_LABEL_NONE,
PROTOBUF_C_TYPE_INT32,
0, /* quantifier_offset */
offsetof(CustomConfigResponse, dummy),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
};
static const unsigned custom_config_response__field_indices_by_name[] = {
1, /* field[1] = dummy */
0, /* field[0] = status */
};
static const ProtobufCIntRange custom_config_response__number_ranges[1 + 1] =
{
{ 1, 0 },
{ 0, 2 }
};
const ProtobufCMessageDescriptor custom_config_response__descriptor =
{
PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
"CustomConfigResponse",
"CustomConfigResponse",
"CustomConfigResponse",
"",
sizeof(CustomConfigResponse),
2,
custom_config_response__field_descriptors,
custom_config_response__field_indices_by_name,
1, custom_config_response__number_ranges,
(ProtobufCMessageInit) custom_config_response__init,
NULL,NULL,NULL /* reserved[123] */
};
static const ProtobufCEnumValue custom_config_status__enum_values_by_number[2] =
{
{ "ConfigSuccess", "CUSTOM_CONFIG_STATUS__ConfigSuccess", 0 },
{ "ConfigFail", "CUSTOM_CONFIG_STATUS__ConfigFail", 1 },
};
static const ProtobufCIntRange custom_config_status__value_ranges[] = {
{0, 0},{0, 2}
};
static const ProtobufCEnumValueIndex custom_config_status__enum_values_by_name[2] =
{
{ "ConfigFail", 1 },
{ "ConfigSuccess", 0 },
};
const ProtobufCEnumDescriptor custom_config_status__descriptor =
{
PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC,
"CustomConfigStatus",
"CustomConfigStatus",
"CustomConfigStatus",
"",
2,
custom_config_status__enum_values_by_number,
2,
custom_config_status__enum_values_by_name,
1,
custom_config_status__value_ranges,
NULL,NULL,NULL,NULL /* reserved[1234] */
};

View File

@ -0,0 +1,113 @@
/* Generated by the protocol buffer compiler. DO NOT EDIT! */
/* Generated from: custom_config.proto */
#ifndef PROTOBUF_C_custom_5fconfig_2eproto__INCLUDED
#define PROTOBUF_C_custom_5fconfig_2eproto__INCLUDED
#include <protobuf-c/protobuf-c.h>
PROTOBUF_C__BEGIN_DECLS
#if PROTOBUF_C_VERSION_NUMBER < 1003000
# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers.
#elif 1003000 < PROTOBUF_C_MIN_COMPILER_VERSION
# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c.
#endif
typedef struct _CustomConfigRequest CustomConfigRequest;
typedef struct _CustomConfigResponse CustomConfigResponse;
/* --- enums --- */
typedef enum _CustomConfigStatus {
CUSTOM_CONFIG_STATUS__ConfigSuccess = 0,
CUSTOM_CONFIG_STATUS__ConfigFail = 1
PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(CUSTOM_CONFIG_STATUS)
} CustomConfigStatus;
/* --- messages --- */
struct _CustomConfigRequest
{
ProtobufCMessage base;
char *info;
int32_t version;
};
#define CUSTOM_CONFIG_REQUEST__INIT \
{ PROTOBUF_C_MESSAGE_INIT (&custom_config_request__descriptor) \
, (char *)protobuf_c_empty_string, 0 }
struct _CustomConfigResponse
{
ProtobufCMessage base;
CustomConfigStatus status;
int32_t dummy;
};
#define CUSTOM_CONFIG_RESPONSE__INIT \
{ PROTOBUF_C_MESSAGE_INIT (&custom_config_response__descriptor) \
, CUSTOM_CONFIG_STATUS__ConfigSuccess, 0 }
/* CustomConfigRequest methods */
void custom_config_request__init
(CustomConfigRequest *message);
size_t custom_config_request__get_packed_size
(const CustomConfigRequest *message);
size_t custom_config_request__pack
(const CustomConfigRequest *message,
uint8_t *out);
size_t custom_config_request__pack_to_buffer
(const CustomConfigRequest *message,
ProtobufCBuffer *buffer);
CustomConfigRequest *
custom_config_request__unpack
(ProtobufCAllocator *allocator,
size_t len,
const uint8_t *data);
void custom_config_request__free_unpacked
(CustomConfigRequest *message,
ProtobufCAllocator *allocator);
/* CustomConfigResponse methods */
void custom_config_response__init
(CustomConfigResponse *message);
size_t custom_config_response__get_packed_size
(const CustomConfigResponse *message);
size_t custom_config_response__pack
(const CustomConfigResponse *message,
uint8_t *out);
size_t custom_config_response__pack_to_buffer
(const CustomConfigResponse *message,
ProtobufCBuffer *buffer);
CustomConfigResponse *
custom_config_response__unpack
(ProtobufCAllocator *allocator,
size_t len,
const uint8_t *data);
void custom_config_response__free_unpacked
(CustomConfigResponse *message,
ProtobufCAllocator *allocator);
/* --- per-message closures --- */
typedef void (*CustomConfigRequest_Closure)
(const CustomConfigRequest *message,
void *closure_data);
typedef void (*CustomConfigResponse_Closure)
(const CustomConfigResponse *message,
void *closure_data);
/* --- services --- */
/* --- descriptors --- */
extern const ProtobufCEnumDescriptor custom_config_status__descriptor;
extern const ProtobufCMessageDescriptor custom_config_request__descriptor;
extern const ProtobufCMessageDescriptor custom_config_response__descriptor;
PROTOBUF_C__END_DECLS
#endif /* PROTOBUF_C_custom_5fconfig_2eproto__INCLUDED */

View File

@ -0,0 +1,11 @@
# Protobuf files for defining custom config-data packet structures
This is an example proto file defining custom configuration related data packet structures, namely -
1. CustomConfigRequest - for sending configuration data consisting of various fields (Info and Version)
2. CustomConfigResponse - for receiving configuration status (fail/success)
Note : These proto files are not automatically compiled during the build process.
Run "make" (Optional) to generate the respective C and Python files. The generated C files are used by protocomm itself to create, delete and manipulate transaction packets. The generated Python files can be used by python based applications for implementing client side interface to protocomm layer.
Compilation requires protoc (Protobuf Compiler) and protoc-c (Protobuf C Compiler) installed. Since the generated files are to remain the same, as long as the proto files are not modified, therefore the generated files are already available under "protocomm/proto-c" and "protocomm/python" directories, and thus running make (and installing the Protobuf compilers) is optional.

View File

@ -0,0 +1,16 @@
syntax = "proto3";
enum CustomConfigStatus {
ConfigSuccess = 0;
ConfigFail = 1;
}
message CustomConfigRequest {
string info = 1;
int32 version = 2;
}
message CustomConfigResponse {
CustomConfigStatus status = 1;
int32 dummy = 2;
}

View File

@ -0,0 +1,7 @@
all: c_proto python_proto
c_proto: *.proto
@protoc-c --c_out=../proto-c/ *.proto
python_proto: *.proto
@protoc --python_out=../python/ *.proto

View File

@ -0,0 +1,150 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: custom_config.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='custom_config.proto',
package='',
syntax='proto3',
serialized_options=None,
serialized_pb=_b('\n\x13\x63ustom_config.proto\"4\n\x13\x43ustomConfigRequest\x12\x0c\n\x04info\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\x05\"J\n\x14\x43ustomConfigResponse\x12#\n\x06status\x18\x01 \x01(\x0e\x32\x13.CustomConfigStatus\x12\r\n\x05\x64ummy\x18\x02 \x01(\x05*7\n\x12\x43ustomConfigStatus\x12\x11\n\rConfigSuccess\x10\x00\x12\x0e\n\nConfigFail\x10\x01\x62\x06proto3')
)
_CUSTOMCONFIGSTATUS = _descriptor.EnumDescriptor(
name='CustomConfigStatus',
full_name='CustomConfigStatus',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='ConfigSuccess', index=0, number=0,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ConfigFail', index=1, number=1,
serialized_options=None,
type=None),
],
containing_type=None,
serialized_options=None,
serialized_start=153,
serialized_end=208,
)
_sym_db.RegisterEnumDescriptor(_CUSTOMCONFIGSTATUS)
CustomConfigStatus = enum_type_wrapper.EnumTypeWrapper(_CUSTOMCONFIGSTATUS)
ConfigSuccess = 0
ConfigFail = 1
_CUSTOMCONFIGREQUEST = _descriptor.Descriptor(
name='CustomConfigRequest',
full_name='CustomConfigRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='info', full_name='CustomConfigRequest.info', index=0,
number=1, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='version', full_name='CustomConfigRequest.version', index=1,
number=2, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=23,
serialized_end=75,
)
_CUSTOMCONFIGRESPONSE = _descriptor.Descriptor(
name='CustomConfigResponse',
full_name='CustomConfigResponse',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='status', full_name='CustomConfigResponse.status', index=0,
number=1, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='dummy', full_name='CustomConfigResponse.dummy', index=1,
number=2, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=77,
serialized_end=151,
)
_CUSTOMCONFIGRESPONSE.fields_by_name['status'].enum_type = _CUSTOMCONFIGSTATUS
DESCRIPTOR.message_types_by_name['CustomConfigRequest'] = _CUSTOMCONFIGREQUEST
DESCRIPTOR.message_types_by_name['CustomConfigResponse'] = _CUSTOMCONFIGRESPONSE
DESCRIPTOR.enum_types_by_name['CustomConfigStatus'] = _CUSTOMCONFIGSTATUS
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
CustomConfigRequest = _reflection.GeneratedProtocolMessageType('CustomConfigRequest', (_message.Message,), dict(
DESCRIPTOR = _CUSTOMCONFIGREQUEST,
__module__ = 'custom_config_pb2'
# @@protoc_insertion_point(class_scope:CustomConfigRequest)
))
_sym_db.RegisterMessage(CustomConfigRequest)
CustomConfigResponse = _reflection.GeneratedProtocolMessageType('CustomConfigResponse', (_message.Message,), dict(
DESCRIPTOR = _CUSTOMCONFIGRESPONSE,
__module__ = 'custom_config_pb2'
# @@protoc_insertion_point(class_scope:CustomConfigResponse)
))
_sym_db.RegisterMessage(CustomConfigResponse)
# @@protoc_insertion_point(module_scope)

View File

@ -0,0 +1,68 @@
// 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.
#include <stdio.h>
#include <esp_log.h>
#include <string.h>
#include <esp_err.h>
#include <custom_provisioning/custom_config.h>
#include "custom_config.pb-c.h"
static const char *TAG = "custom_config";
int custom_prov_config_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data)
{
CustomConfigRequest *req;
CustomConfigResponse resp;
custom_prov_config_handler_t app_handler_custom_config = (custom_prov_config_handler_t) priv_data;
req = custom_config_request__unpack(NULL, inlen, inbuf);
if (!req) {
ESP_LOGE(TAG, "Unable to unpack config data");
return ESP_ERR_INVALID_ARG;
}
custom_config_response__init(&resp);
resp.status = CUSTOM_CONFIG_STATUS__ConfigFail;
if (app_handler_custom_config) {
custom_config_t config;
strlcpy(config.info, req->info, sizeof(config.info));
config.version = req->version;
esp_err_t err = app_handler_custom_config(&config);
resp.status = (err == ESP_OK) ? CUSTOM_CONFIG_STATUS__ConfigSuccess :
CUSTOM_CONFIG_STATUS__ConfigFail;
}
custom_config_request__free_unpacked(req, NULL);
resp.dummy = 47; // Set a non zero value of dummy
*outlen = custom_config_response__get_packed_size(&resp);
if (*outlen <= 0) {
ESP_LOGE(TAG, "Invalid encoding for response");
return ESP_FAIL;
}
*outbuf = (uint8_t *) malloc(*outlen);
if (*outbuf == NULL) {
ESP_LOGE(TAG, "System out of memory");
return ESP_ERR_NO_MEM;
}
custom_config_response__pack(&resp, *outbuf);
return ESP_OK;
}

View File

@ -0,0 +1,6 @@
set(COMPONENT_SRCS "app_main.c"
"app_prov.c"
"app_prov_handlers.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View File

@ -0,0 +1,52 @@
menu "Example Configuration"
config SOFTAP_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config SOFTAP_PASS
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
config USE_SEC_1
bool
default n
prompt "Use Security Version 1"
help
Security version 1 used Curve25519 key exchange for establishing
secure session between device and client during provisioning
config USE_POP
bool
depends on USE_SEC_1
default n
prompt "Use proof-of-possession"
help
Proof-of-possession can be optionally used to prove that the device is indeed
in possession of the user who is provisioning the device. This proof-of-possession
is internally used to generate the shared secret through key exchange.
config POP
string "Proof-of-possession"
default "abcd1234"
depends on USE_POP
config PROTOCOMM_HTTPD_PORT
int "Protocomm HTTP Port"
default 80
help
Port on which to run Protocomm HTTP based provisioning service
config RESET_PROVISIONED
bool
default n
prompt "Reset provisioned status of the device"
help
This erases the NVS to reset provisioned status of the device on every reboot.
Provisioned status is determined by the WiFi STA configuration, saved on the NVS.
endmenu

View File

@ -0,0 +1,115 @@
/* SoftAP based Custom Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_system.h>
#include <esp_wifi.h>
#include <esp_event_loop.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <lwip/err.h>
#include <lwip/sys.h>
#include "app_prov.h"
static const char *TAG = "app";
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
/* Invoke Provisioning event handler first */
app_prov_event_handler(ctx, event);
switch(event->event_id) {
case SYSTEM_EVENT_AP_START:
ESP_LOGI(TAG, "SoftAP started");
break;
case SYSTEM_EVENT_AP_STOP:
ESP_LOGI(TAG, "SoftAP stopped");
break;
case SYSTEM_EVENT_STA_START:
esp_wifi_connect();
break;
case SYSTEM_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, "got ip:%s",
ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
break;
case SYSTEM_EVENT_AP_STACONNECTED:
ESP_LOGI(TAG, "station:"MACSTR" join, AID=%d",
MAC2STR(event->event_info.sta_connected.mac),
event->event_info.sta_connected.aid);
break;
case SYSTEM_EVENT_AP_STADISCONNECTED:
ESP_LOGI(TAG, "station:"MACSTR"leave, AID=%d",
MAC2STR(event->event_info.sta_disconnected.mac),
event->event_info.sta_disconnected.aid);
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
break;
default:
break;
}
return ESP_OK;
}
static void wifi_init_sta()
{
/* Start wifi in station mode with credentials set during provisioning */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_start() );
}
void app_main()
{
/* Security version */
int security = 0;
/* Proof of possession */
const protocomm_security_pop_t *pop = NULL;
#ifdef CONFIG_USE_SEC_1
security = 1;
#endif
/* Having proof of possession is optional */
#ifdef CONFIG_USE_POP
const static protocomm_security_pop_t app_pop = {
.data = (uint8_t *) CONFIG_POP,
.len = (sizeof(CONFIG_POP)-1)
};
pop = &app_pop;
#endif
/* Initialize networking stack */
tcpip_adapter_init();
/* Set our event handling */
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
/* Check if device is provisioned */
bool provisioned;
if (app_prov_is_provisioned(&provisioned) != ESP_OK) {
ESP_LOGE(TAG, "Error getting device provisioning state");
return;
}
if (provisioned == false) {
/* If not provisioned, start provisioning via soft AP */
ESP_LOGI(TAG, "Starting WiFi SoftAP provisioning");
app_prov_start_softap_provisioning(CONFIG_SOFTAP_SSID, CONFIG_SOFTAP_PASS,
security, pop);
} else {
/* Start WiFi station with credentials set during provisioning */
ESP_LOGI(TAG, "Starting WiFi station");
wifi_init_sta(NULL);
}
}

View File

@ -0,0 +1,398 @@
/* SoftAP based Custom Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_wifi.h>
#include <nvs_flash.h>
#include <nvs.h>
#include <protocomm.h>
#include <protocomm_httpd.h>
#include <protocomm_security0.h>
#include <protocomm_security1.h>
#include <wifi_provisioning/wifi_config.h>
#include <custom_provisioning/custom_config.h>
#include "app_prov.h"
static const char *TAG = "app_prov";
/* Handlers for provisioning endpoints */
extern wifi_prov_config_handlers_t wifi_prov_handlers;
extern custom_prov_config_handler_t custom_prov_handler;
/**
* @brief Data relevant to provisioning application
*/
struct app_prov_data {
protocomm_t *pc; /*!< Protocomm handler */
int security; /*!< Type of security to use with protocomm */
const protocomm_security_pop_t *pop; /*!< Pointer to proof of possession */
esp_timer_handle_t timer; /*!< Handle to timer */
/* State of WiFi Station */
wifi_prov_sta_state_t wifi_state;
/* Code for WiFi station disconnection (if disconnected) */
wifi_prov_sta_fail_reason_t wifi_disconnect_reason;
};
/* Pointer to provisioning application data */
static struct app_prov_data *g_prov;
static esp_err_t app_prov_start_service(void)
{
/* Create new protocomm instance */
g_prov->pc = protocomm_new();
if (g_prov->pc == NULL) {
ESP_LOGE(TAG, "Failed to create new protocomm instance");
return ESP_FAIL;
}
/* Config for protocomm_httpd_start() */
protocomm_httpd_config_t pc_config = PROTOCOMM_HTTPD_DEFAULT_CONFIG();
/* Start protocomm server on top of HTTP */
if (protocomm_httpd_start(g_prov->pc, &pc_config) != ESP_OK) {
ESP_LOGE(TAG, "Failed to start protocomm HTTP server");
return ESP_FAIL;
}
/* Set protocomm version verification endpoint for protocol */
protocomm_set_version(g_prov->pc, "proto-ver", "V0.1");
/* Set protocomm security type for endpoint */
if (g_prov->security == 0) {
protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security0, NULL);
} else if (g_prov->security == 1) {
protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security1, g_prov->pop);
}
/* Add endpoint for provisioning to set WiFi STA config */
if (protocomm_add_endpoint(g_prov->pc, "prov-config",
wifi_prov_config_data_handler,
(void *) &wifi_prov_handlers) != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi provisioning endpoint");
protocomm_httpd_stop(g_prov->pc);
return ESP_FAIL;
}
/* Add endpoint for provisioning to set custom config */
if (protocomm_add_endpoint(g_prov->pc, "custom-config",
custom_prov_config_data_handler,
(void *) custom_prov_handler) != ESP_OK) {
ESP_LOGE(TAG, "Failed to set custom provisioning endpoint");
protocomm_httpd_stop(g_prov->pc);
return ESP_FAIL;
}
return ESP_OK;
}
static void app_prov_stop_service(void)
{
/* Remove provisioning endpoint for custom config */
protocomm_remove_endpoint(g_prov->pc, "custom-config");
/* Remove provisioning endpoint for WiFi STA config */
protocomm_remove_endpoint(g_prov->pc, "prov-config");
/* Unset provisioning security */
protocomm_unset_security(g_prov->pc, "prov-session");
/* Unset provisioning version endpoint */
protocomm_unset_version(g_prov->pc, "proto-ver");
/* Stop protocomm server */
protocomm_httpd_stop(g_prov->pc);
/* Delete protocomm instance */
protocomm_delete(g_prov->pc);
}
/* Callback to be invoked by timer */
static void _stop_softap_cb(void * arg)
{
ESP_LOGI(TAG, "Stopping provisioning");
app_prov_stop_service();
esp_wifi_set_mode(WIFI_MODE_STA);
/* Timer not needed anymore */
esp_timer_handle_t timer = g_prov->timer;
esp_timer_delete(timer);
g_prov->timer = NULL;
/* Free provisioning process data */
free(g_prov);
g_prov = NULL;
ESP_LOGI(TAG, "Provisioning stopped");
}
/* Event handler for starting/stopping provisioning.
* To be called from within the context of the main
* event handler.
*/
esp_err_t app_prov_event_handler(void *ctx, system_event_t *event)
{
/* For accessing reason codes in case of disconnection */
system_event_info_t *info = &event->event_info;
/* If pointer to provisioning application data is NULL
* then provisioning is not running, therefore return without
* error */
if (!g_prov) {
return ESP_OK;
}
switch(event->event_id) {
case SYSTEM_EVENT_STA_START:
ESP_LOGI(TAG, "STA Start");
/* Once configuration is received by protocomm server,
* device is restarted as both AP and Station.
* Once station starts, wait for connection to
* establish with configured host SSID and password */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
break;
case SYSTEM_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, "STA Got IP");
/* Station got IP. That means configuraion is successful.
* Schedule timer to stop provisioning app after 30 seconds. */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTED;
if (g_prov && g_prov->timer) {
/* Note that, after restarting the WiFi in Station + AP mode, the
* user gets disconnected from the AP for a while. But at the same
* time, the user app requests for status update from the device
* to verify that the provisioning was successful. Therefore, the
* turning off of the AP must be delayed long enough for the user
* to reconnect and get STA connection status from the device.
* Otherwise, the AP will be turned off before the user can
* reconnect and thus the user app will see connection timed out,
* signalling a failure in provisioning. */
esp_timer_start_once(g_prov->timer, 30000*1000U);
}
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
ESP_LOGE(TAG, "STA Disconnected");
/* Station couldn't connect to configured host SSID */
g_prov->wifi_state = WIFI_PROV_STA_DISCONNECTED;
ESP_LOGE(TAG, "Disconnect reason : %d", info->disconnected.reason);
/* Set code corresponding to the reason for disconnection */
switch (info->disconnected.reason) {
case WIFI_REASON_AUTH_EXPIRE:
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT:
case WIFI_REASON_BEACON_TIMEOUT:
case WIFI_REASON_AUTH_FAIL:
case WIFI_REASON_ASSOC_FAIL:
case WIFI_REASON_HANDSHAKE_TIMEOUT:
ESP_LOGI(TAG, "STA Auth Error");
g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AUTH_ERROR;
break;
case WIFI_REASON_NO_AP_FOUND:
ESP_LOGI(TAG, "STA AP Not found");
g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AP_NOT_FOUND;
break;
default:
/* If none of the expected reasons,
* retry connecting to host SSID */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
esp_wifi_connect();
}
break;
default:
break;
}
return ESP_OK;
}
esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state)
{
if (g_prov == NULL || state == NULL) {
return ESP_FAIL;
}
*state = g_prov->wifi_state;
return ESP_OK;
}
esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason)
{
if (g_prov == NULL || reason == NULL) {
return ESP_FAIL;
}
if (g_prov->wifi_state != WIFI_PROV_STA_DISCONNECTED) {
return ESP_FAIL;
}
*reason = g_prov->wifi_disconnect_reason;
return ESP_OK;
}
esp_err_t app_prov_is_provisioned(bool *provisioned)
{
#ifdef CONFIG_RESET_PROVISIONED
nvs_flash_erase();
#endif
if (nvs_flash_init() != ESP_OK) {
ESP_LOGE(TAG, "Failed to init NVS");
return ESP_FAIL;
}
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
if (esp_wifi_init(&cfg) != ESP_OK) {
ESP_LOGE(TAG, "Failed to init wifi");
return ESP_FAIL;
}
/* Get WiFi Station configuration */
wifi_config_t wifi_cfg;
if (esp_wifi_get_config(ESP_IF_WIFI_STA, &wifi_cfg) != ESP_OK) {
*provisioned = false;
return ESP_FAIL;
}
if (strlen((const char*) wifi_cfg.sta.ssid)) {
*provisioned = true;
ESP_LOGI(TAG, "Found ssid %s", (const char*) wifi_cfg.sta.ssid);
ESP_LOGI(TAG, "Found password %s", (const char*) wifi_cfg.sta.password);
}
return ESP_OK;
}
esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg)
{
/* Configure WiFi as both AP and Station */
if (esp_wifi_set_mode(WIFI_MODE_APSTA) != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi mode");
return ESP_FAIL;
}
/* Configure WiFi station with host credentials
* provided during provisioning */
if (esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_cfg) != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi configuration");
return ESP_FAIL;
}
/* Restart WiFi */
if (esp_wifi_start() != ESP_OK) {
ESP_LOGE(TAG, "Failed to restart WiFi");
return ESP_FAIL;
}
/* Connect to AP */
if (esp_wifi_connect() != ESP_OK) {
ESP_LOGE(TAG, "Failed to connect WiFi");
return ESP_FAIL;
}
if (g_prov) {
/* Reset wifi station state for provisioning app */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
}
return ESP_OK;
}
static esp_err_t start_wifi_ap(const char *ssid, const char *pass)
{
/* Initialise WiFi with default configuration */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_err_t err = esp_wifi_init(&cfg);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to init WiFi : %d", err);
return err;
}
/* Build WiFi configuration for AP mode */
wifi_config_t wifi_config = {
.ap = {
.max_connection = 5,
},
};
strncpy((char *) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid));
wifi_config.ap.ssid_len = strlen(ssid);
if (strlen(pass) == 0) {
memset(wifi_config.ap.password, 0, sizeof(wifi_config.ap.password));
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
} else {
strncpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password));
wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK;
}
/* Start WiFi in AP mode with configuration built above */
err = esp_wifi_set_mode(WIFI_MODE_AP);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi mode : %d", err);
return err;
}
err = esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi config : %d", err);
return err;
}
err = esp_wifi_start();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start WiFi : %d", err);
return err;
}
return ESP_OK;
}
esp_err_t app_prov_start_softap_provisioning(const char *ssid, const char *pass,
int security, const protocomm_security_pop_t *pop)
{
/* If provisioning app data present,
* means provisioning app is already running */
if (g_prov) {
ESP_LOGI(TAG, "Invalid provisioning state");
return ESP_FAIL;
}
/* Allocate memory for provisioning app data */
g_prov = (struct app_prov_data *) calloc(1, sizeof(struct app_prov_data));
if (!g_prov) {
ESP_LOGI(TAG, "Unable to allocate prov data");
return ESP_ERR_NO_MEM;
}
/* Initialise app data */
g_prov->pop = pop;
g_prov->security = security;
/* Create timer object as a member of app data */
esp_timer_create_args_t timer_conf = {
.callback = _stop_softap_cb,
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "stop_softap_tm"
};
esp_err_t err = esp_timer_create(&timer_conf, &g_prov->timer);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to create timer");
return err;
}
/* Start WiFi softAP with specified ssid and password */
err = start_wifi_ap(ssid, pass);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start WiFi AP");
return err;
}
/* Start provisioning service through HTTP */
err = app_prov_start_service();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start provisioning app");
return err;
}
ESP_LOGI(TAG, "SoftAP Provisioning started with SSID %s, Password %s", ssid, pass);
return ESP_OK;
}

View File

@ -0,0 +1,103 @@
/* SoftAP based Custom Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#include <esp_event_loop.h>
#include <protocomm_security.h>
#include <wifi_provisioning/wifi_config.h>
#include <custom_provisioning/custom_config.h>
/**
* @brief Get state of WiFi Station during provisioning
*
* @note WiFi is initially configured as AP, when
* provisioning starts. After provisioning data
* is provided by user, the WiFi is reconfigured
* to run as both AP and Station.
*
* @param[out] state Pointer to wifi_prov_sta_state_t variable to be filled
*
* @return
* - ESP_OK : Successfully retrieved wifi state
* - ESP_FAIL : Provisioning app not running
*/
esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state);
/**
* @brief Get reason code in case of WiFi station
* disconnection during provisioning
*
* @param[out] reason Pointer to wifi_prov_sta_fail_reason_t variable to be filled
*
* @return
* - ESP_OK : Successfully retrieved wifi disconnect reason
* - ESP_FAIL : Provisioning app not running
*/
esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason);
/**
* @brief Event handler for provisioning app
*
* This is called from the main event handler and controls the
* provisioning application, depeding on WiFi events
*
* @param[in] ctx Event context data
* @param[in] event Event info
*
* @return
* - ESP_OK : Event handled successfully
* - ESP_FAIL : Failed to start server on event AP start
*/
esp_err_t app_prov_event_handler(void *ctx, system_event_t *event);
/**
* @brief Checks if device is provisioned
* *
* @param[out] provisioned True if provisioned, else false
*
* @return
* - ESP_OK : Retrieved provision state successfully
* - ESP_FAIL : Failed to retrieve provision state
*/
esp_err_t app_prov_is_provisioned(bool *provisioned);
/**
* @brief Runs WiFi as both AP and Station
*
* Configures the WiFi station mode to connect to the
* SSID and password specified in config structure,
* and restarts WiFi to run as both AP and station
*
* @param[in] wifi_cfg Pointer to WiFi cofiguration structure
*
* @return
* - ESP_OK : WiFi configured and restarted successfully
* - ESP_FAIL : Failed to set configuration
*/
esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg);
/**
* @brief Start provisioning via softAP
*
* Starts the WiFi softAP with specified ssid and pass, provisioning
* security mode and proof of possession (if any).
*
* @param[in] ssid SSID for SoftAP
* @param[in] pass Password for SoftAP
* @param[in] security Security mode
* @param[in] pop Pointer to proof of possession (NULL if not present)
*
* @return
* - ESP_OK : Provisioning started successfully
* - ESP_FAIL : Failed to start
*/
esp_err_t app_prov_start_softap_provisioning(const char *ssid, const char *pass,
int security, const protocomm_security_pop_t *pop);

View File

@ -0,0 +1,118 @@
/* SoftAP based Custom Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/* This file is mostly a boiler-plate code that applications can use without much change */
#include <stdio.h>
#include <string.h>
#include <esp_err.h>
#include <esp_log.h>
#include <esp_wifi.h>
#include <tcpip_adapter.h>
#include <wifi_provisioning/wifi_config.h>
#include <custom_provisioning/custom_config.h>
#include "app_prov.h"
static const char* TAG = "app_prov_handler";
/****************** Handler for Custom Configuration *******************/
static esp_err_t custom_config_handler(const custom_config_t *config)
{
ESP_LOGI(TAG, "Custom config received :\n\tInfo : %s\n\tVersion : %d",
config->info, config->version);
return ESP_OK;
}
custom_prov_config_handler_t custom_prov_handler = custom_config_handler;
/****************** Handlers for Wi-Fi Configuration *******************/
static esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data)
{
/* Initialise to zero */
memset(resp_data, 0, sizeof(wifi_prov_config_get_data_t));
if (app_prov_get_wifi_state(&resp_data->wifi_state) != ESP_OK) {
ESP_LOGW(TAG, "Prov app not running");
return ESP_FAIL;
}
if (resp_data->wifi_state == WIFI_PROV_STA_CONNECTED) {
ESP_LOGI(TAG, "Connected state");
/* IP Addr assigned to STA */
tcpip_adapter_ip_info_t ip_info;
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info);
char *ip_addr = ip4addr_ntoa(&ip_info.ip);
strcpy(resp_data->conn_info.ip_addr, ip_addr);
/* AP information to which STA is connected */
wifi_ap_record_t ap_info;
esp_wifi_sta_get_ap_info(&ap_info);
memcpy(resp_data->conn_info.bssid, (char *)ap_info.bssid, sizeof(ap_info.bssid));
memcpy(resp_data->conn_info.ssid, (char *)ap_info.ssid, sizeof(ap_info.ssid));
resp_data->conn_info.channel = ap_info.primary;
resp_data->conn_info.auth_mode = ap_info.authmode;
} else if (resp_data->wifi_state == WIFI_PROV_STA_DISCONNECTED) {
ESP_LOGI(TAG, "Disconnected state");
/* If disconnected, convey reason */
app_prov_get_wifi_disconnect_reason(&resp_data->fail_reason);
} else {
ESP_LOGI(TAG, "Connecting state");
}
return ESP_OK;
}
static wifi_config_t *wifi_cfg;
static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data)
{
if (wifi_cfg) {
free(wifi_cfg);
wifi_cfg = NULL;
}
wifi_cfg = (wifi_config_t *) calloc(1, sizeof(wifi_config_t));
if (!wifi_cfg) {
ESP_LOGE(TAG, "Unable to alloc wifi config");
return ESP_FAIL;
}
ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s",
req_data->ssid, req_data->password);
memcpy((char *) wifi_cfg->sta.ssid, req_data->ssid,
strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid)));
memcpy((char *) wifi_cfg->sta.password, req_data->password,
strnlen(req_data->password, sizeof(wifi_cfg->sta.password)));
return ESP_OK;
}
static esp_err_t apply_config_handler(void)
{
if (!wifi_cfg) {
ESP_LOGE(TAG, "WiFi config not set");
return ESP_FAIL;
}
app_prov_configure_sta(wifi_cfg);
ESP_LOGI(TAG, "WiFi Credentials Applied");
free(wifi_cfg);
wifi_cfg = NULL;
return ESP_OK;
}
wifi_prov_config_handlers_t wifi_prov_handlers = {
.get_status_handler = get_status_handler,
.set_config_handler = set_config_handler,
.apply_config_handler = apply_config_handler,
};

View File

@ -0,0 +1,8 @@
#
# Main component makefile.
#
# This Makefile can be left empty. By default, it will take the sources in the
# src/ directory, compile them and link them into lib(subdirectory_name).a
# in the build directory. This behaviour is entirely configurable,
# please read the ESP-IDF documents if you need to do this.
#

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(softap_prov)

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := softap_prov
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,141 @@
# SoftAP + HTTPD based Provisioning Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
`softap_prov` example demonstrates the implementation and integration of various IDF components for building a provisioning application.
For this example Wi-Fi SoftAP is chosen as the mode of transport, over which the provisioning related communication is to take place, between the device (to be provisioned) and the client (owner of the device). The provisioning service is hosted by an HTTP server which accepts requests to specific URIs corresponding to the available provisioning endpoints (eg. for session establishment, for Wi-Fi credentials configuration, etc.).
In the provisioning process the device is configured as a Wi-Fi station with specified credentials. Once configured, the device will retain the Wi-Fi configuration, until a flash erase is performed.
Right after provisioning is complete, Wi-Fi SoftAP and the HTTP server are deactivated. Though, that is specific to this example, and the user can choose to keep SoftAP / HTTP server active in their own application.
`softap_prov` uses the following components :
* `wifi_provisioning` : provides data structures and protocomm endpoint handlers for Wi-Fi configuration
* `protocomm` : for protocol based communication and secure session establishment
* `protobuf` : Google's protocol buffer library for serialization of protocomm data structures
This example can be used, as it is, for adding a provisioning service to any application intended for IoT.
## How to use example
### Hardware Required
Example should be able to run on any commonly available ESP32 development board.
### Application Required
To provision the device running this example, the `esp_prov.py` script needs to be run (found under `$IDF_PATH/tools/esp_prov`). This feature of `esp_prov` should work on all platforms as long long as all Python dependencies are satisfied.
For android, a provisioning tool along with source code is available [here](https://github.com/espressif/esp-idf-provisioning-android). This offers a simpler and more close to actual user experience.
### Configure the project
```
make menuconfig
```
* Set serial port under Serial Flasher Options.
* Under Example Configuration set the following :
* SoftAP SSID (Defaults to PROV_<MACID>)
* SoftAP Password (Defaults to PROV_PASS)
* Security Version (default 1)
* Proof of Possession (default "abcd1234")
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
make -j4 flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
I (1562) app: SoftAP started
I (1572) app_prov: SoftAP Provisioning started with SSID 'PROV_261FCC', Password 'PROV_PASS'
```
Make sure to connect the client computer to the SoftAP network, whose SSID and Password are displayed in the serial monitor log. On successful connection the monitor log will show :
```
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
```
Above command will perform the provisioning steps, and the monitor log should display something like this :
```
I (634572) app_prov_handler: WiFi Credentials Received :
ssid : myssid
password : mypassword
.
.
.
I (634652) app_prov_handler: WiFi Credentials Applied
I (634652) app_prov: STA Start
.
.
.
I (688270) app_prov_handler: Connecting state
.
.
.
I (637732) app_prov: STA Got IP
I (637732) app: got ip:192.168.43.220
.
.
.
I (654562) app_prov_handler: Connected state
```
After sometime the provisioning app will exit, SoftAP will be turned off and HTTP server will be stopped
```
I (667732) app_prov: Stopping provisioning
I (668732) app_prov: Provisioning stopped
I (668742) app: SoftAP stopped
```
## Troubleshooting
### Provisioning failed
It is possible that the Wi-Fi credentials provided were incorrect, or the device was not able to establish connection to the network, in which the the `esp_prov` script will notify failure (with reason) and the provisioning app will continue running, allowing the user to retry the process. Serial monitor log will display the failure along with disconnect reason :
```
E (39291) app_prov: STA Disconnected
E (39291) app_prov: Disconnect reason : 201
I (39291) app_prov: STA AP Not found
I (42021) app_prov_handler: Disconnected state
```
### Provisioning does not start
If the serial monitor log is different, as shown below :
```
I (539) app_prov: Found ssid myssid
I (539) app_prov: Found password mypassword
I (549) app: Starting WiFi station
```
It means the Wi-Fi credentials were already set by some other application flashed previously to your device. To erase these credentials either do full erase and then flash the example
```
make erase_flash
make -j4 flash monitor
```
Or, enable `Reset Provisioning` option under `Example Configuration` under menuconfig. But this will erase the saved Wi-Fi credentials every time the device boots, so this is not the preferred solution.

View File

@ -0,0 +1,6 @@
set(COMPONENT_SRCS "app_main.c"
"app_prov.c"
"app_prov_handlers.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View File

@ -0,0 +1,53 @@
menu "Example Configuration"
config SOFTAP_SSID_SET_MAC
bool "Use MAC as SSID"
default y
help
Set SoftAP SSID as PROV_<MAC>.
config SOFTAP_SSID
string "WiFi SSID"
default "PROV_SSID"
depends on !SOFTAP_SSID_SET_MAC
help
SSID (network name) for the example to connect to.
config SOFTAP_PASS
string "WiFi Password"
default "PROV_PASS"
help
WiFi password (WPA or WPA2) for the example to use.
config USE_SEC_1
bool
default y
prompt "Use Security Version 1"
help
Security version 1 used Curve25519 key exchange for establishing
secure session between device and client during provisioning
config USE_POP
bool
depends on USE_SEC_1
default y
prompt "Use proof-of-possession"
help
Proof-of-possession can be optionally used to prove that the device is indeed
in possession of the user who is provisioning the device. This proof-of-possession
is internally used to generate the shared secret through key exchange.
config POP
string "Proof-of-possession"
default "abcd1234"
depends on USE_POP
config RESET_PROVISIONED
bool
default n
prompt "Reset provisioned status of the device"
help
This erases the NVS to reset provisioned status of the device on every reboot.
Provisioned status is determined by the WiFi STA configuration, saved on the NVS.
endmenu

View File

@ -0,0 +1,131 @@
/* SoftAP based Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_system.h>
#include <esp_wifi.h>
#include <esp_event_loop.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <lwip/err.h>
#include <lwip/sys.h>
#include "app_prov.h"
static const char *TAG = "app";
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
/* Invoke Provisioning event handler first */
app_prov_event_handler(ctx, event);
switch(event->event_id) {
case SYSTEM_EVENT_AP_START:
ESP_LOGI(TAG, "SoftAP started");
break;
case SYSTEM_EVENT_AP_STOP:
ESP_LOGI(TAG, "SoftAP stopped");
break;
case SYSTEM_EVENT_STA_START:
esp_wifi_connect();
break;
case SYSTEM_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, "got ip:%s",
ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
break;
case SYSTEM_EVENT_AP_STACONNECTED:
ESP_LOGI(TAG, "station:"MACSTR" join, AID=%d",
MAC2STR(event->event_info.sta_connected.mac),
event->event_info.sta_connected.aid);
break;
case SYSTEM_EVENT_AP_STADISCONNECTED:
ESP_LOGI(TAG, "station:"MACSTR"leave, AID=%d",
MAC2STR(event->event_info.sta_disconnected.mac),
event->event_info.sta_disconnected.aid);
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
break;
default:
break;
}
return ESP_OK;
}
static void wifi_init_sta()
{
/* Start wifi in station mode with credentials set during provisioning */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_start() );
}
void app_main()
{
/* Security version */
int security = 0;
/* Proof of possession */
const protocomm_security_pop_t *pop = NULL;
#ifdef CONFIG_USE_SEC_1
security = 1;
#endif
/* Having proof of possession is optional */
#ifdef CONFIG_USE_POP
const static protocomm_security_pop_t app_pop = {
.data = (uint8_t *) CONFIG_POP,
.len = (sizeof(CONFIG_POP)-1)
};
pop = &app_pop;
#endif
/* Initialize networking stack */
tcpip_adapter_init();
/* Set our event handling */
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
/* Check if device is provisioned */
bool provisioned;
if (app_prov_is_provisioned(&provisioned) != ESP_OK) {
ESP_LOGE(TAG, "Error getting device provisioning state");
return;
}
if (provisioned == false) {
/* If not provisioned, start provisioning via soft AP */
ESP_LOGI(TAG, "Starting WiFi SoftAP provisioning");
const char *ssid = NULL;
#ifdef CONFIG_SOFTAP_SSID
ssid = CONFIG_SOFTAP_SSID;
#else
uint8_t eth_mac[6];
esp_wifi_get_mac(WIFI_IF_STA, eth_mac);
char ssid_with_mac[33];
snprintf(ssid_with_mac, sizeof(ssid_with_mac), "PROV_%02X%02X%02X",
eth_mac[3], eth_mac[4], eth_mac[5]);
ssid = ssid_with_mac;
#endif
app_prov_start_softap_provisioning(ssid, CONFIG_SOFTAP_PASS,
security, pop);
} else {
/* Start WiFi station with credentials set during provisioning */
ESP_LOGI(TAG, "Starting WiFi station");
wifi_init_sta(NULL);
}
}

View File

@ -0,0 +1,386 @@
/* SoftAP based Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_wifi.h>
#include <nvs_flash.h>
#include <nvs.h>
#include <protocomm.h>
#include <protocomm_httpd.h>
#include <protocomm_security0.h>
#include <protocomm_security1.h>
#include <wifi_provisioning/wifi_config.h>
#include "app_prov.h"
static const char *TAG = "app_prov";
/* Handlers for wifi_config provisioning endpoint */
extern wifi_prov_config_handlers_t wifi_prov_handlers;
/**
* @brief Data relevant to provisioning application
*/
struct app_prov_data {
protocomm_t *pc; /*!< Protocomm handler */
int security; /*!< Type of security to use with protocomm */
const protocomm_security_pop_t *pop; /*!< Pointer to proof of possession */
esp_timer_handle_t timer; /*!< Handle to timer */
/* State of WiFi Station */
wifi_prov_sta_state_t wifi_state;
/* Code for WiFi station disconnection (if disconnected) */
wifi_prov_sta_fail_reason_t wifi_disconnect_reason;
};
/* Pointer to provisioning application data */
static struct app_prov_data *g_prov;
static esp_err_t app_prov_start_service(void)
{
/* Create new protocomm instance */
g_prov->pc = protocomm_new();
if (g_prov->pc == NULL) {
ESP_LOGE(TAG, "Failed to create new protocomm instance");
return ESP_FAIL;
}
/* Config for protocomm_httpd_start() */
protocomm_httpd_config_t pc_config = PROTOCOMM_HTTPD_DEFAULT_CONFIG();
/* Start protocomm server on top of HTTP */
if (protocomm_httpd_start(g_prov->pc, &pc_config) != ESP_OK) {
ESP_LOGE(TAG, "Failed to start protocomm HTTP server");
return ESP_FAIL;
}
/* Set protocomm version verification endpoint for protocol */
protocomm_set_version(g_prov->pc, "proto-ver", "V0.1");
/* Set protocomm security type for endpoint */
if (g_prov->security == 0) {
protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security0, NULL);
} else if (g_prov->security == 1) {
protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security1, g_prov->pop);
}
/* Add endpoint for provisioning to set wifi station config */
if (protocomm_add_endpoint(g_prov->pc, "prov-config",
wifi_prov_config_data_handler,
(void *) &wifi_prov_handlers) != ESP_OK) {
ESP_LOGE(TAG, "Failed to set provisioning endpoint");
protocomm_httpd_stop(g_prov->pc);
return ESP_FAIL;
}
return ESP_OK;
}
static void app_prov_stop_service(void)
{
/* Remove provisioning endpoint */
protocomm_remove_endpoint(g_prov->pc, "prov-config");
/* Unset provisioning security */
protocomm_unset_security(g_prov->pc, "prov-session");
/* Unset provisioning version endpoint */
protocomm_unset_version(g_prov->pc, "proto-ver");
/* Stop protocomm server */
protocomm_httpd_stop(g_prov->pc);
/* Delete protocomm instance */
protocomm_delete(g_prov->pc);
}
/* Callback to be invoked by timer */
static void _stop_softap_cb(void * arg)
{
ESP_LOGI(TAG, "Stopping provisioning");
app_prov_stop_service();
esp_wifi_set_mode(WIFI_MODE_STA);
/* Timer not needed anymore */
esp_timer_handle_t timer = g_prov->timer;
esp_timer_delete(timer);
g_prov->timer = NULL;
/* Free provisioning process data */
free(g_prov);
g_prov = NULL;
ESP_LOGI(TAG, "Provisioning stopped");
}
/* Event handler for starting/stopping provisioning.
* To be called from within the context of the main
* event handler.
*/
esp_err_t app_prov_event_handler(void *ctx, system_event_t *event)
{
/* For accessing reason codes in case of disconnection */
system_event_info_t *info = &event->event_info;
/* If pointer to provisioning application data is NULL
* then provisioning is not running, therefore return without
* error */
if (!g_prov) {
return ESP_OK;
}
switch(event->event_id) {
case SYSTEM_EVENT_STA_START:
ESP_LOGI(TAG, "STA Start");
/* Once configuration is received by protocomm server,
* device is restarted as both AP and Station.
* Once station starts, wait for connection to
* establish with configured host SSID and password */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
break;
case SYSTEM_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, "STA Got IP");
/* Station got IP. That means configuraion is successful.
* Schedule timer to stop provisioning app after 30 seconds. */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTED;
if (g_prov && g_prov->timer) {
/* Note that, after restarting the WiFi in Station + AP mode, the
* user gets disconnected from the AP for a while. But at the same
* time, the user app requests for status update from the device
* to verify that the provisioning was successful. Therefore, the
* turning off of the AP must be delayed long enough for the user
* to reconnect and get STA connection status from the device.
* Otherwise, the AP will be turned off before the user can
* reconnect and thus the user app will see connection timed out,
* signalling a failure in provisioning. */
esp_timer_start_once(g_prov->timer, 30000*1000U);
}
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
ESP_LOGE(TAG, "STA Disconnected");
/* Station couldn't connect to configured host SSID */
g_prov->wifi_state = WIFI_PROV_STA_DISCONNECTED;
ESP_LOGE(TAG, "Disconnect reason : %d", info->disconnected.reason);
/* Set code corresponding to the reason for disconnection */
switch (info->disconnected.reason) {
case WIFI_REASON_AUTH_EXPIRE:
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT:
case WIFI_REASON_BEACON_TIMEOUT:
case WIFI_REASON_AUTH_FAIL:
case WIFI_REASON_ASSOC_FAIL:
case WIFI_REASON_HANDSHAKE_TIMEOUT:
ESP_LOGI(TAG, "STA Auth Error");
g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AUTH_ERROR;
break;
case WIFI_REASON_NO_AP_FOUND:
ESP_LOGI(TAG, "STA AP Not found");
g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AP_NOT_FOUND;
break;
default:
/* If none of the expected reasons,
* retry connecting to host SSID */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
esp_wifi_connect();
}
break;
default:
break;
}
return ESP_OK;
}
esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state)
{
if (g_prov == NULL || state == NULL) {
return ESP_FAIL;
}
*state = g_prov->wifi_state;
return ESP_OK;
}
esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason)
{
if (g_prov == NULL || reason == NULL) {
return ESP_FAIL;
}
if (g_prov->wifi_state != WIFI_PROV_STA_DISCONNECTED) {
return ESP_FAIL;
}
*reason = g_prov->wifi_disconnect_reason;
return ESP_OK;
}
esp_err_t app_prov_is_provisioned(bool *provisioned)
{
*provisioned = false;
#ifdef CONFIG_RESET_PROVISIONED
nvs_flash_erase();
#endif
if (nvs_flash_init() != ESP_OK) {
ESP_LOGE(TAG, "Failed to init NVS");
return ESP_FAIL;
}
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
if (esp_wifi_init(&cfg) != ESP_OK) {
ESP_LOGE(TAG, "Failed to init wifi");
return ESP_FAIL;
}
/* Get WiFi Station configuration */
wifi_config_t wifi_cfg;
if (esp_wifi_get_config(ESP_IF_WIFI_STA, &wifi_cfg) != ESP_OK) {
return ESP_FAIL;
}
if (strlen((const char*) wifi_cfg.sta.ssid)) {
*provisioned = true;
ESP_LOGI(TAG, "Found ssid %s", (const char*) wifi_cfg.sta.ssid);
ESP_LOGI(TAG, "Found password %s", (const char*) wifi_cfg.sta.password);
}
return ESP_OK;
}
esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg)
{
/* Configure WiFi as both AP and Station */
if (esp_wifi_set_mode(WIFI_MODE_APSTA) != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi mode");
return ESP_FAIL;
}
/* Configure WiFi station with host credentials
* provided during provisioning */
if (esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_cfg) != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi configuration");
return ESP_FAIL;
}
/* Restart WiFi */
if (esp_wifi_start() != ESP_OK) {
ESP_LOGE(TAG, "Failed to restart WiFi");
return ESP_FAIL;
}
/* Connect to AP */
if (esp_wifi_connect() != ESP_OK) {
ESP_LOGE(TAG, "Failed to connect WiFi");
return ESP_FAIL;
}
if (g_prov) {
/* Reset wifi station state for provisioning app */
g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
}
return ESP_OK;
}
static esp_err_t start_wifi_ap(const char *ssid, const char *pass)
{
/* Initialise WiFi with default configuration */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_err_t err = esp_wifi_init(&cfg);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to init WiFi : %d", err);
return err;
}
/* Build WiFi configuration for AP mode */
wifi_config_t wifi_config = {
.ap = {
.max_connection = 5,
},
};
strncpy((char *) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid));
wifi_config.ap.ssid_len = strlen(ssid);
if (strlen(pass) == 0) {
memset(wifi_config.ap.password, 0, sizeof(wifi_config.ap.password));
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
} else {
strncpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password));
wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK;
}
/* Start WiFi in AP mode with configuration built above */
err = esp_wifi_set_mode(WIFI_MODE_AP);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi mode : %d", err);
return err;
}
err = esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi config : %d", err);
return err;
}
err = esp_wifi_start();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start WiFi : %d", err);
return err;
}
return ESP_OK;
}
esp_err_t app_prov_start_softap_provisioning(const char *ssid, const char *pass,
int security, const protocomm_security_pop_t *pop)
{
/* If provisioning app data present,
* means provisioning app is already running */
if (g_prov) {
ESP_LOGI(TAG, "Invalid provisioning state");
return ESP_FAIL;
}
/* Allocate memory for provisioning app data */
g_prov = (struct app_prov_data *) calloc(1, sizeof(struct app_prov_data));
if (!g_prov) {
ESP_LOGI(TAG, "Unable to allocate prov data");
return ESP_ERR_NO_MEM;
}
/* Initialise app data */
g_prov->pop = pop;
g_prov->security = security;
/* Create timer object as a member of app data */
esp_timer_create_args_t timer_conf = {
.callback = _stop_softap_cb,
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "stop_softap_tm"
};
esp_err_t err = esp_timer_create(&timer_conf, &g_prov->timer);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to create timer");
return err;
}
/* Start WiFi softAP with specified ssid and password */
err = start_wifi_ap(ssid, pass);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start WiFi AP");
return err;
}
/* Start provisioning service through HTTP */
err = app_prov_start_service();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start provisioning app");
return err;
}
ESP_LOGI(TAG, "SoftAP Provisioning started with SSID '%s', Password '%s'", ssid, pass);
return ESP_OK;
}

View File

@ -0,0 +1,102 @@
/* SoftAP based Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#include <esp_event_loop.h>
#include <protocomm_security.h>
#include <wifi_provisioning/wifi_config.h>
/**
* @brief Get state of WiFi Station during provisioning
*
* @note WiFi is initially configured as AP, when
* provisioning starts. After provisioning data
* is provided by user, the WiFi is reconfigured
* to run as both AP and Station.
*
* @param[out] state Pointer to wifi_prov_sta_state_t variable to be filled
*
* @return
* - ESP_OK : Successfully retrieved wifi state
* - ESP_FAIL : Provisioning app not running
*/
esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state);
/**
* @brief Get reason code in case of WiFi station
* disconnection during provisioning
*
* @param[out] reason Pointer to wifi_prov_sta_fail_reason_t variable to be filled
*
* @return
* - ESP_OK : Successfully retrieved wifi disconnect reason
* - ESP_FAIL : Provisioning app not running
*/
esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason);
/**
* @brief Event handler for provisioning app
*
* This is called from the main event handler and controls the
* provisioning application, depeding on WiFi events
*
* @param[in] ctx Event context data
* @param[in] event Event info
*
* @return
* - ESP_OK : Event handled successfully
* - ESP_FAIL : Failed to start server on event AP start
*/
esp_err_t app_prov_event_handler(void *ctx, system_event_t *event);
/**
* @brief Checks if device is provisioned
* *
* @param[out] provisioned True if provisioned, else false
*
* @return
* - ESP_OK : Retrieved provision state successfully
* - ESP_FAIL : Failed to retrieve provision state
*/
esp_err_t app_prov_is_provisioned(bool *provisioned);
/**
* @brief Runs WiFi as both AP and Station
*
* Configures the WiFi station mode to connect to the
* SSID and password specified in config structure,
* and restarts WiFi to run as both AP and station
*
* @param[in] wifi_cfg Pointer to WiFi cofiguration structure
*
* @return
* - ESP_OK : WiFi configured and restarted successfully
* - ESP_FAIL : Failed to set configuration
*/
esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg);
/**
* @brief Start provisioning via softAP
*
* Starts the WiFi softAP with specified ssid and pass, provisioning
* security mode and proof of possession (if any).
*
* @param[in] ssid SSID for SoftAP
* @param[in] pass Password for SoftAP
* @param[in] security Security mode
* @param[in] pop Pointer to proof of possession (NULL if not present)
*
* @return
* - ESP_OK : Provisioning started successfully
* - ESP_FAIL : Failed to start
*/
esp_err_t app_prov_start_softap_provisioning(const char *ssid, const char *pass,
int security, const protocomm_security_pop_t *pop);

View File

@ -0,0 +1,106 @@
/* SoftAP based Provisioning Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/* This file is mostly a boiler-plate code that applications can use without much change */
#include <stdio.h>
#include <string.h>
#include <esp_err.h>
#include <esp_log.h>
#include <esp_wifi.h>
#include <tcpip_adapter.h>
#include <wifi_provisioning/wifi_config.h>
#include "app_prov.h"
static const char* TAG = "app_prov_handler";
static esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data)
{
/* Initialise to zero */
memset(resp_data, 0, sizeof(wifi_prov_config_get_data_t));
if (app_prov_get_wifi_state(&resp_data->wifi_state) != ESP_OK) {
ESP_LOGW(TAG, "Prov app not running");
return ESP_FAIL;
}
if (resp_data->wifi_state == WIFI_PROV_STA_CONNECTED) {
ESP_LOGI(TAG, "Connected state");
/* IP Addr assigned to STA */
tcpip_adapter_ip_info_t ip_info;
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info);
char *ip_addr = ip4addr_ntoa(&ip_info.ip);
strcpy(resp_data->conn_info.ip_addr, ip_addr);
/* AP information to which STA is connected */
wifi_ap_record_t ap_info;
esp_wifi_sta_get_ap_info(&ap_info);
memcpy(resp_data->conn_info.bssid, (char *)ap_info.bssid, sizeof(ap_info.bssid));
memcpy(resp_data->conn_info.ssid, (char *)ap_info.ssid, sizeof(ap_info.ssid));
resp_data->conn_info.channel = ap_info.primary;
resp_data->conn_info.auth_mode = ap_info.authmode;
} else if (resp_data->wifi_state == WIFI_PROV_STA_DISCONNECTED) {
ESP_LOGI(TAG, "Disconnected state");
/* If disconnected, convey reason */
app_prov_get_wifi_disconnect_reason(&resp_data->fail_reason);
} else {
ESP_LOGI(TAG, "Connecting state");
}
return ESP_OK;
}
static wifi_config_t *wifi_cfg;
static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data)
{
if (wifi_cfg) {
free(wifi_cfg);
wifi_cfg = NULL;
}
wifi_cfg = (wifi_config_t *) calloc(1, sizeof(wifi_config_t));
if (!wifi_cfg) {
ESP_LOGE(TAG, "Unable to alloc wifi config");
return ESP_FAIL;
}
ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s",
req_data->ssid, req_data->password);
memcpy((char *) wifi_cfg->sta.ssid, req_data->ssid,
strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid)));
memcpy((char *) wifi_cfg->sta.password, req_data->password,
strnlen(req_data->password, sizeof(wifi_cfg->sta.password)));
return ESP_OK;
}
static esp_err_t apply_config_handler(void)
{
if (!wifi_cfg) {
ESP_LOGE(TAG, "WiFi config not set");
return ESP_FAIL;
}
app_prov_configure_sta(wifi_cfg);
ESP_LOGI(TAG, "WiFi Credentials Applied");
free(wifi_cfg);
wifi_cfg = NULL;
return ESP_OK;
}
wifi_prov_config_handlers_t wifi_prov_handlers = {
.get_status_handler = get_status_handler,
.set_config_handler = set_config_handler,
.apply_config_handler = apply_config_handler,
};

View File

@ -0,0 +1,8 @@
#
# Main component makefile.
#
# This Makefile can be left empty. By default, it will take the sources in the
# src/ directory, compile them and link them into lib(subdirectory_name).a
# in the build directory. This behaviour is entirely configurable,
# please read the ESP-IDF documents if you need to do this.
#

View File

@ -0,0 +1,125 @@
#!/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 imp
import re
import os
import sys
import time
# This environment variable is expected on the host machine
test_fw_path = os.getenv("TEST_FW_PATH")
if test_fw_path and test_fw_path not in sys.path:
sys.path.insert(0, test_fw_path)
# When running on local machine execute the following before running this script
# > export TEST_FW_PATH='~/esp/esp-idf/tools/tiny-test-fw'
# > make print_flash_cmd | tail -n 1 > build/download.config
# > make app bootloader
import TinyFW
import IDF
# Import esp_prov tool
idf_path = os.environ['IDF_PATH']
esp_prov = imp.load_source("esp_prov", idf_path + "/tools/esp_prov/esp_prov.py")
wifi_tools = imp.load_source("wifi_tools", idf_path + "/examples/provisioning/softap_prov/utils/wifi_tools.py")
@IDF.idf_example_test(env_tag="Example_WIFI_BT")
def test_examples_provisioning_softap(env, extra_data):
# Acquire DUT
dut1 = env.get_dut("softap_prov", "examples/provisioning/softap_prov")
# Get binary file
binary_file = os.path.join(dut1.app.binary_path, "softap_prov.bin")
bin_size = os.path.getsize(binary_file)
IDF.log_performance("softap_prov_bin_size", "{}KB".format(bin_size//1024))
IDF.check_performance("softap_prov_bin_size", bin_size//1024)
# Upload binary and start testing
dut1.start_app()
# Parse IP address of STA
dut1.expect("Starting WiFi SoftAP provisioning")
dut1.expect("SoftAP started")
[ssid, password] = dut1.expect(re.compile(r"(?:[\s\S]*)SoftAP Provisioning started with SSID '(\S+)', Password '(\S+)'"))
iface = wifi_tools.get_wiface_name()
if iface == None:
raise RuntimeError("Failed to get Wi-Fi interface on host")
print("Interface name : " + iface)
print("SoftAP SSID : " + ssid)
print("SoftAP Password : " + password)
ctrl = wifi_tools.wpa_cli(iface, reset_on_exit = True)
print("Connecting to DUT SoftAP...")
ip = ctrl.connect(ssid, password)
print("Connected to DUT SoftAP")
print("Starting Provisioning")
verbose = False
protover = "V0.1"
secver = 1
pop = "abcd1234"
provmode = "softap"
ap_ssid = "myssid"
ap_password = "mypassword"
softap_endpoint = ip.split('.')[0] + "." + ip.split('.')[1]+ "." + ip.split('.')[2] + ".1:80"
print("Getting security")
security = esp_prov.get_security(secver, pop, verbose)
if security == None:
raise RuntimeError("Failed to get security")
print("Getting transport")
transport = esp_prov.get_transport(provmode, softap_endpoint, None)
if transport == None:
raise RuntimeError("Failed to get transport")
print("Verifying protocol version")
if not esp_prov.version_match(transport, protover):
raise RuntimeError("Mismatch in protocol version")
print("Starting Session")
if not esp_prov.establish_session(transport, security):
raise RuntimeError("Failed to start session")
print("Sending Wifi credential to DUT")
if not esp_prov.send_wifi_config(transport, security, ap_ssid, ap_password):
raise RuntimeError("Failed to send Wi-Fi config")
print("Applying config")
if not esp_prov.apply_wifi_config(transport, security):
raise RuntimeError("Failed to send apply config")
success = False
while True:
time.sleep(5)
print("Wi-Fi connection state")
ret = esp_prov.get_wifi_config(transport, security)
if (ret == 1):
continue
elif (ret == 0):
print("Provisioning was successful")
success = True
break
if not success:
raise RuntimeError("Provisioning failed")
if __name__ == '__main__':
test_examples_provisioning_softap()

View File

@ -0,0 +1,98 @@
# 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 dbus
import dbus.mainloop.glib
import netifaces
import time
def get_wiface_name():
for iface in netifaces.interfaces():
if iface.startswith('w'):
return iface
return None
def get_wiface_IPv4(iface):
try:
[info] = netifaces.ifaddresses(iface)[netifaces.AF_INET]
return info['addr']
except KeyError:
return None
class wpa_cli:
def __init__(self, iface, reset_on_exit = False):
self.iface_name = iface
self.iface_obj = None
self.iface_ifc = None
self.old_network = None
self.new_network = None
self.connected = False
self.reset_on_exit = reset_on_exit
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
service = dbus.Interface(bus.get_object("fi.w1.wpa_supplicant1", "/fi/w1/wpa_supplicant1"), "fi.w1.wpa_supplicant1")
paths = service.Get("fi.w1.wpa_supplicant1", "Interfaces", dbus_interface='org.freedesktop.DBus.Properties')
iface_path = service.GetInterface(self.iface_name)
self.iface_obj = bus.get_object("fi.w1.wpa_supplicant1", iface_path)
self.iface_ifc = dbus.Interface(self.iface_obj, "fi.w1.wpa_supplicant1.Interface")
if self.iface_ifc == None:
raise RuntimeError('supplicant : Failed to fetch interface')
self.old_network = self.iface_obj.Get("fi.w1.wpa_supplicant1.Interface", "CurrentNetwork", dbus_interface='org.freedesktop.DBus.Properties')
if self.old_network == '/':
self.old_network = None
else:
self.connected = True
def connect(self, ssid, password):
if self.connected == True:
self.iface_ifc.Disconnect()
self.connected = False
if self.new_network != None:
self.iface_ifc.RemoveNetwork(self.new_network)
self.new_network = self.iface_ifc.AddNetwork({"ssid": ssid, "psk": password})
self.iface_ifc.SelectNetwork(self.new_network)
ip = None
retry = 10
while retry > 0:
time.sleep(5)
ip = get_wiface_IPv4(self.iface_name)
if ip != None:
self.connected = True
return ip
retry -= 1
self.reset()
raise RuntimeError('wpa_cli : Connection failed')
def reset(self):
if self.iface_ifc != None:
if self.connected == True:
self.iface_ifc.Disconnect()
self.connected = False
if self.new_network != None:
self.iface_ifc.RemoveNetwork(self.new_network)
self.new_network = None
if self.old_network != None:
self.iface_ifc.SelectNetwork(self.old_network)
self.old_network = None
def __del__(self):
if self.reset_on_exit == True:
self.reset()

109
tools/esp_prov/README.md Normal file
View 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
View 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 != "SUCCESS":
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

View 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")

View 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 *

View 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

View 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

View File

@ -0,0 +1,3 @@
future
protobuf
cryptography

View File

@ -0,0 +1 @@
dbus-python

View 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 *

View 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

View 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

View 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)

View 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 *

View 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()

View 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

View 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)

View 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)

View 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)

View 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 *

View 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))