基于ESP-IDF用UDP在局域网中广播TCP服务器IP地址与端口号(学习笔记)

第一次写文章,以记录esp32的学习过程,做为学习笔记,因能力有限仅供参考。esp32单片机主要用物联网开发,学习它必然绕不过wifi的tcp和udp协议,ble的gatt规范等。学习过程中也遇到了很多困扰了几天的问题,现在把它记录下来,供网友参考。

在这个项目中,目的实现三块esp-wroom32分别作tcp服务器a、客户端b、客户端c,还有手机或电脑做客户端,实现sock通信,手机或电脑控制服务器a和客户端b、c的led灯状态。在局域网中单片机与手机都由DHCP自动分配ip地址,单片机所创建的tcp服务器ip也被绑定为本地ip地址,也就是说tcp服务器ip地址是随机的,客户端也就无法连接服务器。有的解决办法是将服务器设置为静态IP地址,但如果出现同一个ip地址可能无法连接AP网络。此篇文章中采用另一种办法,tcp服务器中在创建一个udp广播,向局域网中广播自己tcp服务器IP地址和端口号,客户端在指定端口接收广播并解析出服务器的ip和端口,连接服务器进行tcp的sock通信。

做这个小项目时主要遇到的问题是udp广播地址是什么,接收方如何接收,如何将本地IP地址转化为广播地址。现在有了答案,通常的广播地址为ipv4局域网中最后一段的地址改为255,比如192.168.1.255。广播套接字所设置的端口为接收方的目标端口号,如果发送方发送的数据的目标端口号与接收方绑定的本地端口号匹配,那么接收方就可以接收到该数据。下面为具体的相关代码:

代码中参考了官方例程wifi_sta和tcp_server等,

首先定义tcp服务器的端口号,udp目标接收方的端口号,一个全局变量以保存本地IP地址:

#define UDP_SERVER_PORT             8848
#define BROADCAST_PORT              8080
#define BROADCAST_INTERVAL_MS       5000
#define DEVICE_NAME             "ESP32_SERVER"

esp_ip4_addr_t ip_addr;

在自定义的wifi事件回调函数中获取并保存本地IP地址:

static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < WIFI_MAXIMUM_RETRY) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG,"connect to the AP fail");

    } 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;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));

        ip_addr = event->ip_info.ip;    //保存本地IP地址

        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

接下来就是udp的广播任务:


/* UDP广播任务 */
void udp_broadcast_task()
{  
    // 将IPv4地址转换为字符串格式的IP地址
    char ip_addr_str[INET_ADDRSTRLEN];
    inet_ntoa_r(ip_addr.addr, ip_addr_str, sizeof(ip_addr_str));

    // 设置广播地址和接收方的端口号
    struct sockaddr_in broadcast_addr = {
        .sin_addr.s_addr = ip_addr.addr | htonl(0xFF), //广播地址,其类型为uint32_t
        .sin_family = AF_INET,
        .sin_port = htons(BROADCAST_PORT),
    };
    // 将广播地址转换为字符串
    ESP_LOGI(TAG, "broadcast_addr IP: %s", inet_ntoa(broadcast_addr.sin_addr.s_addr));

    // Create a socket for UDP broadcast
    int broadcast_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (broadcast_socket < 0) {
        ESP_LOGE(TAG, "Failed to create socket: %d", broadcast_socket);
        goto error;
    }


    // 设置套接字选项以启用地址重用
    int reuseEnable = 1;
    setsockopt(broadcast_socket, SOL_SOCKET, SO_REUSEADDR, &reuseEnable, sizeof(reuseEnable));

    // 使能广播
    int broadcast_enable = 1;
    if (setsockopt(broadcast_socket, SOL_SOCKET, SO_BROADCAST, &broadcast_enable, sizeof(broadcast_enable)) < 0) {
        ESP_LOGE(TAG, "Failed to enable broadcasting");
        close(broadcast_socket);
        goto error;
    }

    // Send the broadcast message every BROADCAST_INTERVAL_MS milliseconds
    while (1) {
        // 创建一个 JSON 对象,并添加三个属性
        cJSON *json = cJSON_CreateObject();
        cJSON_AddStringToObject(json, "device", DEVICE_NAME);
        cJSON_AddStringToObject(json, "ip", ip_addr_str);
        cJSON_AddNumberToObject(json, "port", UDP_SERVER_PORT);

        // 将 JSON 数据转换为字符串
        char *json_str = cJSON_Print(json);

        ESP_LOGI(TAG, "Broadcasting message: %s", json_str);
        int ret = sendto(broadcast_socket, json_str, strlen(json_str), 0, (struct sockaddr *)&broadcast_addr, sizeof(broadcast_addr));
        if (ret < 0) {
            ESP_LOGE(TAG, "Failed to send broadcast message: %d", ret);
        }
        cJSON_Delete(json);
        free(json_str);
        vTaskDelay(BROADCAST_INTERVAL_MS / portTICK_PERIOD_MS);
    }

error:
    vTaskDelete(NULL);
}

此项目代码应还包括设置sta连接wifi,和tcp服务器的任务,参考官方例程即可解决,这里不再给出。最后其广播效果:

 客户端及手机便可以根据广播内容,连接tcp服务器,进行通信操作等。在调试过程中还发现包含5GHZ频段wifi的路由器,esp32无法在局域网通信,即使用的2.4GHZ的频段,只知道esp32无5GHZ的协议,具体原因尚不明白,更换AP就好了。

路虽远,行则将至。

事虽难,做则必成。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在ESP-IDF,可以使用Bluetooth模块来实现将ESP32HID识别为蓝牙手柄的功能。具体步骤如下: 1. 首先需要配置ESP32的Bluetooth模块,可以使用ESP-IDF提供的Bluetooth API进行配置。 2. 在配置完成后,需要创建一个蓝牙手柄的服务和特征,用于与其他设备进行通信。 3. 接下来,需要实现手柄的映射功能,将手柄的按键映射到对应的命令或操作上。可以使用ESP-IDF提供的输入事件驱动库来实现这个功能。 4. 最后,需要将手柄的输入事件发送到其他设备上,以实现手柄控制其他设备的功能。 需要注意的是,不同的游戏可能需要不同的手柄映射方式,因此需要根据具体的游戏进行相应的映射设置。 ### 回答2: 基于ESP-IDF,可以将ESP32HID识别为蓝牙手柄,并实现映射。 首先,需要确保ESP-IDF的正确安装,以及ESP32的开发环境设置。然后,创建一个ESP32HID的项目。 接下来,需要在代码初始化并配置HID连接。使用esp_hid_device_init函数来初始化HID设备,并使用esp_hid_device_register_app主动注册应用程序以接收HID事件。 然后,需要将ESP32HID识别为蓝牙手柄。通过esp_bt_controller_enable函数来使能蓝牙控制器,并使用esp_bt_controller_disable函数来禁用蓝牙控制器。 接着,需要配置蓝牙设备。这包括设置设备名称、使能蓝牙可见性、配置蓝牙发现功能和HID相关设置。可以使用esp_ble_gap_set_device_name函数来设置设备名称,使用esp_ble_gap_config_visibility函数设置可见性,使用esp_ble_gap_start_discovery函数开启蓝牙发现功能,并使用esp_hid_device_register_app来注册应用程序。 最后,需要实现映射功能。映射功能可以根据接收到的HID事件来处理不同的游戏操作。可以在应用程序的回调函数处理HID事件,根据事件类型和接收到的数据来进行相应的操作。 总之,基于ESP-IDF,可以通过初始化并配置HID连接,将ESP32HID识别为蓝牙手柄,并通过实现映射功能来处理游戏操作。通过合理设置蓝牙设备和应用程序的参数,可以实现将ESP32HID作为蓝牙手柄使用的效果。 ### 回答3: 基于ESP-IDF,我们可以将ESP32HID识别为蓝牙手柄并实现映射的功能。 首先,我们需要在ESP32上安装ESP-IDF开发环境,并确保ESP32与计算机连接正常。 其次,我们需要创建一个新的ESP-IDF项目,并添加相应的蓝牙库以支持蓝牙功能。可以使用ESP-IDF提供的Bluetooth Host模块。 然后,我们需要配置ESP32HID的属性和服务,以便其他设备能够识别并连接它。可以通过使用Bluetooth Host模块提供的API进行配置。 接下来,我们需要实现手柄的按键和轴向信号的映射。可以使用ESP-IDF提供的输入设备模块来捕获手柄的按键和轴向数据。然后,我们可以编写代码来将捕获的数据映射到游戏的相应操作。例如,将手柄按键映射到游戏的跳跃动作。 最后,我们需要将代码编译并烧录到ESP32上。可以使用ESP-IDF提供的命令行工具或集成开发环境进行编译和烧录。 一旦代码烧录到ESP32上,并且ESP32HID被识别为蓝牙手柄,它就可以与支持蓝牙手柄的设备进行配对和连接。当手柄上的按键或轴向发生变化时,ESP32将捕获并发送相应的数据到连接设备。 总而言之,通过基于ESP-IDF的开发,我们可以将ESP32HID识别为蓝牙手柄,并实现手柄操作的映射功能。这样,我们可以在游戏使用ESP32HID作为蓝牙手柄来进行游戏操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值