SP32開發之路(7)—ESP32作爲TCP客戶端連接到局域網的PC機
本次開發是在Ubuntu下的,使用的模塊是GOOUUU-ESP32,使用VSCode編輯項目。代碼使用來自esp-idf的例程。
一、代碼準備
從上個工程:ESP32開發之路(6)—連接到WiFi然後保存ssid和password,將其中的代碼封裝成一個wifi_connect_init()
函數,新建app_wifi.c
文件,在app_main.c
代碼的基礎上,將app_main()
函數修改爲wifi_connect_init()
函數:
void wifi_connect_init(void)
{
/* 定義一個gpio配置結構體 初始化LED */
gpio_config_t gpio_config_structure;
/* 初始化gpio配置結構體*/
gpio_config_structure.pin_bit_mask = (1ULL << GPIO_LED_NUM);/* 選擇gpio2 */
gpio_config_structure.mode = GPIO_MODE_OUTPUT; /* 輸出模式 */
gpio_config_structure.pull_up_en = 0; /* 不上拉 */
gpio_config_structure.pull_down_en = 0; /* 不下拉 */
gpio_config_structure.intr_type = GPIO_PIN_INTR_DISABLE; /* 禁止中斷 */
/* 根據設定參數初始化並使能 */
gpio_config(&gpio_config_structure);
/* 默認熄滅LED */
gpio_set_level(GPIO_LED_NUM, 0); /* 熄滅 */
/* 初始化非易失性存儲庫 (NVS) */
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
/* NVS分區被截斷,需要刪除,然後重新初始化NVS */
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK( err );
/* 定義一個NVS操作句柄 */
nvs_handle wificfg_nvs_handler;
/* 打開一個NVS命名空間 */
ESP_ERROR_CHECK( nvs_open("WiFi_cfg", NVS_READWRITE, &wificfg_nvs_handler) );
uint32_t wifi_update = 0;
err = nvs_get_u32(wificfg_nvs_handler,"wifi_update",&wifi_update);
if(MY_WIFI_UPDATE == wifi_update )
printf("wifi_cfg needn't to update. \n");
else
{
printf("wifi_cfg update now... \n");
ESP_ERROR_CHECK( nvs_set_str(wificfg_nvs_handler,"wifi_ssid",MY_WIFI_SSID) );
ESP_ERROR_CHECK( nvs_set_str(wificfg_nvs_handler,"wifi_passwd",MY_WIFI_PASSWD) );
ESP_ERROR_CHECK( nvs_set_u32(wificfg_nvs_handler,"wifi_update",MY_WIFI_UPDATE) );
printf("wifi_cfg update ok. \n");
}
ESP_ERROR_CHECK( nvs_commit(wificfg_nvs_handler) ); /* 提交 */
nvs_close(wificfg_nvs_handler); /* 關閉 */
printf("ESP_WIFI_MODE_STA \n");
/* 初始化爲wifi STA模式連接 */
wifi_init_sta();
}
二、TCP連接
新建一個任務,作爲tcp_client連接任務
xTaskCreate(tcp_client_task, "tcp_client", 4096, NULL, 5, NULL);
使用socket()函數打開一個socket文件,並返回其socket描述符
/* 打開一個socket套接字 */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
如果打開成功,則連接到服務端
if (-1 == sockfd)
{
printf("socket open failure !!! \n");
return -1;
}
else
{
struct sockaddr_in seraddr = {0};
seraddr.sin_family = AF_INET; // 設置地址族爲IPv4
seraddr.sin_port = htons(12341); // 設置地址的端口號信息
seraddr.sin_addr.s_addr = inet_addr("192.168.1.107"); // 設置IP地址
ret = connect(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if(ret < 0)
printf("connect to server failure !!! \n");
else
{
printf("connect success, ret = %d.\n", ret);
}
close(sockfd);
}
在PC上打開網絡調試助手,新建一個服務端,可以看到,連接成功。
這裏要特別注意,要把windows10的防火牆關了,否則連接不上,重要的事說三編!!!
這裏要特別注意,要把windows10的防火牆關了,否則連接不上,重要的事說三編!!!
這裏要特別注意,要把windows10的防火牆關了,否則連接不上,重要的事說三編!!!
我因爲這個防火牆的原因,折騰了一天才發現這個問題,說起來都是淚。。。
連接成功後我們每隔2s向服務端發送一條消息,發送10次後關閉;
int cnt = 10;
while(cnt--)
{
ret = send(sockfd, "I am ESP32.", strlen("I am ESP32."), 0);
if(ret < 0)
printf("send err. \n");
else
printf("send ok. \n");
vTaskDelay(2000 / portTICK_PERIOD_MS); /* 延時2000ms*/
}
燒錄下載後,可以看到,服務端收到消息,ESP32每隔2s發送消息成功
三、代碼
最後,貼上app_wifi.c和app_main.c的代碼
app_wifi.c
:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "esp_wifi.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "esp_smartconfig.h"
#define GPIO_LED_NUM 2 /* LED引腳編號 */
/* 宏定義WiFi更新標識碼、WiFi名稱和密碼 */
#define MY_WIFI_UPDATE 4096 /* 對數值進行修改表示更新NVS的WiFi名稱和密碼*/
#define MY_WIFI_SSID "WiFi-William"
#define MY_WIFI_PASSWD "passwd-william"
/* 宏定義WiFi連接事件標誌位、連接失敗標誌位及智能配網標誌位 */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
#define SMART_CONFIG_BIT BIT2
/* 定義一個WiFi連接事件標誌組句柄 */
static EventGroupHandle_t wifi_event_group_handler;
static void wifi_init_sta(void);
void wifi_connect_init(void)
{
/* 定義一個gpio配置結構體 初始化LED */
gpio_config_t gpio_config_structure;
/* 初始化gpio配置結構體*/
gpio_config_structure.pin_bit_mask = (1ULL << GPIO_LED_NUM);/* 選擇gpio2 */
gpio_config_structure.mode = GPIO_MODE_OUTPUT; /* 輸出模式 */
gpio_config_structure.pull_up_en = 0; /* 不上拉 */
gpio_config_structure.pull_down_en = 0; /* 不下拉 */
gpio_config_structure.intr_type = GPIO_PIN_INTR_DISABLE; /* 禁止中斷 */
/* 根據設定參數初始化並使能 */
gpio_config(&gpio_config_structure);
/* 默認熄滅LED */
gpio_set_level(GPIO_LED_NUM, 0); /* 熄滅 */
/* 初始化非易失性存儲庫 (NVS) */
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
/* NVS分區被截斷,需要刪除,然後重新初始化NVS */
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK( err );
/* 定義一個NVS操作句柄 */
nvs_handle wificfg_nvs_handler;
/* 打開一個NVS命名空間 */
ESP_ERROR_CHECK( nvs_open("WiFi_cfg", NVS_READWRITE, &wificfg_nvs_handler) );
uint32_t wifi_update = 0;
err = nvs_get_u32(wificfg_nvs_handler,"wifi_update",&wifi_update);
if(MY_WIFI_UPDATE == wifi_update )
printf("wifi_cfg needn't to update. \n");
else
{
printf("wifi_cfg update now... \n");
ESP_ERROR_CHECK( nvs_set_str(wificfg_nvs_handler,"wifi_ssid",MY_WIFI_SSID) );
ESP_ERROR_CHECK( nvs_set_str(wificfg_nvs_handler,"wifi_passwd",MY_WIFI_PASSWD) );
ESP_ERROR_CHECK( nvs_set_u32(wificfg_nvs_handler,"wifi_update",MY_WIFI_UPDATE) );
printf("wifi_cfg update ok. \n");
}
ESP_ERROR_CHECK( nvs_commit(wificfg_nvs_handler) ); /* 提交 */
nvs_close(wificfg_nvs_handler); /* 關閉 */
printf("ESP_WIFI_MODE_STA \n");
/* 初始化爲wifi STA模式連接 */
wifi_init_sta();
}
/* 系統事件循環處理函數 */
static void event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
{
static int retry_num = 0; /* 記錄wifi重連次數 */
/* 系統事件爲WiFi事件 */
if (event_base == WIFI_EVENT)
{
if(event_id == WIFI_EVENT_STA_START) /* 事件id爲STA開始 */
esp_wifi_connect();
else if (event_id == WIFI_EVENT_STA_DISCONNECTED) /* 事件id爲失去STA連接 */
{
esp_wifi_connect();
retry_num++;
printf("retry to connect to the AP %d times. \n",retry_num);
if (retry_num > 10) /* WiFi重連次數大於10 */
{
/* 將WiFi連接事件標誌組的WiFi連接失敗事件位置1 */
xEventGroupSetBits(wifi_event_group_handler, WIFI_FAIL_BIT);
}
/* 清除WiFi連接成功標誌位 */
xEventGroupClearBits(wifi_event_group_handler, WIFI_CONNECTED_BIT);
}
}
/* 系統事件爲ip地址事件,且事件id爲成功獲取ip地址 */
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; /* 獲取IP地址信息*/
printf("got ip:%d.%d.%d.%d \n" , IP2STR(&event->ip_info.ip)); /* 打印ip地址*/
retry_num = 0; /* WiFi重連次數清零 */
/* 將WiFi連接事件標誌組的WiFi連接成功事件位置1 */
xEventGroupSetBits(wifi_event_group_handler, WIFI_CONNECTED_BIT);
}
/* 系統事件爲智能配網事件 */
else if (event_base == SC_EVENT)
{
if(event_id == SC_EVENT_SCAN_DONE ) /* 開始掃描智能配網設備端 */
printf("Scan done\n");
else if(event_id == SC_EVENT_FOUND_CHANNEL) /* 得到了智能配網通道 */
printf("Found channel \n");
else if(event_id == SC_EVENT_GOT_SSID_PSWD) /* 得到了智能配網設備提供的ssid和password */
{
printf("smartconfig got SSID and password\n");
/* 獲取智能配網設備端提供的數據信息 */
smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data;
/* 定義WiFi配置結構體和用了結收ssid和password的數組 */
wifi_config_t wifi_config;
char ssid[32] = { 0 };
char password[64] = { 0 };
bzero(&wifi_config, sizeof(wifi_config_t)); /* 將結構體數據清零 */
/* 將智能配網設備發送來的WiFi的ssid、password及MAC地址複製到wifi_config */
memcpy(wifi_config.sta.ssid, evt->ssid, sizeof(wifi_config.sta.ssid));
memcpy(wifi_config.sta.password, evt->password, sizeof(wifi_config.sta.password));
wifi_config.sta.bssid_set = evt->bssid_set;
if (wifi_config.sta.bssid_set == true) {
memcpy(wifi_config.sta.bssid, evt->bssid, sizeof(wifi_config.sta.bssid));
}
/* 打印WiFi名稱和密碼 */
memcpy(ssid, evt->ssid, sizeof(evt->ssid));
memcpy(password, evt->password, sizeof(evt->password));
printf("SSID:%s \n", ssid);
printf("PASSWORD:%s \n", password);
/* 將得到的WiFi名稱和密碼存入NVS*/
nvs_handle wificfg_nvs_handler;
ESP_ERROR_CHECK( nvs_open("WiFi_cfg", NVS_READWRITE, &wificfg_nvs_handler) );
ESP_ERROR_CHECK( nvs_set_str(wificfg_nvs_handler,"wifi_ssid",ssid) );
ESP_ERROR_CHECK( nvs_set_str(wificfg_nvs_handler,"wifi_passwd",password) );
ESP_ERROR_CHECK( nvs_commit(wificfg_nvs_handler) ); /* 提交 */
nvs_close(wificfg_nvs_handler); /* 關閉 */
printf("smartconfig save wifi_cfg to NVS .\n");
/* 根據得到的WiFi名稱和密碼連接WiFi*/
ESP_ERROR_CHECK( esp_wifi_disconnect() );
ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
ESP_ERROR_CHECK( esp_wifi_connect() );
}
else if (event_id == SC_EVENT_SEND_ACK_DONE) /* 智能配網成功,已經給智能配網設備發送應答*/
{
xEventGroupSetBits(wifi_event_group_handler, SMART_CONFIG_BIT);
}
}
}
static void smartconfig_init_start(void)
{
/* 設置智能配網類型爲 AirKiss */
ESP_ERROR_CHECK( esp_smartconfig_set_type(SC_TYPE_AIRKISS) );
/* 通過SMARTCONFIG_START_CONFIG_DEFAULT宏 來獲取一個默認的smartconfig配置參數結構體變量*/
smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
/* 開始智能配網 */
ESP_ERROR_CHECK( esp_smartconfig_start(&cfg) );
printf("smartconfig start ....... \n");
/* 開啓智能配網後讓LED亮起 */
gpio_set_level(GPIO_LED_NUM, 1); /* 點亮 */
/* 使用事件標誌組等待連接建立(WIFI_CONNECTED_BIT)或連接失敗(WIFI_FAIL_BIT)事件 */
EventBits_t uxBits; /* 定義一個事件位變量來接收事件標誌組等待函數的返回值 */
/* 等待事件標誌組,退出前清除設置的事件標誌,任意置1就會返回*/
uxBits = xEventGroupWaitBits(wifi_event_group_handler, WIFI_CONNECTED_BIT | SMART_CONFIG_BIT,
true, false, portMAX_DELAY);
if(uxBits & WIFI_CONNECTED_BIT)
{
printf("WiFi Connected to ap ok. \n");
}
if(uxBits & SMART_CONFIG_BIT)
{
printf("smartconfig over \n");
}
esp_smartconfig_stop(); /* 關閉智能配網 */
/* 將smartconfig事件從系統默認事件循環中卸載 */
ESP_ERROR_CHECK(esp_event_handler_unregister(SC_EVENT, ESP_EVENT_ANY_ID, &event_handler));
}
static void wifi_init_sta(void)
{
/* 創建一個事件標誌組 */
wifi_event_group_handler = xEventGroupCreate();
/* 初始化底層TCP/IP堆棧。在應用程序啓動時,應該調用此函數一次。*/
ESP_ERROR_CHECK(esp_netif_init());
/* 創建默認事件循環,*/
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* 創建一個默認的WIFI-STA網絡接口,如果初始化錯誤,此API將中止。*/
esp_netif_create_default_wifi_sta();
/* 使用WIFI_INIT_CONFIG_DEFAULT() 來獲取一個默認的wifi配置參數結構體變量*/
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
/* 根據cfg參數初始化wifi連接所需要的資源 */
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
/* 將事件處理程序註冊到系統默認事件循環,分別是WiFi事件、IP地址事件及smartconfig事件 */
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
nvs_handle wificfg_nvs_handler; /* 定義一個NVS操作句柄 */
char wifi_ssid[32] = { 0 }; /* 定義一個數組用來存儲ssid*/
char wifi_passwd[64] = { 0 }; /* 定義一個數組用來存儲passwd */
size_t len;
/* 打開一個NVS命名空間 */
ESP_ERROR_CHECK( nvs_open("WiFi_cfg", NVS_READWRITE, &wificfg_nvs_handler) );
len = sizeof(wifi_ssid); /* 從NVS中獲取ssid */
ESP_ERROR_CHECK( nvs_get_str(wificfg_nvs_handler,"wifi_ssid",wifi_ssid,&len) );
len = sizeof(wifi_passwd); /* 從NVS中獲取ssid */
ESP_ERROR_CHECK( nvs_get_str(wificfg_nvs_handler,"wifi_passwd",wifi_passwd,&len) );
ESP_ERROR_CHECK( nvs_commit(wificfg_nvs_handler) ); /* 提交 */
nvs_close(wificfg_nvs_handler); /* 關閉 */
/* 設置WiFi連接的ssid和password參數 */
wifi_config_t wifi_config;
bzero(&wifi_config, sizeof(wifi_config_t)); /* 將結構體數據清零 */
memcpy(wifi_config.sta.ssid, wifi_ssid, sizeof(wifi_config.sta.ssid));
memcpy(wifi_config.sta.password, wifi_passwd, sizeof(wifi_config.sta.password));
/* 設置WiFi的工作模式爲 STA */
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
/* 設置WiFi連接的參數,主要是ssid和password */
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
/* 啓動WiFi連接 */
ESP_ERROR_CHECK(esp_wifi_start());
printf("wifi_init_sta finished. \n");
/* 使用事件標誌組等待連接建立(WIFI_CONNECTED_BIT)或連接失敗(WIFI_FAIL_BIT)事件 */
EventBits_t bits; /* 定義一個事件位變量來接收事件標誌組等待函數的返回值 */
bits = xEventGroupWaitBits( wifi_event_group_handler, /* 需要等待的事件標誌組的句柄 */
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, /* 需要等待的事件位 */
pdFALSE, /* 爲pdFALSE時,在退出此函數之前所設置的這些事件位不變,爲pdFALSE則清零*/
pdFALSE, /* 爲pdFALSE時,設置的這些事件位任意一個置1就會返回,爲pdFALSE則需全爲1才返回 */
portMAX_DELAY); /* 設置爲最長阻塞等待時間,單位爲時鐘節拍 */
/* 根據事件標誌組等待函數的返回值獲取WiFi連接狀態 */
if (bits & WIFI_CONNECTED_BIT) /* WiFi連接成功事件 */
{
printf("connected to ap %s OK \n",wifi_ssid);
vEventGroupDelete(wifi_event_group_handler); /* 刪除WiFi連接事件標誌組,WiFi連接成功後不再需要 */
}
else if (bits & WIFI_FAIL_BIT) /* WiFi連接失敗事件 */
{
printf("Failed to connect to ap %s. \n",wifi_ssid);
smartconfig_init_start(); /* 開啓智能配網 */
}
else
{
printf("UNEXPECTED EVENT \n"); /* 沒有等待到事件 */
smartconfig_init_start(); /* 開啓智能配網 */
}
}
app_main.c
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "driver/gpio.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>
#define GPIO_LED_NUM 2 /* LED引腳編號 */
/* wifi連接函數,位於app_wifi.c,阻塞狀態 */
void wifi_connect_init(void);
#define HOST_IP_ADDR "192.168.0.102"
static void tcp_client_task(void *pvParameters)
{
int sockfd = -1; /* 定義一個socket描述符,用來接收打開的socket */
int ret = -1;
/* 打開一個socket套接字 */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
printf("socket open failure !!! \n");
return ;
}
else
{
struct sockaddr_in seraddr = {0};
seraddr.sin_family = AF_INET; /* 設置地址族爲IPv4 */
seraddr.sin_port = htons(12341); /* 設置地址的端口號信息 */
seraddr.sin_addr.s_addr = inet_addr("192.168.0.107"); /* 設置IP地址 */
ret = connect(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if(ret < 0)
printf("connect to server failure !!! ret = %d \n",ret);
else
{
printf("connect success.\n");
int cnt = 10;
while(cnt--)
{
ret = send(sockfd, "I am ESP32.", strlen("I am ESP32."), 0);
if(ret < 0)
printf("send err. \n");
else
printf("send ok. \n");
vTaskDelay(2000 / portTICK_PERIOD_MS); /* 延時2000ms*/
}
}
close(sockfd);
}
vTaskDelete(NULL);
}
void app_main(void)
{
/* 打印Hello world! */
printf("Hello world!\n");
wifi_connect_init();
xTaskCreate(tcp_client_task, "tcp_client", 4096, NULL, 5, NULL);
while(1)
{
gpio_set_level(GPIO_LED_NUM, 0); /* 熄滅 */
vTaskDelay(500 / portTICK_PERIOD_MS); /* 延時500ms*/
gpio_set_level(GPIO_LED_NUM, 1); /* 點亮 */
vTaskDelay(500 / portTICK_PERIOD_MS); /* 延時500ms*/
}
}