软件参考: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_ */