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