一、预期实现目标

  1. 在旭日X3派建立UDP服务端(上一章已完成)
  2. 在ESP32上建立UDP客户端
  3. 旭日X3通过UDP广播控制ESP32
  4. ESP32通过UDP广播反馈信息

二、所需材料

  1. 旭日X3派
  2. ESP32
  3. 导线、LED灯

三、实现过程

1、总体结构

2、服务器通信流程

服务器代码因某些原因不能给出,这里将对整体流程进行详尽分析

按照流程图所示:
第一步先是要初始化UDP通信,我设计的这个系统的整体通信都是基于UDP网络通信实现的;第二步建立一个空的用户列表,这个用户列表后期会被用来存储每一个ESP32的基本信息。最后,阻塞等待UDP数据,每收到一个数据就建立一个线程对这个数据进行处理。下面的介绍详细的描述了这个过程:

1、UDP通信的socket编程,UDP是本设计系统的基本通信方式,系统中所有的数据接收和发送都是基于UDP通信实现。如果在云服务器下则需要考虑开放端口的问题,首先要配置服务器的防火墙,开放系统后期将要进行通信的端口;其次还要配置服务器的一系列安全策略的组件,并开放所有的通信端口。

2、用户列表的创建和维护,用户列表使用一个数组来存储数据,作为更好的解决方案应该使用数据库或者文件的形式保存用户列表,此处要考虑模型的验证则需要使用简单的数组来简化程序的编写难度。我们在系统中用的控制终端的列表数据的基本格式定义:

// 控制终端信息
typedef struct{
    	char id;
    	char ip[16];
    	short unsigned int port;
    	describe des;
}user_info;
user_info user_info_list[MAX_COUNT_OF_CLIENT];

3、等待UDP数据,监听UDP端口并等待数据。

4、对数据进行初步的校准验证,数据格式如下图4.3所示,初步校验其实就是判断第一个字节是不是0XFE。如果是就在下一步创建新的线程来处理接收到的数据,否则就返回上一步继续等待UDP数据。

5、线程的创建和数据锁,如果系统检测收到一个数据就建立一个线程,在线程中处理接收到的数据。数据锁的主要功能是为用户提供列表服务,防止用户列表在线程间由于同时读写的问题产生错误。

6、判断当前接收到的数据的发送方是否是在用户列表中存在,如果存在就进入下一步去判断接收方;不存在则在用户列表中加入该用户,在控制终端列表中加入控制终端前需要获取数据锁,加入控制终端的任务完成之后要释放该数据锁。

7、判断接收方是服务器还是控制终端,如果是发送给其他控制终端的则服务器起到数据中转和记录的作用;在数据中转之前要先判断接收方是否正确,即遍历控制终端列表中是否存在该终端机,存在则转发数据,转发完成结束线程;如果不存在记录错误就结束此线程。

8、接收方式服务器则根据不同的数据进行处理。

3、UPD通信控制数据格式

如图所示,基本所有在网络间传输的数据都要遵循上图所示的规则。每次进行通信的数据都是由若干个字节构成的(最少 3 个字节)。
第一个字节用于初步校验,即在服务器刚收到数据时就通过该字节是不是程序需要的数据,是需求的数据才将进入下一步,不是则直接丢弃该数据。
第二个字节用来判断接收方,数据分为发送给服务器和转发给其他控制终端的数据。第二个字节如果是0 ,则表示该数据会交给服务器来进行处理;如果是非 0 则表示数据是要发送给其他控制终端来处理的,服务器在这一步里只起到了转发数据的功能。
以上两种情况对于不同的第三个字节以及第三个字节以后的数据,接收方为服务器时,第三字节为0、1、2、3分别代表更改自己的描述信息、给服务器发送的信息、获取控制终端机列表、控制终端主动断开。
更改自己的数据描述信息,从第四字节开始存储描述信息,描述信息的结构定义如下:

#define DESCRIBE_SIZE 32
// 描述信息
typedef struct{
      char type;
      char describe[DESCRIBE_SIZE];
}describe;

在给服务器发送信息时要保留数据的格式,数据格式并未给明确的定义。在服务器上,代码里会直接以字符串的形式对数据进行输出。
获取控制终端机列表,第三字节为2表示获取控制终端列表,这个通信过程只有三个字节。系统收到这个信号之后,服务器将开始以规定的数据格式向发起这个请求信号的控制终端回发一个控制终端的列表。这里所说的规定的数据格式在后面的章节中会有介绍。
控制终端会主动断开,第三字节为3的时候表示控制终端会主动断开此时的连接,这个通信过程也只有三个字节。收到这个信号之后服务器会首先获取数据锁,获取到之后从控制终端列表中删除这个控制终端,释放数据锁,然后结束此刻进行的线程。

4、ESP32流程实现

首先对OLED进行初始化,我在本系统的设计中采用了0.9寸的OLED显示屏作为输出显示模块,一般0.9寸的OLED显示屏模块都支持SPI和IIC通信,ESP32作为一个IO比较少的单片机使用这两种通信方式都可以很大程度上节约IO资源。(示例代码中不使用OLED显示屏,且使用Mriopython进行编程
开启Station模式,即初始化ESP32的Station工作模式,使ESP32作为一个路由的终端接入网络。需要注意的是在ESP32中初始化话Station工作模式之前需要初始化NVS存储系统,因为关于网络的硬件信息需要从NVS分区读取。
UDP的连接使用中,在使能Station模式之后就要开始创建UDP连接使得该设备可以连接到服务器,创建UDP连接可以使用类似Linux的socket网络编程,也可以使用esp-idf封装的UDP网络函数,这两种方法本质上没有差别,都可以实现我们设计中要求的网络连接,我在本设计中使用了第一种方法。

import socket
import time
import network
import machine

def do_connect():
    wlan=network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print('连接网络...')
        #连接WIFI
        wlan.connect('11', '1234567a')
        while not wlan.isconnected():
            pass
    print("联网成功!")
    print('network config:',wlan.ifconfig())

def start_udp():
    #创建udp套接字
    udp_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    #绑定一个固定的端口号
    udp_socket.bind(("0.0.0.0",7788))
    return udp_socket

def main():
    do_connect()
    udp_socket=start_udp()
    led=machine.Pin(22,machine.Pin.OUT)
    while True:
        #接收数据并拆包
        recv_data,sender_info=udp_socket.recvfrom(1024)
        #打印收到的数据
        print("{}发送{}".format(sender_info,recv_data))
        #取出接收到的数据
        recv_data_str=recv_data.decode("utf-8")
        if recv_data_str=='led_off':
            led.value(1)
            s.sendto(b'led_off',(0.0.0.0,7788))
        elif recv_data_str=='led_on':
            led.value(0)
            s.sendto(b'led_on',(0.0.0.0,7788))
            
if __name__ == "__main__":
main()

代码讲解:

在ESP32端创建UDP服务端对象:

def start_udp():
    #创建udp套接字
    udp_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    #绑定一个固定的端口号
    udp_socket.bind(("0.0.0.0",7788))
    return udp_socket

处理收到的数据:

ef main():
    do_connect()
    udp_socket=start_udp()
    led=machine.Pin(22,machine.Pin.OUT)
    while True:
        #接收数据并拆包
        recv_data,sender_info=udp_socket.recvfrom(1024)
        #打印收到的数据
        print("{}发送{}".format(sender_info,recv_data))
        #取出接收到的数据
        recv_data_str=recv_data.decode("utf-8")
        if recv_data_str=='led_off':
            led.value(1)
        elif recv_data_str=='led_on':
            led.value(0)

成功使用旭日X3通过UDP服务点灯: