【ESP32】-【WebSocket】

软件参考:ESP32开发指南目录(开源一小步出品)https://blog.csdn.net/qq_24550925/article/details/85933563

2020-06-22
学习ESP32的Websocket,看完了例程,编译总是出现报错。把例程源码直接编译也是出现同样的错误。在群里面偶然看到某位大哥的消息,指出的问题点和我的一样。按照大哥的修改,重新编译之后,ok了
error: implicit declaration of function ‘_base64_encode’ [-Werror=implicit-function-declaration]
在这里插入图片描述
在这里插入图片描述
下载好程序,测试OK。

贴上软件代码:
websocket.c

/* Esptouch example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "WebSocket_Task.h"

#define LED_IO 22

#define KEY_REC_IO      36
#define KEY_MODE_IO     39

#define LED_ON()		gpio_set_level(LED_IO, 1)
#define LED_OFF()		gpio_set_level(LED_IO, 0)

uint8_t  key_value = 0;
uint16_t timer_ms_cnt = 0;
uint16_t timer_1s_cnt = 0;
uint8_t  led_flag = 0;
//定时器句柄
esp_timer_handle_t fw_timer_handle = 0;


void read_key(void);
void fw_timer_cb(void *arg);


//WebSocket数据包队列
QueueHandle_t WebSocket_rx_queue;

static void user_app(void *pvParameters);
void task_process_WebSocket( void *pvParameters );
esp_err_t event_handler(void *ctx, system_event_t *event);

void app_main()
{
	printf("app_main!\n");

   //选择 IO
   gpio_pad_select_gpio(LED_IO);
   gpio_pad_select_gpio(KEY_REC_IO);
   gpio_pad_select_gpio(KEY_MODE_IO);

   //设置 IO 为输出
   gpio_set_direction(LED_IO, GPIO_MODE_OUTPUT);
   gpio_set_level(LED_IO, 1);//不亮

   //设置按键 IO 输入
   gpio_set_direction(KEY_REC_IO, GPIO_MODE_INPUT);
   gpio_set_direction(KEY_MODE_IO, GPIO_MODE_INPUT);


   //定时器结构体初始化
   esp_timer_create_args_t fw_timer =
   {
      .callback = &fw_timer_cb, //回调函数
      .arg = NULL, //参数
      .name = "fw_timer" //定时器名称
   };

   //定时器创建、启动
   esp_err_t err = esp_timer_create(&fw_timer, &fw_timer_handle);
   err = esp_timer_start_periodic(fw_timer_handle, 1 * 1000);//1ms回调
   if(err == ESP_OK)
   {
      printf("fw timer cteate and start ok!\r\n");
   }


    //一如既往的配置
    nvs_flash_init();
    tcpip_adapter_init();
    ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
    ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
    wifi_config_t sta_config = {
        .sta = {
            .ssid = "MY",//自定义
            .password = "pp612ssbb",//自定义
            .bssid_set = false
        }
    };
    ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &sta_config) );
    ESP_ERROR_CHECK( esp_wifi_start() );
    ESP_ERROR_CHECK( esp_wifi_connect() );



    //接收websocket数据任务:数据接收处理
    xTaskCreate(&task_process_WebSocket, "ws_process_rx", 2048, NULL, 5, NULL);

    //websocket server任务:建立server、等待连接、连接、数据接收打包
    xTaskCreate(&ws_server, "ws_server", 2048, NULL, 5, NULL);

    //新建一个app任务
    xTaskCreate(&user_app,       //任务函数名
                "user_app",      //任务名
                2048,               //栈大小,单位为字,即4个字节,设置太小堆栈会溢出
                NULL,               //任务形参
                8,                  //优先级,数值越大,优先级越高
                NULL                //句柄
                );

}

void read_key(void)
{
   static uint8_t cnt1 = 50;
   static uint8_t cnt2 = 50;

   if(gpio_get_level(KEY_REC_IO)==0)//按键按下
      cnt1++;
   else
      cnt1--;
   
   if(gpio_get_level(KEY_MODE_IO)==0)//按键按下
      cnt2++;
   else
      cnt2--;

   if(cnt1 > 52)
   {
      cnt1 = 50;
      key_value |= 0x01;
   }
   else if(cnt1 < 48)
   {
      cnt1 = 50;
      key_value &= ~0x01;
   }

   if(cnt2 > 52)
   {
      cnt2 = 50;
      key_value |= 0x02;
   }
   else if(cnt2 < 48)
   {
      cnt2 = 50;
      key_value &= ~0x02;
   }
}

void fw_timer_cb(void *arg)
{
   timer_ms_cnt++;
   timer_1s_cnt++;
}

/*
* websocket server数据解析 
* @param[in]   void  		       :无
* @retval      void                :无
* @note        修改日志 
*               Ver0.0.1:
                    hx-zsj, 2018/11/22, 初始化版本\n 
*/
void task_process_WebSocket( void *pvParameters )
{
    (void)pvParameters;
    //WebSocket数据包
    WebSocket_frame_t __RX_frame;
    //create WebSocket RX 队列
    WebSocket_rx_queue = xQueueCreate(10,sizeof(WebSocket_frame_t));
    while (1)
    {
        //接收到WebSocket数据包
        if(xQueueReceive(WebSocket_rx_queue,&__RX_frame, 3*portTICK_PERIOD_MS)==pdTRUE)
        {
        	//打印下
        	printf("Websocket Data Length %d, Data: %.*s \r\n", __RX_frame.payload_length, __RX_frame.payload_length, __RX_frame.payload);
        	if(memcmp( __RX_frame.payload,"ON",2)==0)
            {
                LED_ON();
            }else if(memcmp( __RX_frame.payload,"OFF",3)==0)
            {
                LED_OFF();
            }else {
                //把接收到的数据回发
                WS_write_data(__RX_frame.payload, __RX_frame.payload_length);
            }
        	//free memory
			if (__RX_frame.payload != NULL)
				free(__RX_frame.payload);
        }
    }
}

esp_err_t event_handler(void *ctx, system_event_t *event)
{
    return ESP_OK;
}

static void user_app(void *pvParameters)
{
    static uint8_t key_value_tmp = 0;

    while(1)
    {
        if(timer_ms_cnt > 10)   //10ms
        {
            timer_ms_cnt -= 10;
            read_key();
        }

        if(key_value_tmp != key_value)
        {
            key_value_tmp = key_value;
            printf("\r\nkey_value = 0x%.2x \n",key_value);
        }

        if(timer_1s_cnt >= 1000)   //1s
        {
            timer_1s_cnt -= 1000;   

            if(led_flag)
            {
                led_flag = 0;
//                gpio_set_level(LED_IO, 1);    //led亮
            }
            else
            {
                led_flag = 1;
//                gpio_set_level(LED_IO, 0);    //led灭
            }      

        }

        vTaskDelay(10/ portTICK_RATE_MS);
    }
}

WebSocket_Task.c

/*
* @file         WebSocket_Task.c 
* @brief        websocket server建立和数据接收
* @details      源码来源,http://www.barth-dev.de/websockets-on-the-esp32
* @author       hx-zsj 
* @par Copyright (c):  
*               红旭无线开发团队,QQ群:671139854
* @par History:          
*               Ver0.0.1:
                     hx-zsj, 2018/11/22, 初始化版本\n 
*/

/* 
=============
头文件包含
=============
*/
#include "WebSocket_Task.h"

#include "freertos/FreeRTOS.h"
#include "esp_heap_alloc_caps.h"
#include "esp_heap_caps.h"

#include "hwcrypto/sha.h"
#include "esp_system.h"
#include "wpa2/utils/base64.h"
#include <string.h>
#include <stdlib.h>




#define WS_PORT				9998	/*server tcp端口*/
#define WS_CLIENT_KEY_L		24		/*client key 长度*/
#define SHA1_RES_L			20		/*SHA1 result*/

#define WS_STD_LEN			125		/*数据帧长度*/
#define WS_SPRINTF_ARG_L	4		/*sprintf长度*/

//帧类型
typedef enum {
	WS_OP_CON = 0x0, 				/*!< Continuation Frame*/
	WS_OP_TXT = 0x1, 				/*!< Text Frame*/
	WS_OP_BIN = 0x2, 				/*!< Binary Frame*/
	WS_OP_CLS = 0x8, 				/*!< Connection Close Frame*/
	WS_OP_PIN = 0x9, 				/*!< Ping Frame*/
	WS_OP_PON = 0xa 				/*!< Pong Frame*/
} WS_OPCODES;

//接收数据队列
extern QueueHandle_t WebSocket_rx_queue;

//websocket connect 句柄
static struct netconn* WS_conn = NULL;

//websocket关键参数
const char WS_sec_WS_keys[] = "Sec-WebSocket-Key:";
const char WS_sec_conKey[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
const char WS_srv_hs[] ="HTTP/1.1 101 Switching Protocols \r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %.*s\r\n\r\n";

/*
* websocket发送数据
* @param[in]   p_data  		       :数据指针
* @param[in]   length  		       :数据长度
* @retval      void                :无
* @note        修改日志 
*               Ver0.0.1:
                    hx-zsj, 2018/11/22, 初始化版本\n 
*/
err_t WS_write_data(char* p_data, size_t length) {

	//websocket未连接,直接退出
	if (WS_conn == NULL)
		return ERR_CONN;
	//数据帧长度溢出,直接退出
	if (length > WS_STD_LEN)
		return ERR_VAL;
	err_t result;
	//报头
	WS_frame_header_t hdr;
	hdr.FIN = 0x1;
	hdr.payload_length = length;
	hdr.mask = 0;
	hdr.reserved = 0;
	hdr.opcode = WS_OP_TXT;
	//发送报头
	result = netconn_write(WS_conn, &hdr, sizeof(WS_frame_header_t), NETCONN_COPY);
	if (result != ERR_OK)
		return result;
	//发送数据
	return netconn_write(WS_conn, p_data, length, NETCONN_COPY);
}
/*
* websocket server 连接、握手、数据读取
* @param[in]   conn  		       :websocket connect句柄
* @retval      void                :无
* @note        修改日志 
*               Ver0.0.1:
                    hx-zsj, 2018/11/22, 初始化版本\n 
*/
static void ws_server_netconn_serve(struct netconn *conn) {

	//Netbuf
	struct netbuf *inbuf;
	//数据包
	char *buf;
	//pointer to buffer (multi purpose)
	char* p_buf;
	//Pointer to SHA1 input
	char* p_SHA1_Inp;
	//Pointer to SHA1 result
	char* p_SHA1_result;
	//multi purpose number buffer
	uint16_t i;
	//will point to payload (send and receive
	char* p_payload;
	//Frame header pointer
	WS_frame_header_t* p_frame_hdr;
	//申请SHA1
	p_SHA1_Inp = pvPortMallocCaps(WS_CLIENT_KEY_L + sizeof(WS_sec_conKey),
			MALLOC_CAP_8BIT);
	//申请SHA1 result
	p_SHA1_result = pvPortMallocCaps(SHA1_RES_L, MALLOC_CAP_8BIT);
	//Check if malloc suceeded
	if ((p_SHA1_Inp != NULL) && (p_SHA1_result != NULL)) {

		//接收“连接”过程的数据
		if (netconn_recv(conn, &inbuf) == ERR_OK) {
			//读取“连接”过程的数据到buf
			netbuf_data(inbuf, (void**) &buf, &i);
			//把server的key传给SHA1
			for (i = 0; i < sizeof(WS_sec_conKey); i++)
			{
				//放在后24字节
				p_SHA1_Inp[i + WS_CLIENT_KEY_L] = WS_sec_conKey[i];
			}
			//搜索client的key
			p_buf = strstr(buf, WS_sec_WS_keys);
			//找到key
			if (p_buf != NULL) {
				//get Client Key
				for (i = 0; i < WS_CLIENT_KEY_L; i++)
				{
					//放在前24字节
					p_SHA1_Inp[i] = *(p_buf + sizeof(WS_sec_WS_keys) + i);
				}
				// 计算 hash
				esp_sha(SHA1, (unsigned char*) p_SHA1_Inp, strlen(p_SHA1_Inp),
						(unsigned char*) p_SHA1_result);
				//转base64
				p_buf = (char*) base64_encode((unsigned char*) p_SHA1_result,
						SHA1_RES_L, (size_t*) &i);
				//free SHA1 input
				free(p_SHA1_Inp);
				//free SHA1 result
				free(p_SHA1_result);
				//申请“握手”内存
				p_payload = pvPortMallocCaps(
						sizeof(WS_srv_hs) + i - WS_SPRINTF_ARG_L,
						MALLOC_CAP_8BIT);
				if (p_payload != NULL) {
					//准备“握手”帧
					sprintf(p_payload, WS_srv_hs, i - 1, p_buf);
					//发送“握手”帧
					netconn_write(conn, p_payload, strlen(p_payload),NETCONN_COPY);
					//free base64
					free(p_buf);
					//free “握手”内存
					free(p_payload);
					//websocket连接成功
					WS_conn = conn;
					//“接收数据”
					while (netconn_recv(conn, &inbuf) == ERR_OK) {
						//读取数据到buf
						netbuf_data(inbuf, (void**) &buf, &i);
						//扔到p_frame_hdr
						p_frame_hdr = (WS_frame_header_t*) buf;
						//此帧是“连接关闭”帧,直接退出
						if (p_frame_hdr->opcode == WS_OP_CLS)
							break;
						//有效数据帧长度判断
						if (p_frame_hdr->payload_length <= WS_STD_LEN) {
							//数据扔到p_buf
							p_buf = (char*) &buf[sizeof(WS_frame_header_t)];
							//check if content is masked
							if (p_frame_hdr->mask) {
								//申请内存
								p_payload = pvPortMallocCaps(
										p_frame_hdr->payload_length + 1,
										MALLOC_CAP_8BIT);
								//申请内存成功
								if (p_payload != NULL) {
									//解码
									for (i = 0; i < p_frame_hdr->payload_length;
											i++)
										p_payload[i] = (p_buf + WS_MASK_L)[i]
												^ p_buf[i % WS_MASK_L];
											
									//加个尾巴
									p_payload[p_frame_hdr->payload_length] = 0;
								}
							} else{
								//content is not masked
								p_payload = p_buf;
							}

							//有效数据
							if ((p_payload != NULL)	&& (p_frame_hdr->opcode == WS_OP_TXT)) {
								//组包
								WebSocket_frame_t __ws_frame;
								__ws_frame.conenction=conn;
								__ws_frame.frame_header=*p_frame_hdr;
								__ws_frame.payload_length=p_frame_hdr->payload_length;
								__ws_frame.payload=p_payload;

								//发送给另一个任务解析
								xQueueSendFromISR(WebSocket_rx_queue,&__ws_frame,0);
							}
							//free payload buffer (in this demo done by the receive task)
//							if (p_frame_hdr->mask && p_payload != NULL)
//								free(p_payload);
						} //数据中超长
						//清空buf
						netbuf_delete(inbuf);
					} //有效数据读取失败
				} //握手内存申请失败
			} //连接过程无key
		} //连接数据读取失败
	} //p_SHA1_Inp!=NULL&p_SHA1_result!=NULL

	//清空连接
	WS_conn = NULL;
	//清空buf
	netbuf_delete(inbuf);
	//关闭websocket server connect
	netconn_close(conn);
	netconn_delete(conn);

}

/*
* websocket server 建立服务
* @param[in]   conn  		       :websocket connect句柄
* @retval      void                :无
* @note        修改日志 
*               Ver0.0.1:
                    hx-zsj, 2018/11/22, 初始化版本\n 
*/
void ws_server(void *pvParameters) 
{
	struct netconn *conn, *newconn;
	//获取tcp socket connect
	conn = netconn_new(NETCONN_TCP);
	//绑定port
	netconn_bind(conn, NULL, WS_PORT);
	//监听
	netconn_listen(conn);
	//等待client连接
	while (netconn_accept(conn, &newconn) == ERR_OK)
	{
		//新连接:等待连接、连接过程、数据读取
		ws_server_netconn_serve(newconn);
	}
	//关闭websocket server connect
	netconn_close(conn);
	netconn_delete(conn);
}

WebSocket_Task.h

/**
 * @section License
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2017, Thomas Barth, barth-dev.de
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */


#ifndef	_WEBSOCKET_TASK_H_
#define _WEBSOCKET_TASK_H_

#include "lwip/api.h"

#define WS_MASK_L		0x4		/**< \brief Length of MASK field in WebSocket Header*/


/** \brief Websocket frame header type*/
typedef struct {
	uint8_t opcode :WS_MASK_L;
	uint8_t reserved :3;
	uint8_t FIN :1;
	uint8_t payload_length :7;
	uint8_t mask :1;
} WS_frame_header_t;

/** \brief Websocket frame type*/
typedef struct{
	struct netconn* 	conenction;
	WS_frame_header_t	frame_header;
	size_t				payload_length;
	char*				payload;
}WebSocket_frame_t;


/**
 * \brief Send data to the websocket client
 *
 * \return 	#ERR_VAL: 	Payload length exceeded 2^7 bytes.
 * 			#ERR_CONN:	There is no open connection
 * 			#ERR_OK:	Header and payload send
 * 			all other values: derived from #netconn_write (sending frame header or payload)
 */
err_t WS_write_data(char* p_data, size_t length);

/**
 * \brief WebSocket Server task
 */
void ws_server(void *pvParameters);


#endif /* _WEBSOCKET_TASK_H_ */


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2022 / 01/ 30: 新版esptool 刷micropython固件指令不是 esptool.py cmd... 而是 esptool cmd... 即可;另外rshell 在 >= python 3.10 的时候出错解决方法可以查看:  已于2022年发布的: 第二章:修复rshell在python3.10出错 免费内容: https://edu.csdn.net/course/detail/29666 micropython语法和python3一样,编写起来非常方便。如果你快速入门单片机玩物联网而且像轻松实现各种功能,那绝力推荐使用micropython。方便易懂易学。 同时如果你懂C语音,也可以用C写好函数并编译进micropython固件里然后进入micropython调用(非必须)。 能通过WIFI联网(2.1章),也能通过sim卡使用2G/3G/4G/5G联网(4.5章)。 为实现语音控制,本教程会教大家使用tensorflow利用神经网络训练自己的语音模型并应用。为实现通过网页控制,本教程会教大家linux(debian10 nginx->uwsgi->python3->postgresql)网站前后台入门。为记录单片机传输过来的数据, 本教程会教大家入门数据库。  本教程会通过通俗易懂的比喻来讲解各种原理与思路,并手把手编写程序来实现各项功能。 本教程micropython版本是 2019年6月发布的1.11; 更多内容请看视频列表。  学习这门课程之前你需要至少掌握: 1: python3基础(变量, 循环, 函数, 常用库, 常用方法)。 本视频使用到的零件与淘宝上大致价格:     1: 超声波传感器(3)     2: MAX9814麦克风放大模块(8)     3: DHT22(15)     4: LED(0.1)     5: 8路5V低电平触发继电器(12)     6: HX1838红外接收模块(2)     7:红外发射管(0.1),HX1838红外接收板(1)     other: 电表, 排线, 面包板(2)*2,ESP32(28)  

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值