跳到主要内容

控制中心功能开发

https功能开发​

HTTP基础概念及操作​

HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最广泛的一种网络协议,用于客户端(如浏览器、程序)与服务器之间的通信。简单来说,就是客户端向服务器发送请求,服务器根据请求返回相应的数据。

HTTP有如下几种操作方法。

(1)GET方法:从服务器获取数据(相当于 "读取" 操作)

特点:

  • 参数会直接附加在 URL 后面(例如:https://example.com/get?name=test&age=18)

  • 由于参数在 URL 中可见,不适合传输敏感信息(如密码)

  • 传输的数据量有限制(取决于浏览器和服务器的限制)

  • 典型场景:浏览网页、搜索内容、获取列表数据等

(2)POST方法:服务器提交数据(相当于 "写入" 操作)

特点:

  • 参数不会显示在 URL 中,而是放在请求体(Request Body)中

  • 适合传输敏感信息和大量数据

  • 数据传输量没有严格限制(由服务器配置决定)

  • 典型场景:用户登录、表单提交、上传文件等

(3)其他常见方法

  • PUT:更新服务器上的资源(全量更新)

  • DELETE:删除服务器上的资源

  • PATCH:部分更新服务器上的资源

使用HTTP的通信流程为:

  • 客户端建立与服务器的连接

  • 客户端发送 HTTP 请求(包含请求方法、目标 URL、请求头、请求体等)

  • 服务器接收请求并处理

  • 服务器返回 HTTP 响应(包含状态码、响应头、响应体等)

  • 客户端接收响应并处理数据

  • 连接关闭(短连接模式下)

libcurl库的使用​

第1步:初始化 libcurl 全局环境

curl_global_init(CURL_GLOBAL_ALL);

功能:初始化 libcurl 的全局资源,必须在所有其他 curl 函数之前调用

参数CURL_GLOBAL_ALL表示初始化所有可能的全局资源

第2步:创建 curl 会话句柄

CURL \*curl = curl_easy_init();

if(curl == NULL) {

// 初始化失败处理

return 1;

}

功能:创建一个 curl 会话实例,后续的操作都基于此句柄

必须检查返回值,确保创建成功

第3步:设置请求选项

使用curl_easy_setopt函数设置各种请求参数,常用选项:

// 设置请求的URL

curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");

// 设置是否跟随重定向(1L表示启用)

curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

// SSL相关设置(示例中禁用了验证,实际生产环境不推荐)

curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // 禁用服务器证书验证

curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); // 禁用主机名验证

第4步:通过回调函数处理服务器返回的数据。

// 定义存储响应数据的结构体

struct MemoryStruct {

char \*memory; // 存储数据的缓冲区

size_t size; // 数据大小

};

// 回调函数:用于接收服务器返回的数据

static size_t WriteMemoryCallback(void \*contents, size_t size, size_t nmemb, void \*userp) {

size_t realsize = size \* nmemb;

struct MemoryStruct \*mem = (struct MemoryStruct \*)userp;

// 重新分配内存以容纳新数据

char \*ptr = realloc(mem-\>memory, mem-\>size + realsize + 1);

if(!ptr) {

printf("内存不足\n");

return 0;

}

mem-\>memory = ptr;

// 复制新数据到缓冲区

memcpy(&(mem-\>memory\[mem-\>size\]), contents, realsize);

mem-\>size += realsize;

mem->memory[mem->size] = 0; // 添加字符串结束符

return realsize; // 返回实际处理的数据大小

}

// 初始化缓冲区

struct MemoryStruct chunk;

chunk.memory = malloc(1);

chunk.size = 0;

// 设置回调函数和数据存储结构体

curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);

curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void \*)&chunk);

第5步:执行请求

CURLcode res = curl_easy_perform(curl);

if(res != CURLE_OK) {

// 请求失败处理

syslog(LOG_INFO, "请求失败: %s\n", curl_easy_strerror(res));

} else {

// 请求成功,处理响应数据

syslog(LOG_INFO,"接收数据大小: %lu 字节\n", (unsigned long)chunk.size);

// chunk.memory中存储了服务器返回的数据

}

第6步:释放资源

// 清理curl会话句柄

curl_easy_cleanup(curl);

// 清理libcurl全局环境

curl_global_cleanup();

// 释放存储响应数据的内存

free(chunk.memory);

在第3步中添加以下设置即可发送 POST 请求:

// 设置为POST方法

curl_easy_setopt(curl, CURLOPT_POST, 1L);

// 设置POST数据

curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "name=test&age=18"); // 表单数据

示例程序​

源码如下:

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

#include <fcntl.h>

#include <errno.h>

#include <syslog.h>

#include <curl/curl.h>

// 定义宏将syslog替换为printf

#define syslog(priority, ...) printf(__VA_ARGS__)

struct MemoryStruct {

char \*memory;

size_t size;

};

static size_t WriteMemoryCallback(void \*contents, size_t size, size_t nmemb, void \*userp) {

size_t realsize = size \* nmemb;

struct MemoryStruct \*mem = (struct MemoryStruct \*)userp;

char \*ptr = realloc(mem-\>memory, mem-\>size + realsize + 1);

if(!ptr) {

printf("not enough memory (realloc returned NULL)\n");

return 0;

}

mem-\>memory = ptr;

memcpy(&(mem-\>memory\[mem-\>size\]), contents, realsize);

mem-\>size += realsize;

mem-\>memory\[mem-\>size\] = 0;

return realsize;

}

int main(int argc, char \*argv\[\])

{

CURL \*curl;

CURLcode res;

syslog(LOG_INFO,"to curl_global_init\n");

curl_global_init(CURL_GLOBAL_ALL);

// 初始化curl句柄

curl = curl_easy_init();

if (!curl) {

fprintf(stderr, "curl_easy_init() failed\n");

return 1;

}

// 准备接收数据的结构

struct MemoryStruct chunk;

// 先执行POST请求

syslog(LOG_INFO, "=== Performing POST request ===\n");

// 重置并设置POST请求选项

curl_easy_reset(curl);

curl_easy_setopt(curl, CURLOPT_URL, "https://httpbin.org/post");

curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36");

curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);

curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);

curl_easy_setopt(curl, CURLOPT_POST, 1L);

curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "name=test&age=18");

// 设置接收数据的回调

chunk.memory = malloc(1);

chunk.size = 0;

curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);

curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void \*)&chunk);

res = curl_easy_perform(curl);

if(res != CURLE_OK) {

syslog(LOG_INFO, "POST request failed: %s\n", curl_easy_strerror(res));

} else {

syslog(LOG_INFO, "POST request successful, %lu bytes retrieved\n", (unsigned long)chunk.size);

if(chunk.size \> 0) {

syslog(LOG_INFO, "POST Response data:\n%.\*s\n", (int)chunk.size, chunk.memory);

}

}

// 清理POST请求的数据

if (chunk.memory) {

free(chunk.memory);

}

// 再执行GET请求

syslog(LOG_INFO, "=== Performing GET request ===\n");

// 重置并设置GET请求选项

curl_easy_reset(curl);

curl_easy_setopt(curl, CURLOPT_URL, "https://httpbin.org/get");

curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36");

curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);

curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);

// 设置接收数据的回调

chunk.memory = malloc(1);

chunk.size = 0;

curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);

curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void \*)&chunk);

res = curl_easy_perform(curl);

if(res != CURLE_OK) {

syslog(LOG_INFO, "GET request failed: %s\n", curl_easy_strerror(res));

} else {

syslog(LOG_INFO, "GET request successful, %lu bytes retrieved\n", (unsigned long)chunk.size);

if(chunk.size \> 0) {

syslog(LOG_INFO, "GET Response data:\n%.\*s\n", (int)chunk.size, chunk.memory);

}

}

// 清理

if (chunk.memory) {

free(chunk.memory);

}

curl_easy_cleanup(curl);

curl_global_cleanup();

return 0;

}

上机测试​

按照视频把http加入工程、配置:

CONFIG_EXAMPLES_HTTP=y

CONFIG_LIB_CURL=y

CONFIG_DEV_URANDOM=y # 不加的话会卡死

CONFIG_DEV_URANDOM_ARCH=y

然后先清除再编译、打包,命令如下:

m c \# 必须先清除

m

pack

烧写映像文件后启动,先配置、测试WIFI(最好接上天线):

ifup wlan0

wapi mode wlan0 2

# 100asktech 是WIFI密码,需要改成你自己的

wapi psk wlan0 100asktech 3

# Programmers是WIFI AP名字,需要改成你自己的

wapi essid wlan0 Programmers 1

renew wlan0

# 查看IP

ifconfig

# ping互联网, Ctrl+C退出

ping news.qq.com

再执行http命令:

http

websocket功能开发​

WebSocket 基本概念​

WebSocket是一种在单个 TCP 连接上进行全双工通信的协议,与传统的 HTTP 协议相比,它最大的特点是持久连接和双向通信。这意味着客户端和服务器建立连接后,可以随时向对方发送数据,而无需像 HTTP 那样每次通信都重新建立连接。

WebSocket 定义了两种主要的数据传输格式:

(1)文本数据(Text):

  • 基于 UTF-8 编码的字符串数据

  • 适合传输 JSON、XML、普通文本等可读性强的数据

  • 在 libwebsockets 中对应 LWS_WRITE_TEXT 标志

(2)二进制数据(Binary):

  • 原始字节流,没有编码限制

  • 适合传输图片、音频、视频等二进制文件

  • 在 libwebsockets 中对应 LWS_WRITE_BINARY 标志

两种类型的主要区别在于:文本数据必须符合 UTF-8 编码规范,而二进制数据可以是任意字节组合。接收方需要根据数据类型采用不同的处理方式。

libwebsockets 的使用​

libwebsockets 是一个轻量级的 C 语言 WebSocket 库,支持客户端和服务器模式,同时提供了对 HTTP、SSL/TLS 等协议的支持。下面结合示例代码,讲解如何使用 libwebsockets 开发 WebSocket 客户端。

示例代码实现了一个简单的 WebSocket 客户端,功能包括:

  • 连接到公共回声服务器(echo.websocket.org)

  • 发送 5 条文本消息

  • 接收服务器返回的回声消息

  • 支持优雅退出

第1步:实现核心回调函数(处理事件逻辑)。

回调函数是 libwebsockets 的 “大脑”,所有网络事件(连接、发送、接收、关闭)都通过回调函数触发。需按步骤处理不同事件类型,代码如下:

static int callback(struct lws \*wsi, enum lws_callback_reasons reason, void \*user, void \*in, size_t len) {

struct session_data \*data = (struct session_data \*)user;

switch (reason) {

case LWS_CALLBACK_CLIENT_ESTABLISHED:

printf("连接已建立到服务器\n");

data-\>message_count = 0;

data-\>message_received = 0;

// 连接建立后,标记可写状态以发送第一条消息

lws_callback_on_writable(wsi);

break;

case LWS_CALLBACK_CLIENT_WRITEABLE: {

// 准备并发送消息

if (data-\>message_count \< 5) { // 示例:发送5条消息

char message\[128\];

int msg_len = snprintf(message, sizeof(message), "你好, 服务器! 这是消息 \#%d", data-\>message_count + 1);

// 分配发送缓冲区,libwebsockets 要求在数据前后预留空间

unsigned char \*buf = (unsigned char \*)malloc(LWS_PRE + msg_len + 1);

if (!buf) {

return -1;

}

memcpy(buf + LWS_PRE, message, msg_len);

buf[LWS_PRE + msg_len] = '\0'; // 确保字符串终止

int ret = lws_write(wsi, buf + LWS_PRE, msg_len, LWS_WRITE_TEXT);

free(buf);

if (ret \< msg_len) {

fprintf(stderr, "部分消息发送失败\n");

return -1;

} else {

printf("已发送: %s\n", message);

data-\>message_count++;

}

// 再次请求可写回调,准备发送下一条消息(或等待回复)

lws_callback_on_writable(wsi);

}

break;

}

case LWS_CALLBACK_CLIENT_RECEIVE:

// 处理接收到的消息

printf("收到回声: %.*s\n", (int)len, (char *)in);

data-\>message_received++;

// 收到回复后,可以计划发送下一条消息,或者在此示例中,我们发送5条后主要等待接收

// 在实际程序中,你可能需要更复杂的逻辑来控制流程

if (data-\>message_received \>= 5) {

printf("已收到所有预期回复。准备断开连接。\n");

interrupted = 1; // 触发断开连接和退出

}

break;

case LWS_CALLBACK_CLIENT_CLOSED:

printf("连接已关闭\n");

interrupted = 1;

break;

case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:

fprintf(stderr, "连接错误: %s\n", in ? (char *)in : "未知错误");

interrupted = 1;

break;

default:

break;

}

return 0;

}

第2步:定义协议配置。将回调函数与协议名称绑定,告知 libwebsockets 用哪个回调处理事件。

// 定义我们使用的协议

static struct lws_protocols protocols\[\] = {

{

"echo-protocol",

callback,

sizeof(struct session_data), // 每个会话数据大小

0, // 最大帧大小,0表示使用库默认值

},

{ NULL, NULL, 0, 0 } // 结束标记

};

第3步:创建 libwebsockets 上下文(全局管理对象)

上下文(lws_context)是 libwebsockets 的全局管理对象,负责管理所有连接和资源,需按步骤初始化:

// 定义上下文创建信息结构体,并初始化清零

memset(&info, 0, sizeof(info));

// 配置上下文参数(客户端模式)

info.port = CONTEXT_PORT_NO_LISTEN; // 不作为服务器监听端口

info.iface = NULL;

info.protocols = protocols; // 关联协议数组(绑定回调函数)

info.gid = -1;

info.uid = -1;

info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; // 启用 SSL

// 创建 libwebsockets 上下文

context = lws_create_context(&info);

if (!context) {

fprintf(stderr, "创建 libwebsockets 上下文失败\n");

return -1;

}

第3步:配置并发起服务器连接。

设置服务器地址、端口、路径等信息,发起 WebSocket 连接:

// 创建 libwebsockets 上下文

context = lws_create_context(&info);

if (!context) {

fprintf(stderr, "创建 libwebsockets 上下文失败\n");

return -1;

}

// 配置连接信息

// 先初始化清零

memset(&connect_info, 0, sizeof(connect_info));

// 配置连接参数

connect_info.context = context; // 关联上下文

connect_info.address = url; // 服务器地址

connect_info.port = port; // 端口

connect_info.path = "/"; // 请求路径

connect_info.host = connect_info.address; // 主机名(与地址一致,用于 SSL 验证)

connect_info.origin = connect_info.address;

connect_info.protocol = protocols\[0\].name;

connect_info.ssl_connection = LCCSCF_USE_SSL; // 使用 SSL (WSS)

printf("正在连接到 %s ...\n", url);

// 发起连接

wsi = lws_client_connect_via_info(&connect_info);

if (!wsi) {

fprintf(stderr, "初始连接尝试失败\n");

lws_context_destroy(context);

return -1;

}

第4步:启动事件循环(处理网络事件)。

事件循环是客户端的 “心脏”,通过 lws_service 函数持续处理网络事件(如连接、发送、接收):

// 事件循环

// 进入事件循环,直到 interrupted 为 1(触发退出)

while (!interrupted) {

// 调用 lws_service 处理事件,超时 50 毫秒(避免阻塞)

// 检查是否有新事件(如可写、接收),并触发对应回调函数

lws_service(context, 50); // 50毫秒超时

}

第5步:清理资源(避免内存泄漏)

事件循环退出后,需按步骤销毁连接和上下文,释放所有资源:

// 销毁上下文(自动关闭所有连接)

lws_context_destroy(context);

示例程序​

源码如下:

#include &lt;libwebsockets.h&gt;

#include &lt;stdio.h&gt;

#include &lt;stdlib.h&gt;

#include &lt;string.h&gt;

#include &lt;signal.h&gt;

static int interrupted = 0;

void signal_handler(int sig) {

interrupted = 1;

}

struct session_data {

int message_count;

int message_received;

};

static int callback(struct lws \*wsi, enum lws_callback_reasons reason, void \*user, void \*in, size_t len) {

struct session_data \*data = (struct session_data \*)user;

switch (reason) {

case LWS_CALLBACK_CLIENT_ESTABLISHED:

printf("连接已建立到服务器\n");

data-\>message_count = 0;

data-\>message_received = 0;

// 连接建立后,标记可写状态以发送第一条消息

lws_callback_on_writable(wsi);

break;

case LWS_CALLBACK_CLIENT_WRITEABLE: {

// 准备并发送消息

if (data-\>message_count \< 5) { // 示例:发送5条消息

char message\[128\];

int msg_len = snprintf(message, sizeof(message), "你好, 服务器! 这是消息 \#%d", data-\>message_count + 1);

// 分配发送缓冲区,libwebsockets 要求在数据前后预留空间

unsigned char \*buf = (unsigned char \*)malloc(LWS_PRE + msg_len + 1);

if (!buf) {

return -1;

}

memcpy(buf + LWS_PRE, message, msg_len);

buf[LWS_PRE + msg_len] = '\0'; // 确保字符串终止

int ret = lws_write(wsi, buf + LWS_PRE, msg_len, LWS_WRITE_TEXT);

free(buf);

if (ret \< msg_len) {

fprintf(stderr, "部分消息发送失败\n");

return -1;

} else {

printf("已发送: %s\n", message);

data-\>message_count++;

}

// 再次请求可写回调,准备发送下一条消息(或等待回复)

lws_callback_on_writable(wsi);

}

break;

}

case LWS_CALLBACK_CLIENT_RECEIVE:

// 处理接收到的消息

printf("收到回声: %.*s\n", (int)len, (char *)in);

data-\>message_received++;

// 收到回复后,可以计划发送下一条消息,或者在此示例中,我们发送5条后主要等待接收

// 在实际程序中,你可能需要更复杂的逻辑来控制流程

if (data-\>message_received \>= 5) {

printf("已收到所有预期回复。准备断开连接。\n");

interrupted = 1; // 触发断开连接和退出

}

break;

case LWS_CALLBACK_CLIENT_CLOSED:

printf("连接已关闭\n");

interrupted = 1;

break;

case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:

fprintf(stderr, "连接错误: %s\n", in ? (char *)in : "未知错误");

interrupted = 1;

break;

default:

break;

}

return 0;

}

// 定义我们使用的协议

static struct lws_protocols protocols\[\] = {

{

"echo-protocol",

callback,

sizeof(struct session_data), // 每个会话数据大小

0, // 最大帧大小,0表示使用库默认值

},

{ NULL, NULL, 0, 0 } // 结束标记

};

int main() {

// 设置信号处理,用于优雅退出

signal(SIGINT, signal_handler);

struct lws_context_creation_info info;

struct lws_context \*context;

struct lws_client_connect_info connect_info;

struct lws \*wsi;

const char \*url = "echo.websocket.org";

int port = 443; // SSL 端口

struct session_data data;

// 定义上下文创建信息结构体,并初始化清零

memset(&info, 0, sizeof(info));

// 配置上下文参数(客户端模式)

info.port = CONTEXT_PORT_NO_LISTEN; // 不作为服务器监听端口

info.iface = NULL;

info.protocols = protocols; // 关联协议数组(绑定回调函数)

info.gid = -1;

info.uid = -1;

info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; // 启用 SSL

// 创建 libwebsockets 上下文

context = lws_create_context(&info);

if (!context) {

fprintf(stderr, "创建 libwebsockets 上下文失败\n");

return -1;

}

// 配置连接信息

// 先初始化清零

memset(&connect_info, 0, sizeof(connect_info));

// 配置连接参数

connect_info.context = context; // 关联上下文

connect_info.address = url; // 服务器地址

connect_info.port = port; // 端口

connect_info.path = "/"; // 请求路径

connect_info.host = connect_info.address; // 主机名(与地址一致,用于 SSL 验证)

connect_info.origin = connect_info.address;

connect_info.protocol = protocols\[0\].name;

connect_info.ssl_connection = LCCSCF_USE_SSL; // 使用 SSL (WSS)

printf("正在连接到 %s ...\n", url);

// 发起连接

wsi = lws_client_connect_via_info(&connect_info);

if (!wsi) {

fprintf(stderr, "初始连接尝试失败\n");

lws_context_destroy(context);

return -1;

}

// 事件循环

// 进入事件循环,直到 interrupted 为 1(触发退出)

while (!interrupted) {

// 调用 lws_service 处理事件,超时 50 毫秒(避免阻塞)

// 检查是否有新事件(如可写、接收),并触发对应回调函数

lws_service(context, 50); // 50毫秒超时

}

// 销毁上下文(自动关闭所有连接)

lws_context_destroy(context);

printf("程序结束\n");

return 0;

}

上机测试​

按照视频把websocket_test加入工程、配置:

CONFIG_EXAMPLES_WEBSOCKET_TEST=y

CONFIG_NETUTILS_CJSON=y

CONFIG_OPENSSL_MBEDTLS_WRAPPER=y

CONFIG_NETUTILS_LIBWEBSOCKETS=y

CONFIG_CRYPTO_MBEDTLS=y

CONFIG_MBEDTLS_NET_C=y

CONFIG_NET_TCPPROTO_OPTIONS=y

修改“apps/netutils/libwebsockets/Makefile”,加入最后3行:

CFLAGS += \

-I. \

-I$(LIBWEBSOCKETS_UNPACK)/lib/core \

-I$(LIBWEBSOCKETS_UNPACK)/lib/plat/unix \

-I$(LIBWEBSOCKETS_UNPACK)/lib/event-libs \

-I$(LIBWEBSOCKETS_UNPACK)/lib/system/smd \

-I$(LIBWEBSOCKETS_UNPACK)/lib/system/metrics \

-I$(LIBWEBSOCKETS_UNPACK)/lib/core-net \

-I$(LIBWEBSOCKETS_UNPACK)/lib/roles \

-I$(LIBWEBSOCKETS_UNPACK)/lib/roles/http \

-I$(LIBWEBSOCKETS_UNPACK)/lib/roles/h1 \

-I$(LIBWEBSOCKETS_UNPACK)/lib/roles/h2 \

-I$(LIBWEBSOCKETS_UNPACK)/lib/roles/ws \

-I$(LIBWEBSOCKETS_UNPACK)/lib/tls \

-I$(LIBWEBSOCKETS_UNPACK)/lib/tls/mbedtls/wrapper/include/openssl \

-I$(LIBWEBSOCKETS_UNPACK)/lib/tls/mbedtls/wrapper/include/internal \

-I$(LIBWEBSOCKETS_UNPACK)/lib/tls/mbedtls/wrapper/include/platform

继续修改“apps/netutils/libwebsockets/Makefile”,加入最后1行:

CSRCS += \

$(LIBWEBSOCKETS_UNPACK)/lib/tls/tls.c \

$(LIBWEBSOCKETS_UNPACK)/lib/tls/tls-network.c \

$(LIBWEBSOCKETS_UNPACK)/lib/tls/tls-sessions.c \

$(LIBWEBSOCKETS_UNPACK)/lib/tls/tls-client.c \

$(LIBWEBSOCKETS_UNPACK)/lib/tls/mbedtls/mbedtls-tls.c \

$(LIBWEBSOCKETS_UNPACK)/lib/tls/mbedtls/mbedtls-extensions.c \

$(LIBWEBSOCKETS_UNPACK)/lib/tls/mbedtls/mbedtls-x509.c \

$(LIBWEBSOCKETS_UNPACK)/lib/tls/mbedtls/mbedtls-ssl.c \

$(LIBWEBSOCKETS_UNPACK)/lib/tls/mbedtls/mbedtls-session.c \

$(LIBWEBSOCKETS_UNPACK)/lib/tls/mbedtls/mbedtls-client.c \

$(LIBWEBSOCKETS_UNPACK)/lib/tls/mbedtls/wrapper/platform/ssl_pm.c # 100ask

修改“apps/crypto/openssl_mbedtls_wrapper/mbedtls/ssl_lib.c”,加入下面的代码:

/* 100ask */

int SSL_want_write(const SSL \*ssl)

{

SSL_ASSERT1(ssl);

if (ssl-\>err)

return 0;

return (SSL_want(ssl) == SSL_WRITING);

}

修改“apps/crypto/openssl_mbedtls_wrapper/mbedtls/err.c”,加入下面的代码:

/* 100ask */

char \*ERR_error_string(unsigned long e, char \*buf)

{

return "unknown";

}

然后先清除再编译、打包,命令如下:

m c \# 必须先清除

m

pack

烧写映像文件后启动,先配置、测试WIFI(最好接上天线):

ifup wlan0

wapi mode wlan0 2

# 100asktech 是WIFI密码,需要改成你自己的

wapi psk wlan0 100asktech 3

# Programmers是WIFI AP名字,需要改成你自己的

wapi essid wlan0 Programmers 1

renew wlan0

# 查看IP

ifconfig

# ping互联网, Ctrl+C退出

ping news.qq.com

再执行http命令:

websocket_test

任务间通信功能开发​

代码为:ipc_udp.c、ipc_udp.h。

接口函数​

我们使用UDP网络通信实现任务之间的数据传输,它有如下优点:

  • 无需创建连接,想发送时可以即刻发送

  • 通信双方没有依赖,不必等对方先运行,大不了数据丢失

  • 不必维护数据缓冲区

ipc_endpoint_t是核心结构体,它的内部成员为:

  • send:发送数据的函数

  • recv:接受数据的函数

  • cb:回调函数,如果提供cb函数,会创建一个线程循环等待、读取数据,读到后使用cb来处理。

结构体定义如下:

/**

* 传输的各方被称为endpoint

* 数据结构体,包含套接字、端口、服务器地址和回调函数

*/

typedef struct ipc_endpoint_t {

void \*priv;

void \*user_data;

transfer_callback_t cb; // 接收到远端的客户端发来的信息后使用它来处理

int (\*send)(struct ipc_endpoint_t \*self, const char \*data, int len); // 发送数据的函数指针

int (\*recv)(struct ipc_endpoint_t \*self, unsigned char \*data, int maxlen, int \*retlen); // 接收数据的函数指针

} ipc_endpoint_t, \*p_ipc_endpoint_t;

接口函数只有2个:

// 创建一个UDP类型的IPC端点

p_ipc_endpoint_t ipc_endpoint_create_udp(int port_local, int port_remote, transfer_callback_t cb, void \*user_data);

// 销毁IPC端点,释放相关资源

void ipc_endpoint_destroy_udp(p_ipc_endpoint_t pendpoint);

使用示例​

以arecord为例,它读取到声音数据后要发送给控制中心,它如下初始化、使用IPC:

static p_ipc_endpoint_t g_ipc_ep_audio_upload;

g_ipc_ep_audio_upload = ipc_endpoint_create_udp(0, AUDIO_PORT_UP, NULL, NULL);

g_ipc_ep_audio_upload-\>send(g_ipc_ep_audio_upload, opus_buffer, encoded_bytes);

第2行:创建IPC对象时,只传入了远端端口AUDIO_PORT_UP,以后调用第3行来发送数据时,它内部会把数据通过UPD发送给远端。

以aplay为例,它调用recv函数等待网络数据,读到网络数据后再解码、播发:

static p_ipc_endpoint_t g_ipc_ep_audio_download;

g_ipc_ep_audio_download = ipc_endpoint_create_udp(AUDIO_PORT_DOWN, 0, NULL, NULL);

while (0 != g_ipc_ep_audio_download-\>recv(g_ipc_ep_audio_download, opusbuf,

MAX_PACKET_SIZE, &opuslen));

第2行:创建IPC对象时,只传入了本地端口AUDIO_PORT_DOWN,以后调用第3行来读取数据时,内部就是读取该端口。

以控制中心为例,它要等待arecrod上报的音频数据,接受到之后马上使用websocket发给服务器。创建IPC时可以提供回调函数,这会导致控制中心创建了一个后台线程,它会读网络数据、然后调用回调函数发送给服务器:

static p_ipc_endpoint_t g_ipc_ep_audio;

g_ipc_ep_audio = ipc_endpoint_create_udp(AUDIO_PORT_UP, AUDIO_PORT_DOWN, process_opus_data_uploaded, NULL);

声音程序与控制中心演示​

源码位于如下目录:

上传源码​

把aplay.c、arecord.c放到如下目录:

/home/ubuntu/vela-opensource/vendor/allwinnertech/chips/r528/drivers/rtos-hal/hal/test/sound

把“control_center”整个目录放到如下目录:

/home/ubuntu/vela-opensource/apps/examples

修改编译脚本​

修改“/home/ubuntu/vela-opensource/apps/examples/Kconfig”,添加:

source "/home/ubuntu/vela-opensource/apps/examples/control_center/Kconfig"

修改“/home/ubuntu/vela-opensource/vendor/allwinnertech/chips/r528/drivers/rtos-hal/hal/test/sound/Makefile”,添加:

CFLAGS += \${INCDIR_PREFIX}\$(APPDIR)/examples/control_center/

配置openvela​

CONFIG_EXAMPLES_CONTROL_CENTER=y

CONFIG_EXAMPLES_CONTROL_CENTER_STACKSIZE=204800 # 这个栈要大一点

CONFIG_NET_UDP_MAX_CONNS=0 # 不限制UDP socket的数量

使用小智AI机器人​

先连接WIFI:

ifup wlan0

wapi mode wlan0 2

wapi psk wlan0 100asktech 3

wapi essid wlan0 Programmers 1

renew wlan0

ifconfig

ping news.qq.com

再设置声卡:

amixer set 19 255

amixer set 20 255

amixer set 21 255

amixer set 6 180

amixer set 7 180

amixer set 15 7

接着运行程序:

arecord -D hw:snddmic -r 48000 -f 16 -c 2 -o &

aplay -D hw:audiocodec -r 48000 -f 16 -c 2 -o &

control_center &

根据提示信息,在<https://xiaozhi.me/&gt;登录后激活设备,即可跟板子进行语音聊天。

声音程序与控制中心代码阅读​

代码框架:

arecord​

功能:读取声卡麦克风数据,使用opus编码,发送给control_center。

aplay​

功能:等待control_center发来声音数据,使用opus解码,播放。

control_center​

核心作用有4:

① 创建IPC端点:跟arecord、aplay、GUI程序通信

② 使用HTTP激活、认证设备

③ 建立websocket连接

④ 通过websocket发送音频数据给服务器,接收并处理服务器下发的音频数据、文本数据

先创建2个IPC线程:线程A用于等待arecord上报的音频数据然后发送给服务器,线程B用于等待GUI发来的数据然后处理(为空函数,留待扩展)。

其中“process_opus_data_uploaded”函数为:

然后使用http协议激活、验证设备:

最后启动websocket线程:

① 提供函数给线程A发送音频数据给服务器

② 等待服务器的数据:

对于bin数据(就是音频)直接发送给aplay进程:

对于txt数据则分别处理:

收到hello信息(握手)时,上报设备的iot参数(比如设备上的LED信息等)、通知服务器启动监听(start,开始处理上报的语音)。

收到stt(用户说话后,服务器转换为文字):就发送给GUI显示出来。

收到llm:把表情转发给GUI显示出来。

收到tts(服务器的回复,先传文本):发送给GUI显示出来。