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