#创作者激励#【FFH】OpenHarmony南向研究(7)-TCP(WPA) 原创 精华
【本文正在参加2023年第一期优质创作者激励计划】
基于标准系统的TCP和WAP使用-实现一个远程示波器
1 综述
近期在学习标准系统wifi能力子系统过程中同时也在调试一台OpenHarmony小车底盘机,因为不能像以前调试STM32使用 Cube monitor进行方便的数据观测,进行PID和其他控制算法调参,完成小车的闭环自动控制,所以自己使用wpa子系统构建wifi访问,在本地widows主机上基于easyX构建了一套示波器用于PID参数调测,链路传输使用TCP进行数据传输。
2 关于标准系统南向设备应用程序开发的建议
一般对于新操作系统,尤其是类似OHOS这类不能够支持本地化编程,目前还没能移植VIM,GCC等工具进行非侵入式编程的操作系统,一般会使用linux本地机器进行程序验证,再加载到新操作系统的环境当中进行适配编译,这样可以有效减少编译浪费的时间。
3WPA子系统配置自启动开发
3.1WPA子系统简介
OpenHarmony的wifi能力使用基于第三方开源wifi软件WPA-supplicant子系统完成,这部分在OpenHarmony系统中进行了深度移植和融合
wpa_supplicant是跨平台的开源软件,支持WPA、WPA2、WPA3(IEEE 802.11i)等Wlan接入技术,可用于台式机、笔记本电脑、手机和其他嵌入式系统。
wpa_supplicant
支持多种网络管理器,如 NetworkManager 和 wicd,也可以通过命令行界面直接使用。用户可以通过编辑 wpa_supplicant
配置文件来配置连接的 Wi-Fi 网络的参数,例如 SSID、密码、加密类型等。
除了作为 Wi-Fi 客户端,wpa_supplicant
还支持将 Linux 计算机配置为 Wi-Fi 热点,使其他设备能够通过该计算机连接到 Wi-Fi 网络。
在OpenHarmony中,wpa_supplicant用来提供Wi-Fi接入和Wi-Fi热点开启的协议栈。
我们使用WPA相关的配置和设置来进行wifi配置,
接口层 :提供wpa_supplicant与Wi-Fi服务通信的接口。
wpa协议栈 :提供Wi-Fi接入与热点功能,其中wpa_supplicant负责Wi-Fi接入,hostapd负责热点功能。
我们主要使用接口层的能力,进行两种类型的配置:
- wifi连接 即本机设置为STA模式,连接到其他热点
- 热点模式 即本机设置为AP模式,其他设备可以链接到本机
配置为任意一种形式取决于当前场景的需要接下来对这两种配置模式进行分析和实操
3.2STA模式
此处需要wpa配置文件从而使能联网配置,如果使用STA模式需要创建一个 wpa_supplicant.conf 配置文件其中包含要连接的 Wi-Fi 网络的 SSID 和密码等信息
network={
ssid="your_SSID"
psk="your_password"
}
注意替换 your_SSID
和 your_password
为你要连接的 Wi-Fi 网络的 SSID 和密码。
在文件处理阶段有个核心问题,如果我们想把WIFI配置作为初始文件写入系统,无法在编码阶段创造好该文件,在打包结束后我们自己写的定义设置文件会丢失或者无法找到相对路径,这时候可以通过重新添加文件分区的方式进行添加,需要用到如下工具,当然如果非必须要开机初始化,可以使用hdcstd工具send功能将文件传输到系统当中,并作为参数文件调用。
如果重新创建分区要使用到芯片厂商的打包工具以及开源打包工具mtd 通过以下指令安装和打包,打包后可以将原本的OpenHarmony.img通过上述工具amologic-toolkits进行解包,再重写进行打包,完成后可以重新烧录
sudo apt-get install mtd-utils
sudo mkfs.jffs2 -o userfs.img -d userfs
(第二段指令将userfs文件打包为userfs.img镜像文件)
在开发板上电后连接到终端控制台
输入指令
./bin/wpa_supplicant -i wlan0 -c /userfs/wpa_supplicant.conf
执行wpa_supplicant 脚本,配置conf文件到系统中,连接到指定网络。
3.3AP模式(未测试)
将conf文件编辑为
ctrl_interface=/var/run/wpa_supplicant
ctrl_interface_group=0
update_config=1
network={
ssid="my_hotspot"
mode=2
frequency=2412
proto=RSN
key_mgmt=WPA-PSK
pairwise=CCMP TKIP
group=CCMP TKIP
psk="my_password"
}
重复在STA模式中配置的方式,用以下命令启动热点
wpa_supplicant -B -i wlan0 -c userfs/wpa_supplicant.conf
-B 表示在后台执行 ,-i表示参数后跟无线接口名称, -c 是conf文件的路径。
3.4WPA命令行常用指令
wpa_supplicant -iwlan0 -Dnl80211 -c/etc/ambaipcam/IPC_Q313/config/wlan/wpa_supplicant.conf -B
#查询网卡状态
wpa_cli -p/var/run/wpa_supplicant -iwlan0 status
#搜索附近网络功能 no/ok
wpa_cli -i wlan0 scan
#搜索附近网络,并列出结果
wpa_cli -i wlan0 scan_result
#查看当前连接的是哪个网络
wpa_cli -i wlan0 list_network
#获取一个存储wifi结构的id,假设为1
wpa_cli -i wlan0 add_network
#设置ID为1的热点SSID
wpa_cli -i wlan0 set_network 1 ssid '"HO4428"'
#设置ID为1的热点的密码
wpa_cli -i wlan0 set_network 1 psk '"442701102"'
#设置加密方式,可以不加
wpa_cli -i wlan0 set_network 1 key_mgmt WPA2-PSK-CCMP
#启动连接ID为1的网络
wpa_cli -i wlan0 enable_network 1
#选择网络0(这样做的好处在于,可以取消先前的其它网络连接)
wpa_cli -i wlan0 select_network 1
#保存刚刚填写的wifi帐号,写入配置文件
wpa_cli -i wlan0 save_config
#请求自动分配IP地址,-b:后台(back) –i:指定接口 –q:获得续约后退出,如果想尽在前台运行,则去掉-b,加上-f
#不用不加,会自动分配
udhcpc -b -i wlan0 -q
3.5查询IP地址
使用ipconfig指令查询当前网络地址
ipconfig
4TCP 实现
4.1TCP基本概念和主要特点
TCP(Transmission Control Protocol)是一种传输层协议,用于在网络上可靠地传输数据。TCP 是一种面向连接的协议,使用三次握手建立连接,并使用四次挥手终止连接。
TCP 的主要特点包括:
- 可靠性:TCP 通过序列号和确认应答机制,确保数据传输的可靠性和完整性。如果数据包在传输过程中丢失或损坏,TCP 会自动重传该数据包,直到接收方收到正确的数据为止。
- 流量控制:TCP 使用滑动窗口机制来控制数据的流量,确保发送方和接收方之间的数据传输速度相匹配,防止数据包拥塞和丢失。
- 拥塞控制:TCP 可以检测网络拥塞情况,并采取相应的措施,例如降低数据传输速度,以避免拥塞的发生。
- 面向连接:TCP 使用三次握手建立连接,并使用四次挥手终止连接,确保数据传输的可靠性和完整性。
4.2使用C实现TCP的基本操作
在LINUX中使用socket通信方式的TCP协议来实现数据传输,需要分别实现服务器端,和客户端,在这次的样例实现当中,九联UnionPi开发板作为服务端,因为有多个设备需要接入。
4.2.1TCP服务端样例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8080 // 监听的端口号
#define BUFFER_SIZE 1024 // 缓冲区大小
int main() {
int server_fd, new_socket, valread;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
char *hello = "Hello from server";
// 创建 socket 文件描述符
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置 socket 选项,允许地址重用
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT,
&opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
// 设置地址族、端口号和 IP 地址
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定 socket 和地址
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
// 等待新连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address,
(socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
// 读取客户端发送的数据
valread = read(new_socket, buffer, BUFFER_SIZE);
printf("%s\n", buffer);
// 向客户端发送数据
send(new_socket, hello, strlen(hello), 0);
printf("Hello message sent\n");
return 0;
}
4.2.2TCP客户端样例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080 // 目标端口号
#define BUFFER_SIZE 1024 // 缓冲区大小
int main(int argc, char const *argv[]) {
int sock = 0, valread;
struct sockaddr_in serv_addr;
char *hello = "Hello from client";
char buffer[BUFFER_SIZE] = {0};
// 创建 socket 文件描述符
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation error");
exit(EXIT_FAILURE);
}
// 设置目标地址族、端口号和 IP 地址
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// 将点分十进制 IP 地址转换为网络字节序
if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) {
perror("Invalid address/ Address not supported");
exit(EXIT_FAILURE);
}
// 连接到目标地址
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("Connection Failed");
exit(EXIT_FAILURE);
}
// 向服务端发送数据
send(sock, hello, strlen(hello), 0);
printf("Hello message sent\n");
// 读取服务端发送的数据
valread = read(sock, buffer, BUFFER_SIZE);
printf("%s\n", buffer);
return 0;
}
在案例中实现了简单的TCP客户端和服务端,客户端向服务端发送HELLO。
5OHOS端实现线程通信部署和数据发送
5.1目录结构
在之前的开发中对于小车的IMU数据进行了收取,以及小车底盘,蓝牙模块等进行了控制,通过工程化设计,分别划分在不同的线程当中,并进行了低耦合高内聚的函数化设计。
├── CMakeLists.txt
├── main.c
├── main.h
├── pthread_usr.c
├── pthread_usr.h
├── serial_base.c
├── serial_base.h
├── serial_protocal.c
└── serial_protocal.h
usr_pthread 负责进行所有线程的实现,main负责线程入口控制和初始化设置。线程间使用消息队列进行通信,因为存在IMU加速度计等信息高频刷新需要进行数据低延迟发送。serial_base和serial_protocal分别实现了串口的底层驱动和串口基础协议。
5.2 消息队列
消息队列样例演示线程1 向线程2发送数据:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <mqueue.h>
#define QUEUE_NAME "/my_queue" // 消息队列名称
#define MESSAGE_SIZE 256 // 消息大小
#define MAX_MESSAGES 10 // 消息队列中最大消息数
#define QUEUE_PERMISSIONS 0660 // 消息队列权限
void *sender_thread(void *arg);
void *receiver_thread(void *arg);
int main(int argc, char const *argv[]) {
mqd_t mq;
pthread_t sender, receiver;
int ret;
// 创建消息队列
struct mq_attr attr = {
.mq_flags = 0,
.mq_maxmsg = MAX_MESSAGES,
.mq_msgsize = MESSAGE_SIZE,
.mq_curmsgs = 0
};
mq_unlink(QUEUE_NAME); // 删除已存在的消息队列
mq = mq_open(QUEUE_NAME, O_CREAT | O_RDWR, QUEUE_PERMISSIONS, &attr);
if (mq == -1) {
perror("mq_open");
exit(EXIT_FAILURE);
}
// 创建发送线程和接收线程
ret = pthread_create(&sender, NULL, sender_thread, &mq);
if (ret != 0) {
perror("pthread_create");
exit(EXIT_FAILURE);
}
ret = pthread_create(&receiver, NULL, receiver_thread, &mq);
if (ret != 0) {
perror("pthread_create");
exit(EXIT_FAILURE);
}
// 等待线程退出
pthread_join(sender, NULL);
pthread_join(receiver, NULL);
// 关闭消息队列
mq_close(mq);
mq_unlink(QUEUE_NAME);
return 0;
}
void *sender_thread(void *arg) {
mqd_t mq = *(mqd_t *)arg;
char message[MESSAGE_SIZE];
for (int i = 0; i < MAX_MESSAGES; i++) {
sprintf(message, "Message %d", i);
if (mq_send(mq, message, strlen(message) + 1, 0) == -1) {
perror("mq_send");
exit(EXIT_FAILURE);
}
printf("Sent message: %s\n", message);
sleep(1);
}
pthread_exit(NULL);
}
void *receiver_thread(void *arg) {
mqd_t mq = *(mqd_t *)arg;
char message[MESSAGE_SIZE];
unsigned int priority;
for (int i = 0; i < MAX_MESSAGES; i++) {
if (mq_receive(mq, message, MESSAGE_SIZE, &priority) == -1) {
perror("mq_receive");
exit(EXIT_FAILURE);
}
printf("Received message: %s\n", message);
}
pthread_exit(NULL);
}
在所有的线程当中都实现了类似的消息队列结构来保证数据的完整性和实效性。
绑定了socket通信格式和维特智能提供的IMU SDK后架构如下,不详细展开描述,只描述与示波器相关部分。
5.3 TCP线程
(暂时这部分还有一些BUG,以及没有完整封装,在构建完成后会重新更新代码仓库)
通过一个线程使用socket通信获取PID控制器返回的数据存入发送缓冲区,再将其发送出去
void *_cpp_tcp_client(void) {
// 设置 socket 选项,允许地址重用
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT,
&opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
// 设置地址族、端口号和 IP 地址
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定 socket 和地址
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
// 2. 发送数据,并接受客户端数据
char send_info[256] = {0};
//int n = -129;
//char nums[10];
//itostr(nums, n);
// printf("%s\n", nums);
int out_tcp_client;
while (1) {
if (IMU_INIT_FLAG == 0) {
sleep(10);
continue;
}
// if(pid.out>0)out_tcp_client=pid.out-60;
// else out_tcp_client=pid.out+60;
itostr(send_info, (int) pid.ActualAngle);
send(sock_client, send_info, strlen(send_info) + 1, 0);
printf("pid_out: %f,send: %s\n", pid.out, send_info);
usleep(3000);
//char recv_info[50];
//recv(sock_client, recv_info, sizeof(recv_info), 0);
//printf("receive: %s\n", recv_info);
memset(send_info, '\0', 256);
}
// 3. 关闭客户端
close(sock_client);
return 0;
}
整套小车控制程序在实现小车的偏航角闭环,此时要发送到客户端示波器的数据是当前角度。
我们现在将pid.actangle发送到17.20.10.1
server_addr.sin_addr.s_addr = inet_addr("172.20.10.1");
6Windows端实现示波器
6.1 使用easyX图形库绘制波形图
由于熟悉的绘制波形图的图形库easyX在windows VS才能提供有效支持,所以暂时使用Windows的easyX图形库进行绘制
easyX图形库使用链接EasyX Graphics Library for C++
![image-20230327203843023]
6.2 使用Windows socket完成网络通信
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main(int argv, char* argc[])
{
//初始化
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
//创建套接字
SOCKET clntSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
//向服务器发送消息
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
sockAddr.sin_family = PF_INET;
sockAddr.sin_addr.S_un.S_addr = inet_addr("192.168.43.235");
sockAddr.sin_port = htons(8888);
connect(clntSock, (SOCKADDR*)& sockAddr, sizeof(SOCKADDR));
char szBuffer[MAXBYTE] = { 0 };
while (1) {
//接收服务器消息
recv(clntSock, szBuffer, MAXBYTE, NULL);
//输出接收到的数据
cout << "服务端:" << szBuffer << endl;
//向服务端发送消息
const char* str = "100";
send(clntSock, str, strlen(str) + sizeof(char), NULL);
Sleep(10);
}
//关闭套接字
closesocket(clntSock);
//终止dll
WSACleanup();
system("pause");
return 0;
}
6.3 结合两者完成示波器
切分为两个文件并把从socket读取到的数据放入示波器中进行展示
grsp.cpp
#include"grsp.h"
void num2WSTR(double x, wchar_t*& buffer)
{
// NUM TO WSS TO WSTR
std::wstringstream ss;
ss << x;
size_t size = ss.str().length();
buffer = new wchar_t[size + 1];
ss >> buffer;
buffer[size] = 0;
}
// DynaWin 构造函数
DynaWin::DynaWin(double XLIM, double YLIM, unsigned short xSegment, unsigned short ySegment, int xResolution)
{
this->XLIM = XLIM;
this->YLIM = YLIM;
this->xSegment = xSegment;
this->ySegment = ySegment;
this->xResolution = xResolution;
this->DynaQue = new double[xResolution + 1];
this->xAxis = new AxisX[xSegment + 1];
}
//背景初始化
void DynaWin::InitBackGround()
{
// 定义画布
initgraph(1100, 400, EX_SHOWCONSOLE);
// 初始化画笔
setlinecolor(RED);
setlinestyle(PS_SOLID | PS_ENDCAP_ROUND | PS_JOIN_ROUND, 3);
// 定义坐标原点以及方向
setorigin(CENTERX, CENTERY);
setaspectratio(1, -1);
// 绘制静态窗口
rectangle(0, 150, 1000, -150);
line(0, 0, 1000, 0);
// 标注说明
setbkmode(TRANSPARENT);
settextcolor(YELLOW);
setaspectratio(1, 1);// 纠正字体倒置
settextstyle(30, 0, L"宋体");
outtextxy(450, -185, L"EasyPlot");
settextstyle(10, 0, L"宋体");
outtextxy(850, 160, L"Author:Dylan");
outtextxy(870, 180, L"Version:1.0");
// 绘制刻度
unsigned short yVertex = 2 * ySegment + 1;
double yUnit = YLIM / ySegment;
setbkmode(TRANSPARENT);
settextcolor(YELLOW);
settextstyle(15, 0, L"10");
setlinestyle(PS_DASH, 1); // 虚线
// 水平刻度线
for (int i = 1; i <= yVertex; ++i)
{
// 表明 y 刻度值
double label = YLIM - (i - 1) * yUnit;
wchar_t* buffer;
num2WSTR(label, buffer);
outtextxy(-30, (int)((i - ySegment - 1) * (ROWS / ySegment)) - 10, buffer);
delete[] buffer;
buffer = (wchar_t*)NULL;
// 画出虚线
line(0, (int)((i - ySegment - 1) * (ROWS / ySegment)), 1000, (int)((i - ySegment - 1) * (ROWS / ySegment)));
}
setaspectratio(1, -1); // 恢复坐标方向
canvas = GetWorkingImage(); // 画板地址
getimage(&local, -50, -160, 1100, 320); // 保存局部背景
}
// 更新窗
void DynaWin::UpateWin(double y) // 更新 win
{
++DynaNum; // 信息个数
UpdateAxisX(); // 更新AxisX
if (DynaNum <= xResolution + 1) // 队列未满
DynaQue[DynaNum - 1] = y;
else // 队列已满
{
for (int i = 0; i <= xResolution - 1; ++i) // 出队列
{
DynaQue[i] = DynaQue[i + 1]; // 向左移位
}
DynaQue[xResolution] = y; // 尾部添加新元素
}
}
// 绘制窗
void DynaWin::ShowWin()
{
// 选定画板
SetWorkingImage(canvas);
// 背景覆盖
putimage(-50, -160, &local);
// (1) 显示 AxisX
setlinecolor(RED);
setlinestyle(PS_DASH, 2);
for (int j = 0; j <= xSegment - 1; ++j)
{
// 绘制虚线
line((int)xAxis[j].index, -150, (int)xAxis[j].index, 150);
// 绘制标注
setaspectratio(1, 1);
setbkmode(TRANSPARENT);
settextcolor(YELLOW);
settextstyle(15, 0, L"10");
double label = xAxis[j].label;
wchar_t* wstr;
num2WSTR(label, wstr);
outtextxy((int)xAxis[j].index - 10, 0, wstr);
delete[] wstr;
wstr = NULL;
setaspectratio(1, -1); // 回复坐标系
}
// (2) 显示 DynaQue
setlinecolor(WHITE);
setlinestyle(PS_SOLID | PS_ENDCAP_ROUND | PS_JOIN_ROUND, 2);
if (DynaNum <= xResolution) // 队列未满
{
for (int i = 0; i <= DynaNum - 2; ++i) // 0 to DynaNum-1 (DynaNum 个)
{
line((int)(i * COLS / xResolution), (int)(DynaQue[i] / YLIM * ROWS), (int)((i + 1) * COLS / xResolution), (int)(DynaQue[i + 1] / YLIM * ROWS));
}
}
else // 队列已满
{
for (int i = 0; i <= xResolution - 1; ++i) // 0 to xResolution (xResolution + 1 个) 即显示全部 DynaQue
{
line((int)(i * COLS / xResolution), (int)(DynaQue[i] / YLIM * ROWS), (int)((i + 1) * COLS / xResolution), (int)(DynaQue[i + 1] / YLIM * ROWS));
}
}
}
// 更新坐标
void DynaWin::UpdateAxisX()
{
if (DynaNum <= xResolution) // 队列未满
for (int i = 0; i <= xSegment - 1; ++i)
{
xAxis[i].index = (i + 1) * COLS / xSegment;
xAxis[i].label = (i + 1) * XLIM / xSegment;
}
else // 队列已满
{
for (int i = 0; i <= xSegment - 1; ++i) // 向左移动
{
xAxis[i].index -= COLS / xResolution;
}
if (xAxis[0].index <= 0) // AxisX 动态更新
{
int j;
for (j = 0; j <= xSegment - 2; ++j) // 队列更替
{
xAxis[j].index = xAxis[j + 1].index;
xAxis[j].label = xAxis[j + 1].label;
}
xAxis[j].index = xAxis[j - 1].index + COLS / xSegment; // 尾部添加新元素
xAxis[j].label = xAxis[j - 1].label + XLIM / xSegment;
}
}
}
grsp.h
#pragma once
#include <iostream>
#include <easyx.h>
#include <conio.h>
#include <sstream>
#include <string>
#define PI 3.141592
/*************** 定义动态 X 坐标结构体 ************************/
struct AxisX {
double index; // 坐标
double label; // 标签
};
/*************** 定义数字转 LPWSTR 字符型函数 ****************/
void num2WSTR(double x, wchar_t*& buffer);
/*************** 定义类:动态窗口 ****************************************************************************/
// 说明:动态窗口实现绘制实时信号的原理,其实是将当前信号送入定长队列 DynaQue,
// 并在每一帧进行绘制,其中队列长度由分辨率决定。最大分辨率应该 <= COLS
// 例如:DynaQue[xResolution+1] = {DynaQue[0],DynaQue[1,..,DynaQue[xRelolution]},其中 DynaQue[N] 为最近信号
// 那么相邻点距离为:COLS/xResolution
// 当然 X 坐标也需要动态更新,同理类中也定义了队列 xAxis 对坐标进行同步更新
// 例如:xAxis[xSegment+1] = {xAxis[0],xAxis[1],..,xAxis[xSegment]},其中 xAxis[N] 保存的是 AxisX 数据
// 那么相邻刻度距离:COLS/xSegment
/***************************************************************************************************************/
class DynaWin {
public:
DynaWin(double XLIM, double YLIM, unsigned short xSegment, unsigned short ySegment, int xResolution); // 构造器
void UpateWin(double y); // 更新窗口
void ShowWin(); // 绘制窗口
void InitBackGround(); // 背景初始化(静态窗口)
private:
const int CENTERX = 50; // 定义原点
const int CENTERY = 200;
const int ROWS = 150; // 定义边长
const int COLS = 1000;
double XLIM; // 定义 X 轴区间
double YLIM; // 定义 Y 轴区间
unsigned short xSegment; // 定义坐标的刻度
unsigned short ySegment;
int xResolution; // x轴分辨率(决定 Dynaque 的长度)
int DynaNum = 0; // 已读信号个数
double* DynaQue; // 实时信号队列
AxisX* xAxis; // 实时坐标队列
IMAGE* canvas; // 当前画布
IMAGE local; // 局部背景
void UpdateAxisX(); // 更新坐标
};
socket.c
#include <iostream>
#include <WinSock2.h>
#include <Ws2tcpip.h>
#include "grsp.h"
#pragma comment(lib,"ws2_32.lib") //加载ws2_32.dll
using namespace std;
int String2Int(char* str)//字符串转数字
{
char flag = '+';//指示结果是否带符号
long res = 0;
if (*str == '-')//字符串带负号
{
++str;//指向下一个字符
flag = '-';//将标志设为负号
}
sscanf_s(str, "%ld", &res);
if (flag == '-')
{
res = -res;
}
return (int)res;
}
int main(int argv, char* argc[])
{
DynaWin win(2000, 360, 2, 5, 500);
win.InitBackGround(); //背景初始化
win.UpateWin(0); // 更新窗
int i = 0;
// 批量绘制:绘制一个y=sin(wt),其中 w = 2*π*f , t = n*T0 =n/Fs,这里我们绘制 f = 5 的正弦信号
BeginBatchDraw();
//初始化
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
//创建套接字
SOCKET servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
// 本机地址
char hostAddress[] = "172.20.10.1";
//绑定套接字
struct sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节用0填充
sockAddr.sin_family = AF_INET; //使用ipv4
inet_pton(AF_INET, hostAddress, &sockAddr.sin_addr.S_un.S_addr);
sockAddr.sin_port = htons(8888); //端口
bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
//进入监听状态
listen(servSock, 20);
SOCKADDR clntAddr;
int nSize = sizeof(SOCKADDR);
SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);
if (clntSock == -1)
{
printf("failed");
return -1;
}
else printf("yes");
//示波器初始化
// 定义动态窗口:
// X 坐标:长度 = 2 * PI, 10 个刻度,500 分辨率(采样点)
// Y 坐标:长度 = 1, 5 个刻度
char* szBuffer=new char[MAXBYTE];
szBuffer[0] = '\0';
while (1) {
if (szBuffer != "\0") {
recv(clntSock, szBuffer, MAXBYTE, NULL);
}
//向客户端发送消息
//const char* str = "hello client";
//send(clntSock, str, strlen(str) + sizeof(char), NULL);
cout << "客户端:" << szBuffer << endl;
win.UpateWin(String2Int(szBuffer)); // 更新窗
win.ShowWin(); // 显示窗
FlushBatchDraw();
Sleep(10);
++i;
szBuffer = new char[MAXBYTE];
szBuffer[0] = '\0';
}
closesocket(clntSock);
closesocket(servSock);
//终止dll使用
WSACleanup();
system("pause");
return 0;
}
7总结与注意事项
注意事项
- 在socket中进行地址族设置时,服务器端将地址设置为本地地址,客户端将地址设置为服务器端地址。
- easyX的环境配置要注意属性设置和父类集成
- 示波器响应速度提升可以通过减少冗余数据实现,以及车机滤波器响应参数调高,IMU的频率调高,可以实现更加有效的参数调制。
- OpenHarmony的wpa子系统使用与linux的基本一致,可以很方便的完成wifi配置
车机迭代图片记录
第一代 OpenHarmony作为ROS节点运行
第二代 OpenHarmony作为车体主控平台运行
总结
使用OpenHarmony进行车机控制可以获得比较好的实时性,比普通liunx响应速度要快,代码体量子系统复杂度小,但是编译和代码测试时间久,各有优劣,目前完成的OpenHarmony版本全向运动小车底盘还将加入更多功能,包括,视觉识别,人体跟踪等,在近期的下一步工作中将小车底盘适配为线性控制模型,加入LQR算法和MPC算法验证小车控制以及UnionPi OpenHarmony开发板进行车机控制的算力支持情况。
整个搭建过程非常完整,必须点赞支持一下
OpenHarmony的使用比之前方便多了
这个小车的功能真是丰富
小车型号能分享下吗
没有看到怎么自启动
总结的很全面