app_prov.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. /* BLE based Provisioning Example
  2. This example code is in the Public Domain (or CC0 licensed, at your option.)
  3. Unless required by applicable law or agreed to in writing, this
  4. software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  5. CONDITIONS OF ANY KIND, either express or implied.
  6. */
  7. #include <string.h>
  8. #include <esp_log.h>
  9. #include <esp_err.h>
  10. #include <esp_wifi.h>
  11. #include <nvs_flash.h>
  12. #include <nvs.h>
  13. #include <esp_bt.h>
  14. #include <esp_event.h>
  15. #ifdef CONFIG_BT_NIMBLE_ENABLED
  16. #include "esp_nimble_hci.h"
  17. #include "nimble/nimble_port.h"
  18. #include "nimble/nimble_port_freertos.h"
  19. #include "host/ble_hs.h"
  20. #endif
  21. #include <protocomm.h>
  22. #include <protocomm_ble.h>
  23. #include <protocomm_security0.h>
  24. #include <protocomm_security1.h>
  25. #include <wifi_provisioning/wifi_config.h>
  26. #include "app_prov.h"
  27. static const char *TAG = "app_prov";
  28. static const char *ssid_prefix = "PROV_";
  29. /* Handler for catching WiFi events */
  30. static void app_prov_event_handler(void* handler_arg, esp_event_base_t base, int id, void* data);
  31. /* Handlers for wifi_config provisioning endpoint */
  32. extern wifi_prov_config_handlers_t wifi_prov_handlers;
  33. /**
  34. * @brief Data relevant to provisioning application
  35. */
  36. struct app_prov_data {
  37. protocomm_t *pc; /*!< Protocomm handler */
  38. int security; /*!< Type of security to use with protocomm */
  39. const protocomm_security_pop_t *pop; /*!< Pointer to proof of possession */
  40. esp_timer_handle_t timer; /*!< Handle to timer */
  41. /* State of WiFi Station */
  42. wifi_prov_sta_state_t wifi_state;
  43. /* Code for WiFi station disconnection (if disconnected) */
  44. wifi_prov_sta_fail_reason_t wifi_disconnect_reason;
  45. };
  46. /* Pointer to provisioning application data */
  47. static struct app_prov_data *g_prov;
  48. static esp_err_t app_prov_start_service(void)
  49. {
  50. /* Create new protocomm instance */
  51. g_prov->pc = protocomm_new();
  52. if (g_prov->pc == NULL) {
  53. ESP_LOGE(TAG, "Failed to create new protocomm instance");
  54. return ESP_FAIL;
  55. }
  56. /* Endpoint UUIDs */
  57. protocomm_ble_name_uuid_t nu_lookup_table[] = {
  58. {"prov-session", 0x0001},
  59. {"prov-config", 0x0002},
  60. {"proto-ver", 0x0003},
  61. };
  62. /* Config for protocomm_ble_start() */
  63. protocomm_ble_config_t config = {
  64. .service_uuid = {
  65. /* LSB <---------------------------------------
  66. * ---------------------------------------> MSB */
  67. 0xb4, 0xdf, 0x5a, 0x1c, 0x3f, 0x6b, 0xf4, 0xbf,
  68. 0xea, 0x4a, 0x82, 0x03, 0x04, 0x90, 0x1a, 0x02,
  69. },
  70. .nu_lookup_count = sizeof(nu_lookup_table)/sizeof(nu_lookup_table[0]),
  71. .nu_lookup = nu_lookup_table
  72. };
  73. /* With the above selection of 128bit primary service UUID and
  74. * 16bit endpoint UUIDs, the 128bit characteristic UUIDs will be
  75. * formed by replacing the 12th and 13th bytes of the service UUID
  76. * with the 16bit endpoint UUID, i.e. :
  77. * service UUID : 021a9004-0382-4aea-bff4-6b3f1c5adfb4
  78. * masked base : 021a____-0382-4aea-bff4-6b3f1c5adfb4
  79. * ------------------------------------------------------
  80. * resulting characteristic UUIDs :
  81. * 1) prov-session : 021a0001-0382-4aea-bff4-6b3f1c5adfb4
  82. * 2) prov-config : 021a0002-0382-4aea-bff4-6b3f1c5adfb4
  83. * 3) proto-ver : 021a0003-0382-4aea-bff4-6b3f1c5adfb4
  84. *
  85. * Also, note that each endpoint (characteristic) will have
  86. * an associated "Characteristic User Description" descriptor
  87. * with 16bit UUID 0x2901, each containing the corresponding
  88. * endpoint name. These descriptors can be used by a client
  89. * side application to figure out which characteristic is
  90. * mapped to which endpoint, without having to hardcode the
  91. * UUIDs */
  92. uint8_t eth_mac[6];
  93. esp_wifi_get_mac(WIFI_IF_STA, eth_mac);
  94. snprintf(config.device_name, sizeof(config.device_name), "%s%02X%02X%02X",
  95. ssid_prefix, eth_mac[3], eth_mac[4], eth_mac[5]);
  96. /* Release BT memory, as we need only BLE */
  97. esp_err_t err = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
  98. if (err) {
  99. ESP_LOGE(TAG, "bt_controller_mem_release failed %d", err);
  100. if (err != ESP_ERR_INVALID_STATE) {
  101. return err;
  102. }
  103. }
  104. /* Start protocomm layer on top of BLE */
  105. if (protocomm_ble_start(g_prov->pc, &config) != ESP_OK) {
  106. ESP_LOGE(TAG, "Failed to start BLE provisioning");
  107. return ESP_FAIL;
  108. }
  109. /* Set protocomm version verification endpoint for protocol */
  110. protocomm_set_version(g_prov->pc, "proto-ver", "V0.1");
  111. /* Set protocomm security type for endpoint */
  112. if (g_prov->security == 0) {
  113. protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security0, NULL);
  114. } else if (g_prov->security == 1) {
  115. protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security1, g_prov->pop);
  116. }
  117. /* Add endpoint for provisioning to set wifi station config */
  118. if (protocomm_add_endpoint(g_prov->pc, "prov-config",
  119. wifi_prov_config_data_handler,
  120. (void *) &wifi_prov_handlers) != ESP_OK) {
  121. ESP_LOGE(TAG, "Failed to set provisioning endpoint");
  122. protocomm_ble_stop(g_prov->pc);
  123. return ESP_FAIL;
  124. }
  125. ESP_LOGI(TAG, "Provisioning started with BLE devname : '%s'", config.device_name);
  126. return ESP_OK;
  127. }
  128. static void app_prov_stop_service(void)
  129. {
  130. /* Remove provisioning endpoint */
  131. protocomm_remove_endpoint(g_prov->pc, "prov-config");
  132. /* Unset provisioning security */
  133. protocomm_unset_security(g_prov->pc, "prov-session");
  134. /* Unset provisioning version endpoint */
  135. protocomm_unset_version(g_prov->pc, "proto-ver");
  136. /* Stop protocomm ble service */
  137. protocomm_ble_stop(g_prov->pc);
  138. /* Delete protocomm instance */
  139. protocomm_delete(g_prov->pc);
  140. /* Remove event handler */
  141. esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, app_prov_event_handler);
  142. esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, app_prov_event_handler);
  143. /* Release memory used by BT stack */
  144. esp_bt_mem_release(ESP_BT_MODE_BTDM);
  145. }
  146. /* Task spawned by timer callback */
  147. static void stop_prov_task(void * arg)
  148. {
  149. ESP_LOGI(TAG, "Stopping provisioning");
  150. app_prov_stop_service();
  151. /* Timer not needed anymore */
  152. esp_timer_handle_t timer = g_prov->timer;
  153. esp_timer_delete(timer);
  154. g_prov->timer = NULL;
  155. /* Free provisioning process data */
  156. free(g_prov);
  157. g_prov = NULL;
  158. ESP_LOGI(TAG, "Provisioning stopped");
  159. vTaskDelete(NULL);
  160. }
  161. /* Callback to be invoked by timer */
  162. static void _stop_prov_cb(void * arg)
  163. {
  164. xTaskCreate(&stop_prov_task, "stop_prov", 2048, NULL, tskIDLE_PRIORITY, NULL);
  165. }
  166. /* Event handler for starting/stopping provisioning */
  167. static void app_prov_event_handler(void* handler_arg, esp_event_base_t event_base,
  168. int event_id, void* event_data)
  169. {
  170. /* If pointer to provisioning application data is NULL
  171. * then provisioning is not running */
  172. if (!g_prov) {
  173. return;
  174. }
  175. if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
  176. ESP_LOGI(TAG, "STA Start");
  177. /* Once configuration is received through protocomm,
  178. * device is started as station. Once station starts,
  179. * wait for connection to establish with configured
  180. * host SSID and password */
  181. g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
  182. } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
  183. ESP_LOGI(TAG, "STA Got IP");
  184. /* Station got IP. That means configuration is successful.
  185. * Schedule timer to stop provisioning app after 30 seconds. */
  186. g_prov->wifi_state = WIFI_PROV_STA_CONNECTED;
  187. if (g_prov && g_prov->timer) {
  188. esp_timer_start_once(g_prov->timer, 30000*1000U);
  189. }
  190. } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
  191. ESP_LOGE(TAG, "STA Disconnected");
  192. /* Station couldn't connect to configured host SSID */
  193. g_prov->wifi_state = WIFI_PROV_STA_DISCONNECTED;
  194. wifi_event_sta_disconnected_t* disconnected = (wifi_event_sta_disconnected_t*) event_data;
  195. ESP_LOGE(TAG, "Disconnect reason : %d", disconnected->reason);
  196. /* Set code corresponding to the reason for disconnection */
  197. switch (disconnected->reason) {
  198. case WIFI_REASON_AUTH_EXPIRE:
  199. case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT:
  200. case WIFI_REASON_BEACON_TIMEOUT:
  201. case WIFI_REASON_AUTH_FAIL:
  202. case WIFI_REASON_ASSOC_FAIL:
  203. case WIFI_REASON_HANDSHAKE_TIMEOUT:
  204. ESP_LOGI(TAG, "STA Auth Error");
  205. g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AUTH_ERROR;
  206. break;
  207. case WIFI_REASON_NO_AP_FOUND:
  208. ESP_LOGI(TAG, "STA AP Not found");
  209. g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AP_NOT_FOUND;
  210. break;
  211. default:
  212. /* If none of the expected reasons,
  213. * retry connecting to host SSID */
  214. g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
  215. esp_wifi_connect();
  216. }
  217. }
  218. }
  219. esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state)
  220. {
  221. if (g_prov == NULL || state == NULL) {
  222. return ESP_FAIL;
  223. }
  224. *state = g_prov->wifi_state;
  225. return ESP_OK;
  226. }
  227. esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason)
  228. {
  229. if (g_prov == NULL || reason == NULL) {
  230. return ESP_FAIL;
  231. }
  232. if (g_prov->wifi_state != WIFI_PROV_STA_DISCONNECTED) {
  233. return ESP_FAIL;
  234. }
  235. *reason = g_prov->wifi_disconnect_reason;
  236. return ESP_OK;
  237. }
  238. esp_err_t app_prov_is_provisioned(bool *provisioned)
  239. {
  240. *provisioned = false;
  241. #ifdef CONFIG_EXAMPLE_RESET_PROVISIONED
  242. nvs_flash_erase();
  243. #endif
  244. /* Get WiFi Station configuration */
  245. wifi_config_t wifi_cfg;
  246. if (esp_wifi_get_config(ESP_IF_WIFI_STA, &wifi_cfg) != ESP_OK) {
  247. return ESP_FAIL;
  248. }
  249. if (strlen((const char*) wifi_cfg.sta.ssid)) {
  250. *provisioned = true;
  251. ESP_LOGI(TAG, "Found ssid %s", (const char*) wifi_cfg.sta.ssid);
  252. ESP_LOGI(TAG, "Found password %s", (const char*) wifi_cfg.sta.password);
  253. }
  254. return ESP_OK;
  255. }
  256. esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg)
  257. {
  258. /* Configure WiFi as station */
  259. if (esp_wifi_set_mode(WIFI_MODE_STA) != ESP_OK) {
  260. ESP_LOGE(TAG, "Failed to set WiFi mode");
  261. return ESP_FAIL;
  262. }
  263. /* Configure WiFi station with host credentials
  264. * provided during provisioning */
  265. if (esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_cfg) != ESP_OK) {
  266. ESP_LOGE(TAG, "Failed to set WiFi configuration");
  267. return ESP_FAIL;
  268. }
  269. /* Start WiFi */
  270. if (esp_wifi_start() != ESP_OK) {
  271. ESP_LOGE(TAG, "Failed to set WiFi configuration");
  272. return ESP_FAIL;
  273. }
  274. /* Connect to AP */
  275. if (esp_wifi_connect() != ESP_OK) {
  276. ESP_LOGE(TAG, "Failed to connect WiFi");
  277. return ESP_FAIL;
  278. }
  279. if (g_prov) {
  280. /* Reset wifi station state for provisioning app */
  281. g_prov->wifi_state = WIFI_PROV_STA_CONNECTING;
  282. }
  283. return ESP_OK;
  284. }
  285. esp_err_t app_prov_start_ble_provisioning(int security, const protocomm_security_pop_t *pop)
  286. {
  287. /* If provisioning app data present,
  288. * means provisioning app is already running */
  289. if (g_prov) {
  290. ESP_LOGI(TAG, "Invalid provisioning state");
  291. return ESP_FAIL;
  292. }
  293. /* Allocate memory for provisioning app data */
  294. g_prov = (struct app_prov_data *) calloc(1, sizeof(struct app_prov_data));
  295. if (!g_prov) {
  296. ESP_LOGI(TAG, "Unable to allocate prov data");
  297. return ESP_ERR_NO_MEM;
  298. }
  299. /* Initialize app data */
  300. g_prov->pop = pop;
  301. g_prov->security = security;
  302. /* Create timer object as a member of app data */
  303. esp_timer_create_args_t timer_conf = {
  304. .callback = _stop_prov_cb,
  305. .arg = NULL,
  306. .dispatch_method = ESP_TIMER_TASK,
  307. .name = "stop_ble_tm"
  308. };
  309. esp_err_t err = esp_timer_create(&timer_conf, &g_prov->timer);
  310. if (err != ESP_OK) {
  311. ESP_LOGE(TAG, "Failed to create timer");
  312. return err;
  313. }
  314. err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, app_prov_event_handler, NULL);
  315. if (err != ESP_OK) {
  316. ESP_LOGE(TAG, "Failed to register WiFi event handler");
  317. return err;
  318. }
  319. err = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, app_prov_event_handler, NULL);
  320. if (err != ESP_OK) {
  321. ESP_LOGE(TAG, "Failed to register IP event handler");
  322. return err;
  323. }
  324. /* Start provisioning service through BLE */
  325. err = app_prov_start_service();
  326. if (err != ESP_OK) {
  327. ESP_LOGE(TAG, "Provisioning failed to start");
  328. return err;
  329. }
  330. ESP_LOGI(TAG, "BLE Provisioning started");
  331. return ESP_OK;
  332. }