-
Notifications
You must be signed in to change notification settings - Fork 706
/
at_web_server_cmd.c
2033 lines (1789 loc) · 74.3 KB
/
at_web_server_cmd.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <time.h>
#include <sys/queue.h>
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "cJSON.h"
#include "esp_wifi.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_vfs.h"
#include "esp_ota_ops.h"
#include "esp_image_format.h"
#include "esp_flash_partitions.h"
#include "esp_partition.h"
#include "esp_mac.h"
#include "esp_at.h"
#ifdef CONFIG_AT_WEB_SERVER_SUPPORT
#include "esp_http_server.h"
// AT web can use fatfs to storge html or use embeded file to storge html.
// If use fatfs,we should enable AT FS Command support.
#ifdef CONFIG_AT_WEB_USE_FATFS
#include "esp_vfs_fat.h"
#include "diskio_wl.h"
#include "diskio_impl.h"
#endif
#ifdef CONFIG_AT_WEB_CAPTIVE_PORTAL_ENABLE
#include "at_web_dns_server.h"
static char *s_at_web_redirect_url = NULL;
#endif
#define ESP_AT_WEB_SERVER_CHECK(a, str, goto_tag, ...) \
do \
{ \
if (!(a)) \
{ \
ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
goto goto_tag; \
} \
} while (0)
#define ESP_AT_WEB_VERSION "1.0"
#define ESP_AT_WEB_FILE_PATH_MAX (ESP_VFS_PATH_MAX + 128)
#define ESP_AT_WEB_SCRATCH_BUFSIZE 320
#define ESP_AT_WEB_WIFI_MAX_RECONNECT_TIMEOUT 60 // 60sec
#define ESP_AT_WEB_WIFI_MIN_RECONNECT_TIMEOUT 21 // 21sec
#define ESP_AT_WEB_MOUNT_POINT "/www"
#define ESP_AT_WEB_TIMER_POLLING_PERIOD 500 // 500ms
#define ESP_AT_WEB_BROADCAST_TIMES_DEFAULT 20 // When connect without ssid, for multicast wifi connect result
#define ESP_AT_WEB_BROADCAST_INTERVAL_DEFAULT 500000 // 500000us
#define ESP_AT_WEB_UDP_PORT_DEFAULT 3339 // When connect without ssid, for multicast wifi connect result
#define ESP_AT_WEB_IPV4_MAX_IP_LEN_DEFAULT 32
#define ESP_AT_WEB_RECEIVED_ACK_MESSAGE "received"
#define ESP_AT_WEB_AP_SCAN_NUM_DEFAULT 10
#define ESP_AT_WEB_AP_RECORD_JSON_STR_LEN 600
#define ESP_AT_WEB_WIFI_CONNECTED_BIT BIT0
#define ESP_AT_WEB_WIFI_FAIL_BIT BIT1
#define ESP_AT_WEB_SCAN_RSSI_THRESHOLD -50
#define ESP_AT_WEB_SCAN_LIST_SIZE 40
#define ESP_AT_WEB_ENABLE_VIRTUAL_MAC_MATCH 1
#define ESP_AT_WEB_ENABLE_CONNECT_HIGHEST_RSSI 1
#define ESP_AT_WEB_HIGH_RSSI_CONNECT_COUNT 1
#define ESP_AT_WEB_WIFI_TRY_CONNECT_TIMEOUT 8000 // try connect timeout is 8000ms
#define ESP_AT_WEB_WIFI_SSID_LEN_DEFAULT 32
#define ESP_AT_WEB_WIFI_LAST_SCAN_TIMEOUT 10 // 10s
#define ESP_AT_WEB_ROOT_DIR_DEFAULT CONFIG_AT_WEB_ROOT_DIR
#define ESP_AT_WEB_REDIRECT_URL_PREFIX_LEN 24
#define ESP_AT_WEB_HEADER_AUTH_NAME ("Object")
#define ESP_AT_UPGRADE_PARTITION_NAME ("ota")
extern void at_wifi_reconnect_stop(void);
extern void at_wifi_reconnect_init(bool force);
extern esp_err_t at_wifi_connect(void);
extern esp_err_t at_wifi_disconnect(void);
extern esp_err_t at_wifi_scan_start(const wifi_scan_config_t *config, bool block);
typedef struct router_obj {
uint8_t ssid[32];
int8_t rssi;
uint8_t mac[6];
SLIST_ENTRY(router_obj) next;
} router_obj_t;
typedef struct web_server_context {
char base_path[ESP_VFS_PATH_MAX + 1];
char scratch[ESP_AT_WEB_SCRATCH_BUFSIZE];
} web_server_context_t;
typedef struct {
uint8_t ssid[33];
uint8_t password[65];
} wifi_sta_connect_config_t;
typedef enum {
ESP_AT_WIFI_STA_NOT_START = 0x0,
ESP_AT_WIFI_STA_CONFIG_DONE = 0x1,
ESP_AT_WIFI_STA_CONNECTING = 0x2,
ESP_AT_WIFI_STA_CONNECT_FAIL = 0x3,
ESP_AT_WIFI_STA_CONNECT_OK = 0x4,
ESP_AT_WIFI_STA_RESULT_CHECKED = 0x5,
} esp_at_web_config_wifi_status;
typedef struct {
esp_at_web_config_wifi_status config_status;
char sta_ip[ESP_AT_WEB_IPV4_MAX_IP_LEN_DEFAULT];
} wifi_sta_connection_info_t;
typedef struct {
int udp_socket;
struct sockaddr_in broadcast_addr;
struct sockaddr_in unicast_addr;
char sendline[64];
char rx_buffer[32];
} udp_broadcast_info_t;
static web_server_context_t *s_web_context = NULL;
static httpd_handle_t s_server = NULL;
static int32_t s_at_web_wifi_reconnect_timeout = ESP_AT_WEB_WIFI_MAX_RECONNECT_TIMEOUT;
static wifi_sta_connection_info_t s_wifi_sta_connection_info = {0};
static wifi_sta_connect_config_t s_wifi_sta_connect_config = {0};
static TimerHandle_t s_wifi_sta_connect_timer_handler = NULL;
static EventGroupHandle_t s_wifi_sta_connect_event_group = NULL;
static uint8_t s_mobile_phone_mac[6] = {0};
static bool s_sta_got_ip_flag = false;
static const char *s_wifi_start_connect_response = "+WEBSERVERRSP:1\r\n";
static const char *s_wifi_conncet_finish_response = "+WEBSERVERRSP:2\r\n";
static const char *s_ota_start_response = "+WEBSERVERRSP:3\r\n";
static const char *s_ota_receive_success_response = "+WEBSERVERRSP:4\r\n";
static const char *s_ota_receive_fail_response = "+WEBSERVERRSP:5\r\n";
static SLIST_HEAD(router_fail_list_head_, router_obj) s_router_fail_list = SLIST_HEAD_INITIALIZER(s_router_fail_list);
static const char *TAG = "at web";
// AT web can use fatfs to storge html or use embeded file to storge html.
// If use fatfs,we should enable AT FS Command support.
#ifdef CONFIG_AT_WEB_USE_FATFS
static wl_handle_t s_wl_handle = WL_INVALID_HANDLE; // Handle of the wear levelling library instance
static BYTE pdrv = 0xFF;
#endif
static uint8_t at_web_get_mac_match_len(uint8_t *mac1, uint8_t *mac2, uint8_t mac_length)
{
uint8_t match_len = 0;
uint8_t i = 0;
if (mac1 == NULL || mac2 == NULL) {
return 0;
}
for (i = 0; i < mac_length; i++) {
if (mac1[i] == mac2[i]) {
match_len++;
}
}
return match_len;
}
/**
* @brief Apply ssid、password、bssid to Wi-Fi config and start connect.
*
* @param[in] ssid - ssid used for Wi-Fi connect config
* @param[in] password - password used for Wi-Fi connect config
* @param[in] bssid - bssid used for Wi-Fi connect config, can be null.
* @param[in] connect_event - the handler of wifi connect status eventgroup, used to feedback try connect result.
*
* @return
* - ESP_OK : success
* - Others : fail
*/
static esp_err_t at_web_try_connect(uint8_t *ssid, uint8_t *password, uint8_t *bssid, EventGroupHandle_t connect_event)
{
esp_err_t ret;
EventBits_t bits;
char temp_ssid[ESP_AT_WEB_WIFI_SSID_LEN_DEFAULT + 1] = {0};
wifi_sta_config_t sta = {0};
memcpy(sta.ssid, ssid, sizeof(sta.ssid));
memcpy(sta.password, password, sizeof(sta.password));
memcpy(temp_ssid, ssid, sizeof(sta.ssid));
if (bssid != NULL) {
sta.bssid_set = 1;
memcpy(sta.bssid, bssid, sizeof(sta.bssid));
}
at_wifi_disconnect();
// stop reconnect if reconnect is in process(if has connected, then keep the connection)
at_wifi_reconnect_stop();
ret = esp_wifi_set_config(ESP_IF_WIFI_STA, (wifi_config_t*) &sta);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "wifi set config fail");
return ret;
}
// force connect to AP
at_wifi_reconnect_init(true);
// apply connect
at_wifi_connect();
if (connect_event != NULL) { // need to wait wifi connect result, now it's phone config wifi and ssid is null
bits = xEventGroupWaitBits(connect_event,
ESP_AT_WEB_WIFI_CONNECTED_BIT | ESP_AT_WEB_WIFI_FAIL_BIT,
pdTRUE,
pdFALSE,
ESP_AT_WEB_WIFI_TRY_CONNECT_TIMEOUT / portTICK_PERIOD_MS); // wait until timeout
if (bits & ESP_AT_WEB_WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s", temp_ssid);
} else if (bits & ESP_AT_WEB_WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "connecting to SSID:%s, reconnect timeout", temp_ssid);
ret = ESP_ERR_TIMEOUT;
} else if (bits == 0) { // timeout expeird
ESP_LOGI(TAG, "try connected to ap SSID:%s timeout", temp_ssid);
ret = ESP_FAIL;
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
ret = ESP_ERR_INVALID_STATE;
}
} else { // don't need to wait wifi connect result
printf("connect config finish\r\n");
}
return ret;
}
/**
* @brief Get mobile phone's mac connecting to the ESP AP
*
* @note If there is more than one station connecting to the ESP AP, the function will return the last station's mac.
*
* @param[out] mobile_phone_mac
*
* @return
* - ESP_OK
* - ESP_FAIL
*/
static esp_err_t at_web_get_mobile_phone_mac(uint8_t *mobile_phone_mac)
{
esp_err_t err;
wifi_sta_list_t sta_list;
err = esp_wifi_ap_get_sta_list(&sta_list);
if ((err != ESP_OK) && (err != ESP_ERR_WIFI_MODE)) {
ESP_LOGE(TAG, "Get sta list fail");
return ESP_FAIL;
} else if (err == ESP_ERR_WIFI_MODE) {
ESP_LOGW(TAG, "WiFi mode is wrong");
return ESP_FAIL;
}
ESP_LOGI(TAG, "Find mobile phone mac is : %02x:%02x:%02x:%02x:%02x:%02x", MAC2STR(sta_list.sta[sta_list.num - 1].mac));
memcpy(mobile_phone_mac, sta_list.sta[sta_list.num - 1].mac, sizeof(sta_list.sta[sta_list.num - 1].mac));
return ESP_OK;
}
// return false means repeat try error connect
static bool check_fail_list(uint8_t *bssid)
{
if (bssid == NULL) {
ESP_LOGI(TAG, "ERROR bssid");
return false;
}
if (!SLIST_EMPTY(&s_router_fail_list)) {
for (router_obj_t *fail_item = SLIST_FIRST(&s_router_fail_list); fail_item != NULL; fail_item = SLIST_NEXT(fail_item, next)) {
if (at_web_get_mac_match_len(fail_item->mac, bssid, sizeof(fail_item->mac)) == 6) {
ESP_LOGI(TAG, "Skip ssid: %s", fail_item->ssid);
return false;
}
}
}
return true;
}
static esp_err_t stop_scan_filter(void)
{
if (!SLIST_EMPTY(&s_router_fail_list)) {
for (router_obj_t *fail_item = SLIST_FIRST(&s_router_fail_list); fail_item != NULL; fail_item = SLIST_NEXT(fail_item, next)) {
SLIST_REMOVE(&s_router_fail_list, fail_item, router_obj, next);
free(fail_item);
}
}
return ESP_OK;
}
static void insert_fail_list(router_obj_t *item)
{
if (item == NULL) {
return;
}
SLIST_INSERT_HEAD(&s_router_fail_list, item, next);
}
static void ap_record_sort_by_rssi(wifi_ap_record_t *ap_record_array, int len)
{
int i, j;
wifi_ap_record_t tmp_ap_record;
int flag;
for (i = 0; i < len - 1; i++) {
flag = 1;
for (j = 0; j < len - i - 1; j++) {
if (ap_record_array[j].rssi < ap_record_array[j + 1].rssi) {
tmp_ap_record = ap_record_array[j];
ap_record_array[j] = ap_record_array[j + 1];
ap_record_array[j + 1] = tmp_ap_record;
flag = 0;
}
}
if (flag == 1) {
break;
}
}
}
/**
* @brief Start one scan, and get AP list found in the scan.
*
* @param[in,out] number As input param, it stores max AP number ap_records can hold.
* As output param, it receives the actual AP number this API returns.
* @param[out] ap_records wifi_ap_record_t array to hold the found APs
*
* @return
* - ESP_OK: succeed
* - ESP_ERR_WIFI_NOT_INIT: WiFi is not initialized by esp_wifi_init
* - ESP_ERR_WIFI_NOT_STARTED: WiFi is not started by esp_wifi_start
* - ESP_ERR_INVALID_ARG: invalid argument
* - ESP_ERR_NO_MEM: out of memory
*/
esp_err_t at_web_wifi_scan_get_ap_records(uint16_t *number, wifi_ap_record_t *ap_records)
{
if ((number == NULL) || (ap_records == NULL)) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t ret = ESP_FAIL;
ret = at_wifi_scan_start(NULL, true);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "scan start fail");
return ret;
}
ret = esp_wifi_scan_get_ap_records(number, ap_records);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "get scan fail");
return ret;
}
if (*number == 0) {
ESP_LOGW(TAG, "There is no ap here");
return ESP_FAIL;
}
ESP_LOGI(TAG, "Total APs scanned = %u", *number);
// sort ap_record according to rssi
ap_record_sort_by_rssi(ap_records, *number);
return ESP_OK;
}
static esp_err_t at_web_check_ap_info(wifi_ap_record_t *ap_info)
{
if (ap_info == NULL) {
return ESP_ERR_INVALID_ARG;
}
// check ssid
if (strlen((const char *)ap_info->ssid) == 0) {
ESP_LOGD(TAG, "Ignore hidden ssid");
goto check_err;
}
// check encryption
if (ap_info->authmode > 5) {
ESP_LOGD(TAG, "error encryption %d, ssid: %s", ap_info->authmode, ap_info->ssid);
goto check_err;
}
if (ap_info->authmode < 2) {
ESP_LOGD(TAG, "Ignore unsafe password, ssid: %s", ap_info->ssid);
goto check_err;
}
// check rssi
if (ap_info->rssi < ESP_AT_WEB_SCAN_RSSI_THRESHOLD) {
ESP_LOGD(TAG, "Ignore low rssi, ssid: %s", ap_info->ssid);
goto check_err;
}
return ESP_OK;
check_err:
return ESP_FAIL;
}
/**
* @brief Start Wi-Fi scan and try connect
*
* @param[in] phone_mac - the mac of phone which post the connect data.
* @param[in] password - web server received Wi-Fi connect password.
* @param[in] max_connect_time - the Max connection time allowed to attempt(include scan delay and try connect time, unit: s).
* @param[in] connect_event - the handler of wifi connect status eventgroup, used to feedback try connect result.
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG
* - ESP_FAIL
*/
static esp_err_t at_web_start_scan_filter(uint8_t *phone_mac, uint8_t *password, int32_t max_connect_time, EventGroupHandle_t connect_event)
{
esp_err_t ret = ESP_FAIL;
router_obj_t *last = NULL;
uint8_t try_connect_count = 0;
// Calculate the max number of try to connect
uint8_t max_try_connect_num = (max_connect_time * 1000) / ESP_AT_WEB_WIFI_TRY_CONNECT_TIMEOUT;
int32_t current_available_time = max_connect_time;
uint16_t ap_scan_number = 0;
int32_t loop = 0;
bool last_scan = false;
uint64_t start = 0;
uint64_t end = 0;
router_obj_t *item = NULL;
router_obj_t *head_item = NULL;
wifi_ap_record_t *ap_info = NULL;
uint8_t highest_rssi_connect_count = 0;
static uint8_t s_connect_success_flag = 0;
SLIST_HEAD(router_all_list_head_, router_obj) s_router_all_list = SLIST_HEAD_INITIALIZER(s_router_all_list);
if ((password == NULL) || (max_connect_time <= 0)) {
return ESP_ERR_INVALID_ARG;
}
ap_info = (wifi_ap_record_t*) malloc(ESP_AT_WEB_SCAN_LIST_SIZE * sizeof(wifi_ap_record_t));
if (ap_info == NULL) {
ESP_LOGE(TAG, "ap info malloc fail");
return ESP_ERR_NO_MEM;
}
ESP_LOGD(TAG, "max connect time is %d", max_connect_time);
while (max_try_connect_num > 0) {
start = clock();
if (current_available_time <= ESP_AT_WEB_WIFI_LAST_SCAN_TIMEOUT) {
last_scan = true;
}
// clear the value of the variable
try_connect_count = 0;
ap_scan_number = ESP_AT_WEB_SCAN_LIST_SIZE;
memset(ap_info, 0, ESP_AT_WEB_SCAN_LIST_SIZE * sizeof(wifi_ap_record_t));
ret = at_web_wifi_scan_get_ap_records(&ap_scan_number, ap_info);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "get scan fail");
goto err;
}
for (loop = 0; loop < ap_scan_number; loop++) {
if (at_web_check_ap_info(&ap_info[loop]) != ESP_OK) {
continue;
}
item = (router_obj_t*) malloc(sizeof(router_obj_t));
if (item == NULL) {
ESP_LOGE(TAG, "router malloc fail");
break;
}
memset(item, 0x0, sizeof(router_obj_t));
// copy mac
memcpy(item->mac, ap_info[loop].bssid, sizeof(item->mac));
// copy ssid
memcpy(item->ssid, ap_info[loop].ssid, sizeof(item->ssid));
// cpoy rssi
item->rssi = ap_info[loop].rssi;
if (last == NULL) {
SLIST_INSERT_HEAD(&s_router_all_list, item, next);
} else {
SLIST_INSERT_AFTER(last, item, next);
}
last = item;
}
if (SLIST_EMPTY(&s_router_all_list)) {
ESP_LOGE(TAG, "Not find router");
goto err;
}
// If have mobile phone mac, first consider connect the router which has similar mac
if (phone_mac != NULL) {
ESP_LOGI(TAG, "Try to macth MAC");
for (item = SLIST_FIRST(&s_router_all_list); (item != NULL) && (try_connect_count < max_try_connect_num); item = SLIST_NEXT(item, next)) {
// some phone(like XIAOMI10), the difference between SOFTAP and STA is two bytes
if ((at_web_get_mac_match_len(phone_mac, item->mac, sizeof(item->mac)) >= 5)) {
if (check_fail_list(item->mac)) { // ignore fail connect mac
try_connect_count++;
ret = at_web_try_connect(item->ssid, password, item->mac, connect_event);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "match mac connect error");
SLIST_REMOVE(&s_router_all_list, item, router_obj, next);
insert_fail_list(item);
} else {
s_connect_success_flag = 1;
}
break; // find ssid, skip search
}
}
}
if (item == NULL) {
ESP_LOGI(TAG, "No macth MAC");
}
}
#if ESP_AT_WEB_ENABLE_VIRTUAL_MAC_MATCH
// if connect fail, try to connect the router which has virtual mac
if (last_scan == true && s_connect_success_flag == 0 && try_connect_count < max_try_connect_num) {
ESP_LOGI(TAG, "Try to connect router through virtual MAC");
for (item = SLIST_FIRST(&s_router_all_list); (item != NULL) && (try_connect_count < max_try_connect_num); item = SLIST_NEXT(item, next)) {
if ((item->mac[1]) & 0x2) { // Compare the first byte
ESP_LOGI(TAG, "Find a virtual MAC: %02x:%02x:%02x:%02x:%02x:%02x, ssid: %s", MAC2STR(item->mac), item->ssid);
if (check_fail_list(item->mac)) {
try_connect_count++;
ret = at_web_try_connect(item->ssid, password, item->mac, connect_event);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "virtual mac connect error");
SLIST_REMOVE(&s_router_all_list, item, router_obj, next);
insert_fail_list(item);
} else {
s_connect_success_flag = 1;
}
break; // find ssid, skip seach
}
}
}
if (item == NULL) {
ESP_LOGI(TAG, "No virtual MAC");
}
}
#endif
#if ESP_AT_WEB_ENABLE_CONNECT_HIGHEST_RSSI
// if connect fail, try to connect the router which has highest rssi
if (last_scan == true && s_connect_success_flag == 0 && try_connect_count < max_try_connect_num) {
ESP_LOGI(TAG, "Try to connect highest RSSI SSID");
head_item = SLIST_FIRST(&s_router_all_list);
highest_rssi_connect_count = ESP_AT_WEB_HIGH_RSSI_CONNECT_COUNT;
while ((highest_rssi_connect_count > 0) && (try_connect_count < max_try_connect_num)) {
if (check_fail_list(head_item->mac)) {
ESP_LOGI(TAG, "Try to connect highest rssi ssid %s, rssi: %d", head_item->ssid, head_item->rssi);
try_connect_count++;
ret = at_web_try_connect(head_item->ssid, password, head_item->mac, connect_event);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "rssi connect error");
SLIST_REMOVE(&s_router_all_list, head_item, router_obj, next);
insert_fail_list(head_item);
} else {
s_connect_success_flag = 1;
break;
}
}
highest_rssi_connect_count--;
head_item = SLIST_NEXT(head_item, next);
if (head_item == NULL) {
ESP_LOGE(TAG, "List is NULL");
break;
}
}
}
#endif
// delete scan list
for (item = SLIST_FIRST(&s_router_all_list); item != NULL; item = SLIST_NEXT(item, next)) {
ESP_LOGD(TAG, "Delete SSID:%s", item->ssid);
SLIST_REMOVE(&s_router_all_list, item, router_obj, next);
free(item);
}
if (s_connect_success_flag) {
s_connect_success_flag = 0;
free(ap_info);
ap_info = NULL;
stop_scan_filter();
ESP_LOGI(TAG, "try connect count is %d", try_connect_count);
return ESP_OK;
}
last = NULL;
end = clock();
if (end > start) {
ESP_LOGI(TAG, "scan and try connect use time is %d", (int32_t)((end - start) / 1000));
current_available_time -= ((end - start) / 1000);
} else if (end < start) {
ESP_LOGI(TAG, "scan and try connect use time is %d", (int32_t)((end + 0xFFFFFFFFUL - start) / 1000));
current_available_time -= ((end + 0xFFFFFFFFUL - start) / 1000);
} else {
ESP_LOGE(TAG, "time interval fatal error");
break;
}
if ((current_available_time <= 0) || (current_available_time > max_connect_time)) {
break;
} else {
max_try_connect_num = (current_available_time * 1000) / ESP_AT_WEB_WIFI_TRY_CONNECT_TIMEOUT;
ESP_LOGI(TAG, "current avail time is %d, max_try_connect_num is %d", current_available_time, max_try_connect_num);
}
}
if (ap_info != NULL) {
free(ap_info);
ap_info = NULL;
}
// delete fail connect list
stop_scan_filter();
ESP_LOGW(TAG, "scan filter timeout");
return ESP_FAIL;
err:
free(ap_info);
ap_info = NULL;
// delete fail connect list
stop_scan_filter();
ESP_LOGW(TAG, "scan filter error");
return ESP_FAIL;
}
static int at_web_url_decode(char *src, int src_len, char *des, int des_len)
{
int len = MIN(src_len, des_len);
memset(des, 0, des_len);
memcpy(des, src, len);
return len;
}
/**
* @brief Find a specific arg in a string of get- or post-data.
* Line is the string of post/get-data, arg is the name of the value to find. The
* zero-terminated result is written in buff, with at most buffLen bytes used. The
* function returns the length of the result, or -1 if the value wasn't found. The
* returned string will be urldecoded already.
*
*/
static int at_web_find_arg(char *line, char *arg, char *buff, int buffLen)
{
char *p, *e;
if (line == NULL) {
return -1;
}
p = line;
while (p != NULL && *p != '\n' && *p != '\r' && *p != 0) {
if (strncmp(p, arg, strlen(arg)) == 0 && p[strlen(arg)] == '=') {
p += strlen(arg) + 1; // move p to start of value
e = (char*) strstr(p, "&");
if (e == NULL) {
e = p + strlen(p);
}
return at_web_url_decode(p, (e - p), buff, buffLen);
}
p = (char*) strstr(p, "&");
if (p != NULL) {
p += 1;
}
}
ESP_LOGD(TAG, "Finding %s in %s: Not found :/\n", arg, line);
return -1; // not found
}
// AT web can use fatfs to storge html or use embeded file to storge html.
// If use fatfs,we should enable AT FS Command support.
#ifdef CONFIG_AT_WEB_USE_FATFS
/* Send HTTP response with the contents of the requested file */
static esp_err_t web_common_get_handler(httpd_req_t *req)
{
char filepath[ESP_AT_WEB_FILE_PATH_MAX];
esp_err_t err = ESP_FAIL;
web_server_context_t *s_web_context = (web_server_context_t*) req->user_ctx;
strlcpy(filepath, s_web_context->base_path, sizeof(filepath));
strlcat(filepath, "/index.html", sizeof(filepath)); // Now, we just send the index html for the common handler
ESP_LOGW(TAG, "open file : %s", filepath);
int fd = open(filepath, O_RDONLY);
if (fd == -1) {
ESP_LOGE(TAG, "Failed to open file : %s, errno =%d", filepath, errno);
/*Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read existing file");
return ESP_FAIL;
}
httpd_resp_set_type(req, "text/html");
char *chunk = s_web_context->scratch;
ssize_t read_bytes;
do {
/* Read file in chunks into the scratch buffer */
read_bytes = read(fd, chunk, ESP_AT_WEB_SCRATCH_BUFSIZE);
if (read_bytes == -1) {
ESP_LOGE(TAG, "Failed to read file : %s", filepath);
} else if (read_bytes > 0) {
/* Send the buffer contents as HTTP response chunk */
err = httpd_resp_send_chunk(req, chunk, read_bytes);
if (err != ESP_OK) {
close(fd);
ESP_LOGE(TAG, "File sending failed!,err: %d,read_bytes: %d", err, read_bytes);
/* Abort sending file */
httpd_resp_sendstr_chunk(req, NULL);
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
return ESP_FAIL;
}
}
} while (read_bytes > 0);
/* Close file after sending complete */
close(fd);
ESP_LOGD(TAG, "File sending complete");
/* Respond with an empty chunk to signal HTTP response completion */
httpd_resp_send_chunk(req, NULL, 0);
return ESP_OK;
}
#else
static esp_err_t index_html_get_handler(httpd_req_t *req)
{
extern const char html_start[] asm("_binary_index_html_start");
extern const char html_end[] asm("_binary_index_html_end");
const size_t html_size = (html_end - html_start);
httpd_resp_set_type(req, "text/html");
/* Add file upload form and script which on execution sends a POST request to /upload */
httpd_resp_send_chunk(req, (const char*) html_start, html_size);
/* Respond with an empty chunk to signal HTTP response completion */
return httpd_resp_send_chunk(req, NULL, 0);
}
/* Send HTTP response with the contents of the requested file */
static esp_err_t web_common_get_handler(httpd_req_t *req)
{
return index_html_get_handler(req);
return ESP_OK;
}
#endif
/* A help function to get post request data */
static esp_err_t recv_post_data(httpd_req_t *req, char *buf)
{
int total_len = req->content_len;
int cur_len = 0;
int received = 0;
if (total_len >= ESP_AT_WEB_SCRATCH_BUFSIZE) {
/* Respond with 500 Internal Server Error */
httpd_resp_send_500(req);
ESP_LOGE(TAG, "context too long");
return ESP_FAIL;
}
while (cur_len < total_len) {
received = httpd_req_recv(req, buf + cur_len, total_len);
if (received <= 0) {
/* Respond with 500 Internal Server Error */
httpd_resp_send_500(req);
ESP_LOGE(TAG, "Failed to post control value");
return ESP_FAIL;
}
cur_len += received;
}
buf[total_len] = '\0'; // now ,the post is str format, like ssid=yuxin&pwd=TestPWD&chl=1&ecn=0&maxconn=1&ssidhidden=0
ESP_LOGI(TAG, "Post data is : %s\n", buf);
return ESP_OK;
}
static void at_web_response_ok(httpd_req_t *req)
{
const char *temp_str = "{\"state\": 0}";
httpd_resp_set_type(req, HTTPD_TYPE_JSON);
httpd_resp_set_status(req, HTTPD_200);
httpd_resp_send(req, temp_str, strlen(temp_str));
}
static void at_web_response_error(httpd_req_t *req, const char *status)
{
const char *temp_str = "{\"state\": 1}";
httpd_resp_set_type(req, HTTPD_TYPE_JSON);
httpd_resp_set_status(req, status);
httpd_resp_send(req, temp_str, strlen(temp_str));
}
static void at_web_update_sta_connect_config(wifi_sta_connect_config_t *connect_conf)
{
memcpy(&s_wifi_sta_connect_config, connect_conf, sizeof(wifi_sta_connect_config_t));
}
static wifi_sta_connect_config_t *at_web_get_sta_connect_config(void)
{
return &s_wifi_sta_connect_config;
}
static void at_web_clear_sta_connect_config(void)
{
memset(&s_wifi_sta_connect_config, 0x0, sizeof(wifi_sta_connect_config_t));
}
static void at_web_update_sta_connection_info(wifi_sta_connection_info_t *connection_info)
{
memcpy(&s_wifi_sta_connection_info, connection_info, sizeof(wifi_sta_connection_info_t));
}
static wifi_sta_connection_info_t *at_web_get_sta_connection_info(void)
{
return &s_wifi_sta_connection_info;
}
static void at_web_update_sta_reconnect_timeout(int32_t timeout)
{
s_at_web_wifi_reconnect_timeout = timeout;
}
static int32_t at_web_get_sta_reconnect_timeout(void)
{
return s_at_web_wifi_reconnect_timeout;
}
void at_web_update_sta_got_ip_flag(bool flag)
{
s_sta_got_ip_flag = flag;
}
static bool at_web_get_sta_got_ip_flag(void)
{
return s_sta_got_ip_flag;
}
static void listen_sta_connect_status_timer_cb(TimerHandle_t timer)
{
wifi_sta_connection_info_t connection_info = {0};
int32_t reconnnect_timeout = at_web_get_sta_reconnect_timeout();
int32_t connect_max_count = (reconnnect_timeout * 1000) / ESP_AT_WEB_TIMER_POLLING_PERIOD;
esp_err_t ret = ESP_FAIL;
bool sta_got_ip = false;
static int connect_count = 1;
ESP_LOGD(TAG, "Connect callback timer %p count = %d", timer, connect_count);
esp_netif_ip_info_t sta_ip = {0};
esp_netif_t *sta_if = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (connect_count < connect_max_count) {
sta_got_ip = at_web_get_sta_got_ip_flag(); // to check whether sta has connnected to appointed ap(like at_wifi_station_get_connect_status())
if (sta_got_ip != true) {
connect_count++;
return;
} else {
ESP_LOGI(TAG, "Connect SSID success");
ret = esp_netif_get_ip_info(sta_if, &sta_ip);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "get sta ip fail");
}
ESP_LOGI(TAG, "Congratulate, got ip:" IPSTR, IP2STR(&sta_ip.ip));
sprintf(connection_info.sta_ip, IPSTR, IP2STR(&sta_ip.ip));
connection_info.config_status = ESP_AT_WIFI_STA_CONNECT_OK;
goto connect_finish;
}
} else {
ESP_LOGW(TAG, "Listen connect %d times and connect fail", connect_count);
connection_info.config_status = ESP_AT_WIFI_STA_CONNECT_FAIL;
at_wifi_reconnect_stop();
goto connect_finish;
}
return;
connect_finish:
connect_count = 1;
at_web_update_sta_got_ip_flag(false);
at_web_update_sta_connection_info(&connection_info);
xTimerStop(s_wifi_sta_connect_timer_handler, portMAX_DELAY);
xTimerDelete(s_wifi_sta_connect_timer_handler, portMAX_DELAY);
}
static int udp_create(uint16_t port, char *bind_ip)
{
struct sockaddr_in6 dest_addr = {0};
struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in*) &dest_addr;
int ip_protocol = 0;
const int on = 1;
inet_aton(bind_ip, &dest_addr_ip4->sin_addr.s_addr); // bind sta ip
dest_addr_ip4->sin_family = AF_INET;
dest_addr_ip4->sin_port = htons(port);
ip_protocol = IPPROTO_IP;
int sock = socket(AF_INET, SOCK_DGRAM, ip_protocol);
if (sock < 0) {
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
return -1;
}
int err = bind(sock, (struct sockaddr*) &dest_addr, sizeof(dest_addr));
if (err < 0) {
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
shutdown(sock, 0);
close(sock);
return -1;
}
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) {
ESP_LOGE(TAG, "reuse addr fail");
close(sock);
return -1;
}
if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) != 0) {
ESP_LOGE(TAG, "broadcast enable fail");
close(sock);
return -1;
}
return sock;
}
static int readable_check(int fd, int sec, int usec)
{
fd_set rset;
struct timeval tv;
FD_ZERO(&rset);
FD_SET(fd, &rset);
tv.tv_sec = sec;
tv.tv_usec = usec;
/* > 0 if descriptor is readable */
return (select(fd + 1, &rset, NULL, NULL, &tv));
}
static void listen_sta_connect_success_timer_cb(TimerHandle_t timer)
{
bool sta_got_ip = false;
wifi_sta_connection_info_t *current_connection_info = at_web_get_sta_connection_info();
if (current_connection_info->config_status == ESP_AT_WIFI_STA_CONNECTING) {
sta_got_ip = at_web_get_sta_got_ip_flag(); // to check whether sta has connnected to appointed ap(like at_wifi_station_get_connect_status())
if (sta_got_ip != true) {
return;
} else {
ESP_LOGI(TAG, "Connect SSID success");
at_web_update_sta_got_ip_flag(false); // clear connect status flag
xEventGroupSetBits(s_wifi_sta_connect_event_group, ESP_AT_WEB_WIFI_CONNECTED_BIT);
return;
}
}
}
/**
* @brief Apply WiFi connect info to try connect
*
* @param[in] udp_port: indicates the device in use, -1: brower, others: WeChat;
* when Wechat is in use and ssid is null, we use udp send wifi connect result.
*
* @return
* - ESP_OK: Success
* - Others: Fail
*/
static esp_err_t at_web_apply_wifi_connect_info(int32_t udp_port)
{
esp_err_t ret;
wifi_sta_connect_config_t *connect_config = at_web_get_sta_connect_config();
int32_t reconnnect_timeout = at_web_get_sta_reconnect_timeout();
wifi_sta_connection_info_t connection_info = {0};
int send_count = ESP_AT_WEB_BROADCAST_TIMES_DEFAULT;
int udp_socket = -1;
int len = 0;
char gateway[ESP_AT_WEB_IPV4_MAX_IP_LEN_DEFAULT] = {0};
struct sockaddr_in broadcast_addr = {0};
struct sockaddr_in unicast_addr = {0};
char sendline[64] = {0};
char rx_buffer[32] = {0};
// Calculate the max connect time,unit: s
int32_t connect_timeout = reconnnect_timeout - ESP_AT_WEB_BROADCAST_TIMES_DEFAULT * ESP_AT_WEB_BROADCAST_INTERVAL_DEFAULT / 1000000;
// Clear connect status flag
at_web_update_sta_got_ip_flag(false);
// According to config wifi device to try connect
// when udp_port == -1, it's web browser post data to config wifi. otherwise, Now, It's WeChat post data to config wifi.
// when (strlen((char *)connect_config->ssid) == 0) && (udp_port != -1), it's WeChat post data, and target AP is local phone.
if ((strlen((char *)connect_config->ssid) != 0) || (udp_port == -1)) {
ESP_LOGI(TAG, "Use SSID %s direct connect", (char *)connect_config->ssid);
ret = at_web_try_connect(connect_config->ssid, connect_config->password, NULL, NULL);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Apply connect fail");
goto err;
} else {
ESP_LOGI(TAG, "Apply connect success");