XR806开发板MQTT电源智能控制器

非常感谢这次极术社区,借助对Xr806开发板的试用,接触到了鸿蒙harmonyos,使用一定过程历时较长,也是一点点摸索,得到了很好学习机会。
社区里很多文章,感谢各位大佬的文章指点,本次试用主要是参考了两位大佬的, 【XR806开发板试用】儿童遥控挖掘机无线化升级改造 和 【XR806开发板试用】基于MQTT与Cjson库的花式点灯,其他大佬的文章也有翻阅学习。
##环境搭建##
使用Ubuntu20.04的虚拟机,尝试了很久,最后只要能运行Hellworld示例说明环境就算是正常了。
##外部硬件##
一个5V的4路继电器
在这里插入图片描述

##接线方式##
在这里插入图片描述

##代码部分##
建文件夹mymqttrelay
下面建文件BUILD.gn

import("//device/xradio/xr806/liteos_m/config.gni")

static_library("app_mymqttrelay") {
   configs = []

   sources = [
      "src/main.c",
   ]

   cflags = board_cflags

   include_dirs = board_include_dirs
   include_dirs += [
      "//kernel/liteos_m/kernel/arch/include",
      "//base/iot_hardware/peripheral/interfaces/kits",
      "include",

      ".",
      "//utils/native/lite/include",
      "//foundation/communication/wifi_lite/interfaces/wifiservice",
      "//device/xradio/xr806/xr_skylark/project"
   ]
}

建src/main.cs核心代码

#include <stdio.h>
#include <string.h>
#include "ohos_init.h"
#include "kernel/os/os.h"

#include "iot_gpio.h"
#include "wifi_device.h"
#include "common/framework/net_ctrl.h"
#include "net/mqtt/MQTTClient-C/MQTTClient.h"
#include "driver/chip/hal_pwm.h"

static OS_Thread_t g_main_thread;
static OS_Thread_t g_mqtt_thread;


#define PWM_OUTPUT_CHL        PWM_GROUP1_CH2
#define PWM_OUTPUT_MODE       PWM_CYCLE_MODE

#define WIFI_DEVICE_CONNECT_AP_SSID "Xiaomi"//路由器的SSID
#define WIFI_DEVICE_CONNECT_AP_PSK "123456789"//路由器的PWD


#define MQTT_DEMO_CLIENT_ID "mqtt_xr806"
#define MQTT_DEMO_HOST_NAME "broker-cn.emqx.io"//这个是免费调试用的MQTT服务器地址
#define MQTT_DEMO_PORT      "1883"
#define MQTT_DEMO_USERNAME  "xr806_0001"
#define MQTT_DEMO_PASSWORD  "12345678"
#define MQTT_RESP_TOPIC "/test/relay" 
#define MQTT_RECV_TOPIC  "/show/relay" // TOPIC
#define MQTT_DEMO_BUF_SIZE (2*1024)

static MQTTPacket_connectData mqtt_demo_connectData = MQTTPacket_connectData_initializer;
static Client mqtt_demo_client;
static Network mqtt_demo_network;
static int max_duty_ratio = 0;

static int mqtt_demo_publish(char *topic, char *msg) ;

static int mqtt_demo_init(void) {
    char *send_buf;
    char *recv_buf;

    mqtt_demo_connectData.clientID.cstring = MQTT_DEMO_CLIENT_ID;
    mqtt_demo_connectData.keepAliveInterval = 30; // 30s
    mqtt_demo_connectData.cleansession = 0;
    mqtt_demo_connectData.MQTTVersion = 4; //Version of MQTT 3.1.1

    send_buf = malloc(MQTT_DEMO_BUF_SIZE);
    if (send_buf == NULL) {
        printf("no memory\n");
        return -1;
    }
    recv_buf = malloc(MQTT_DEMO_BUF_SIZE);
    if (recv_buf == NULL) {
        free(send_buf);
        printf("no memory\n");
        return -1;
    }

    /* init network */
    NewNetwork(&mqtt_demo_network);
    /* init mqtt client object */
    MQTTClient(&mqtt_demo_client, &mqtt_demo_network, 6000,
               (unsigned char *)send_buf, MQTT_DEMO_BUF_SIZE,
               (unsigned char *)recv_buf, MQTT_DEMO_BUF_SIZE);

    /* set username and password */
    mqtt_demo_connectData.username.cstring = MQTT_DEMO_USERNAME;
    mqtt_demo_connectData.password.cstring = MQTT_DEMO_PASSWORD;
    return 0;
}

static int mqtt_demo_connect(char *host_name, char *host_port) {
    int ret = -1;

    ret = ConnectNetwork(&mqtt_demo_network, host_name, atoi(host_port));
    if (ret != 0) {
        printf("mqtt connect faild, ret:%d, host:%s, port:%s\n", ret, host_name, host_port);
        goto exit;
    }

    ret = MQTTConnect(&mqtt_demo_client, &mqtt_demo_connectData);
    if (ret != 0) {
        printf("mqtt connect faild, ret:%d\n", ret);
        mqtt_demo_network.disconnect(&mqtt_demo_network);
        goto exit;
    }
    printf("mqtt connected\n");
exit:
    return ret;
}

static void mqtt_demo_msg_cb(MessageData *data) {
    printf("get a message, topic: %.*s, msg: %.*s\n", data->topicName->lenstring.len,
           data->topicName->lenstring.data, data->message->payloadlen,
           (char *)data->message->payload);
    char *temptest = (char *)data->message->payload;
    if(!strncmp(data->topicName->lenstring.data, "/show/relay", 11) && data->message->payloadlen) {
        if(!strcmp(data->message->payload,"relay01")){
            IoTGpioSetOutputVal(GPIO_ID_PA23, 0);
            IoTGpioSetOutputVal(GPIO_ID_PA22, 0);
            IoTGpioSetOutputVal(GPIO_ID_PA21, 0);
            IoTGpioSetOutputVal(GPIO_ID_PA20, 1);
            printf("mqtt------relay01");
        }else if(!strcmp(data->message->payload,"relay02")){
            IoTGpioSetOutputVal(GPIO_ID_PA23, 0);
            IoTGpioSetOutputVal(GPIO_ID_PA22, 0);
            IoTGpioSetOutputVal(GPIO_ID_PA21, 1);
            IoTGpioSetOutputVal(GPIO_ID_PA20, 0);
            printf("mqtt------relay02");
        }else if(!strcmp(data->message->payload,"relay03")){
            IoTGpioSetOutputVal(GPIO_ID_PA23, 0);
            IoTGpioSetOutputVal(GPIO_ID_PA22, 1);
            IoTGpioSetOutputVal(GPIO_ID_PA21, 0);
            IoTGpioSetOutputVal(GPIO_ID_PA20, 0);
            printf("mqtt------relay03");
        }else if(!strcmp(data->message->payload,"relay04")){
            IoTGpioSetOutputVal(GPIO_ID_PA23, 1);
            IoTGpioSetOutputVal(GPIO_ID_PA22, 0);
            IoTGpioSetOutputVal(GPIO_ID_PA21, 0);
            IoTGpioSetOutputVal(GPIO_ID_PA20, 0);
            printf("mqtt------relay04");
        }else {
            IoTGpioSetOutputVal(GPIO_ID_PA23, 0);
            IoTGpioSetOutputVal(GPIO_ID_PA22, 0);
            IoTGpioSetOutputVal(GPIO_ID_PA21, 0);
            IoTGpioSetOutputVal(GPIO_ID_PA20, 0);
        }


        char *payload = data->message->payload;
        char str[8] = "";
        int max_len = data->message->payloadlen > 3 ? 3 : data->message->payloadlen;
        strncpy(str, payload, max_len);
        int duty = atoi(str);
        HAL_Status status = HAL_PWM_ChSetDutyRatio(PWM_OUTPUT_CHL, duty * max_duty_ratio / 100);
        if (status != HAL_OK)
            printf("%s(): %d, PWM set duty ratio error\n", __func__, __LINE__);
        
        if(duty) {
            mqtt_demo_publish(MQTT_RESP_TOPIC, "light on");
        } else {
            mqtt_demo_publish(MQTT_RESP_TOPIC, "light off");
        }
    } 
}

static int mqtt_demo_subscribe(char *topic) {
    int ret = -1;
    if (mqtt_demo_client.isconnected) {
        ret = MQTTSubscribe(&mqtt_demo_client, topic, 0, mqtt_demo_msg_cb);
        if (ret != 0)
            printf("mqtt subscribe faild ret:%d\n", ret);
    }
    return ret;
}

static int mqtt_demo_unsubscribe(char *topic) {
    int ret = -1;
    if (mqtt_demo_client.isconnected) {
        ret = MQTTUnsubscribe(&mqtt_demo_client, topic);
        if (ret != 0)
            printf("mqtt unsubscribe faild, ret:%d\n", ret);
    }
    return ret;
}

static int mqtt_demo_publish(char *topic, char *msg) {
    int ret = -1;

    MQTTMessage message;
    memset(&message, 0, sizeof(message));
    message.qos = 0;
    message.retained = 0; /* disable retain the message in server */
    message.payload = msg;
    message.payloadlen = strlen(msg);
    ret = MQTTPublish(&mqtt_demo_client, topic, &message);
    if (ret != 0)
        printf("mqtt publish faild, ret:%d\n", ret);
    return ret;
}

static int mqtt_demo_disconnect(void) {
    int ret = -1;

    if (mqtt_demo_client.isconnected) {
        ret = MQTTDisconnect(&mqtt_demo_client);
        if (ret != 0)
            printf("mqtt disconnect fail, ret:%d\n", ret);
        mqtt_demo_network.disconnect(&mqtt_demo_network);
    }
    return ret;
}

static void mqtt_demo_deinit(void) {
    if (mqtt_demo_client.buf) {
        free(mqtt_demo_client.buf);
        mqtt_demo_client.buf = NULL;
    }
    if (mqtt_demo_client.readbuf) {
        free(mqtt_demo_client.readbuf);
        mqtt_demo_client.readbuf = NULL;
    }
}

static void mqtt_task(void *arg)   {
    int ret;
    int reconnect_times = 0;

    mqtt_demo_init();

    ret = mqtt_demo_connect(MQTT_DEMO_HOST_NAME, MQTT_DEMO_PORT);
    if (ret != 0)
        goto exit;

    ret = mqtt_demo_subscribe(MQTT_RECV_TOPIC);
    if (ret != 0)
        goto exit;

    mqtt_demo_publish(MQTT_RESP_TOPIC, "light ready");

    while (1) {
        ret = MQTTYield(&mqtt_demo_client, 300);
        if (ret != 0) {
            printf("mqtt yield err, ret:%d\n", ret);
reconnect:
            printf("mqtt reconnect\n");
            mqtt_demo_disconnect();
            ret = mqtt_demo_connect(MQTT_DEMO_HOST_NAME, MQTT_DEMO_PORT);
            if (ret != 0) {
                reconnect_times++;
                if (reconnect_times > 5)
                    goto exit;
                OS_MSleep(5000); //5s
                goto reconnect;
            }
        }
    }

exit:
    mqtt_demo_unsubscribe(MQTT_RECV_TOPIC);
    mqtt_demo_disconnect();
    mqtt_demo_deinit();
    OS_ThreadDelete(&g_mqtt_thread);
}

static void net_cb(uint32_t event, uint32_t data, void *arg) {
    uint16_t type = EVENT_SUBTYPE(event);
    switch (type) {
    case NET_CTRL_MSG_NETWORK_UP:
        printf("NET_CTRL_MSG_NETWORK_UP\n");
        if (!OS_ThreadIsValid(&g_mqtt_thread)) {
            OS_ThreadCreate(&g_mqtt_thread, "connect_to_server_task",
                                mqtt_task, (void *)NULL,  OS_THREAD_PRIO_APP, (8 * 1024));
        }
        break;
    case NET_CTRL_MSG_NETWORK_DOWN:
        break;
    default:
        break;
    }
}

static void MainThread(void *arg)   {
    printf("MainThread start\r\n");

    /*Config GPIO*/
    IoTGpioInit(GPIO_ID_PA23);
    IoTGpioSetDir(GPIO_ID_PA23, IOT_GPIO_DIR_OUT);
    IoTGpioSetOutputVal(GPIO_ID_PA23, 0);

    IoTGpioInit(GPIO_ID_PA22);
    IoTGpioSetDir(GPIO_ID_PA22, IOT_GPIO_DIR_OUT);
    IoTGpioSetOutputVal(GPIO_ID_PA22, 0);

    IoTGpioInit(GPIO_ID_PA21);
    IoTGpioSetDir(GPIO_ID_PA21, IOT_GPIO_DIR_OUT);
    IoTGpioSetOutputVal(GPIO_ID_PA21, 0);

    IoTGpioInit(GPIO_ID_PA20);
    IoTGpioSetDir(GPIO_ID_PA20, IOT_GPIO_DIR_OUT);
    IoTGpioSetOutputVal(GPIO_ID_PA20, 0);


    HAL_Status status = HAL_ERROR;
    PWM_ClkParam clk_param;
    PWM_ChInitParam ch_param;

    clk_param.clk = PWM_CLK_HOSC;
    clk_param.div = PWM_SRC_CLK_DIV_1;

    status = HAL_PWM_GroupClkCfg(PWM_OUTPUT_CHL, &clk_param);
    if (status != HAL_OK)
        printf("%s(): %d, PWM group clk config error\n", __func__, __LINE__);

    ch_param.hz = 1000;
    ch_param.mode = PWM_OUTPUT_MODE;
    ch_param.polarity = PWM_HIGHLEVE;
    max_duty_ratio = HAL_PWM_ChInit(PWM_OUTPUT_CHL, &ch_param);
    if (max_duty_ratio == -1)
        printf("%s(): %d, PWM ch init error\n", __func__, __LINE__);

    printf("max_duty_ratio=%d\n", max_duty_ratio);
    status = HAL_PWM_ChSetDutyRatio(PWM_OUTPUT_CHL, 0);
    if (status != HAL_OK)
        printf("%s(): %d, PWM set duty ratio error\n", __func__, __LINE__);

    status = HAL_PWM_EnableCh(PWM_OUTPUT_CHL, PWM_OUTPUT_MODE, 1);
    if (status != HAL_OK)
        printf("%s(): %d, PWM ch enable error\n", __func__, __LINE__);

    if (WIFI_SUCCESS != EnableWifi()) {
        printf("Error: EnableWifi fail\n");
        return;
    }

    OS_Sleep(1);

    if (WIFI_SUCCESS != Scan()) {
        printf("Error: Scan fail.\n");
        return;
    }

    OS_Sleep(3);//这里为了方便用延时,实际用回调更好,否则3秒可能不够

    const char ssid_want_connect[] = WIFI_DEVICE_CONNECT_AP_SSID;
    const char psk[] = WIFI_DEVICE_CONNECT_AP_PSK;
    WifiScanInfo scan_results[30];
    unsigned int scan_num = 30;

    if (WIFI_SUCCESS != GetScanInfoList(scan_results, &scan_num)) {
        printf("Error: GetScanInfoList fail.\n");
        return;
    }

    WifiDeviceConfig config = { 0 };
    int netId = 0;

    int i;
    for (i = 0; i < scan_num; i++) {
        printf("ssid: %s    ", scan_results[i].ssid);
        printf("securityType: %d\n", scan_results[i].securityType);
        if (0 == strcmp(scan_results[i].ssid, ssid_want_connect)) {
            memcpy(config.ssid, scan_results[i].ssid,
                   WIFI_MAX_SSID_LEN);
            memcpy(config.bssid, scan_results[i].bssid,
                   WIFI_MAC_LEN);
            strcpy(config.preSharedKey, psk);
            config.securityType = scan_results[i].securityType;
            config.wapiPskType = WIFI_PSK_TYPE_ASCII;
            config.freq = scan_results[i].frequency;
            break;
        }
    }

    if (i >= scan_num) {
        printf("Error: No found ssid in scan_results\n");
        return;
    }

    if (WIFI_SUCCESS != AddDeviceConfig(&config, &netId)) {
        printf("Error: AddDeviceConfig Fail\n");
        return;
    }
    printf("Config Success\n");

    if (WIFI_SUCCESS != ConnectTo(netId)) {
        printf("Error: ConnectTo Fail\n");
        return;
    }
    
    observer_base *net_ob;
    net_ob = sys_callback_observer_create(CTRL_MSG_TYPE_NETWORK, NET_CTRL_MSG_ALL, net_cb, NULL);
    if (net_ob == NULL)
        return;

    if (sys_ctrl_attach(net_ob) != 0)
        return;

    while (1) {
        OS_MSleep(500);
    }
}

void MqttMain(void) {
    if (OS_ThreadCreate(&g_main_thread, "MainThread", MainThread, NULL, OS_THREAD_PRIO_APP, 4 * 1024) != OS_OK) {
        printf("[ERR] Create MainThread Failed\n");
    }
}

SYS_RUN(MqttMain);

##视频##

https://www.bilibili.com/video/BV1xb4y1476Y/?aid=636601969&cid=508147028&page=1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/623757.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构(二) 线性表

2024年5月13日一稿 线性表的定义与基本操作 数据类型相同(各个元素占用空间相同) 是有限序列 基操

武汉星起航:亚马逊店铺经营秘籍,揭秘提升点击率的关键策略

在竞争激烈的亚马逊电商平台上&#xff0c;提升点击率成为了每个卖家都渴望实现的目标。点击率不仅直接关系到商品的曝光度和销售量&#xff0c;更是衡量店铺经营效果的重要指标。那么&#xff0c;如何才能在众多商品中脱颖而出&#xff0c;吸引潜在买家的目光呢&#xff1f;武…

浏览器不兼容 replaceAll 方法问题解决

问题 在一些较旧版本的浏览器中可能会出现 replaceAll 方法不兼容&#xff0c;提示replaceAll 方法 undefined 的问题。浏览器版本兼容情况如下图所示&#xff1a; 解决 可以通过 replace 正则表达式 的方法来代替 replaceAll 方法&#xff1a; let str "我是一段文本…

【激活函数--下】非线性函数与ReLU函数

文章目录 一、非线性函数在神经网络中的重要性二、ReLU函数介绍及其实现2.1 ReLU函数概述2.2 ReLU函数的Python实现及可视化 一、非线性函数在神经网络中的重要性 在神经网络中&#xff0c;激活函数的选择对于网络的性能和能力至关重要。阶跃函数和Sigmoid函数除了是激活函数的…

想跨境出海?云手机提供了一种可能性

全球化时代&#xff0c;越来越多的中国电商开始将目光投向了海外市场。这并不是偶然&#xff0c;而是他们在长期的市场运营中&#xff0c;看到了出海的必要性和潜在的机会。 中国的电商市场无疑是全球最大也最发达的之一。然而&#xff0c;随着市场的不断发展和竞争的日益加剧…

300订单,成交大于一切

最近一直在忙于做老客户的需求&#xff0c;新客户挖掘方面有点大大的落后了&#xff0c;新客户的成交率接近0。 今天来了一个新客户&#xff0c;部署一套系统&#xff0c;我的正常报价都是300/次&#xff0c;至于为什么定这个价格后面再说&#xff0c;经过沟通客户没有服务器&…

【数据库】数据库指令

一。数据库打开 1.命令行 2.进入mysql mysql -uroot -p密码 3.退出 exit&#xff1b; 二。针对数据库的操作 1.创建数据库&#xff08;有分号&#xff09; create database student; 2.使用数据库 use student 3.删除数据库&#xff08;有分号&#xff09; drop database…

KNIME 报告扩展

文档对应的 KNIME AP 版本为 5.2 介绍 本指南介绍了 KNIME 报告扩展&#xff0c;并展示了如何创建简单和高级报告。 本指南更新于 2024/05/13&#xff0c;最新版请访问指北君网站 https://havef.fun/knime-cn/knime-doc/ KNIME 报告扩展允许您根据工作流程的结果创建静态报告。…

机器人系统ros2内部接口介绍

内部 ROS 接口是公共 C API &#xff0c;供创建客户端库或添加新的底层中间件的开发人员使用&#xff0c;但不适合典型 ROS 用户使用。 ROS客户端库提供大多数 ROS 用户熟悉的面向用户的API&#xff0c;并且可能采用多种编程语言。 内部API架构概述 内部接口主要有两个&#x…

岩土工程监测仪器之一:振弦采集仪的工作原理解析

岩土工程监测仪器之一&#xff1a;振弦采集仪的工作原理解析 河北稳控科技振弦采集仪是岩土工程监测中常用的一种仪器&#xff0c;用于测量地面、结构物或其他物体的振动情况。它通过感应振弦的振动来获取相关的数据&#xff0c;进而分析和评估土壤、地基或结构物的稳定性和安…

hdfs块数据丢失(启动安全模式)

进入安全模式 hdfs dfsadmin -safemode退出安全模式 hdfs dfsadmin -safemode forceExit

数据可视化(九):Pandas北京租房数据分析——房源特征绘图、箱线图、动态可视化等高级操作

Tips&#xff1a;"分享是快乐的源泉&#x1f4a7;&#xff0c;在我的博客里&#xff0c;不仅有知识的海洋&#x1f30a;&#xff0c;还有满满的正能量加持&#x1f4aa;&#xff0c;快来和我一起分享这份快乐吧&#x1f60a;&#xff01; 喜欢我的博客的话&#xff0c;记得…

5.13号模拟前端面试10问

1.介绍箭头函数和普通函数的区别 箭头函数和普通函数在JavaScript中有一些重要的区别。以下是关于这些区别的详细解释&#xff1a; 语法结构上的差异&#xff1a; 箭头函数使用更简洁的语法&#xff0c;它不需要使用function关键字&#xff0c;而是使用一个箭头&#xff08;…

羊大师解析,羊奶滋养健康伴你行

羊大师解析&#xff0c;羊奶滋养健康伴你行 羊大师发现&#xff0c;羊奶在健康方面具有一定的优势&#xff0c;主要体现在以下几个方面。 补充营养&#xff1a;羊奶富含多种营养物质&#xff0c;包括蛋白质、钙、维生素D、维生素B12、矿物质等&#xff0c;这些成分有助于满足…

高考志愿系统-信息管理模块:专业信息和分数线信息分析

之前分析可知&#xff0c;专业和学校的关系为多对一&#xff0c;专业和分数线的关系为一对多。所以专业信息的管理稍微复杂一点。 其中分数线信息的管理和专业信息的业务逻辑相互联系&#xff0c;就是在对专业信息管理的时候&#xff0c;分数线信息也会随着更新。 1.获取专业…

有哪些值得买的开放式耳机推荐?2024年开放式运动耳机选购指南

开放式耳机因其独特设计&#xff0c;能在一定程度上保护听力。相较于传统封闭式耳机&#xff0c;开放式设计允许周围环境声音自然流入耳内&#xff0c;降低了耳内共振和声压&#xff0c;减少了耳道的不适感&#xff0c;从而减轻了对听力的潜在损害。对于追求音质与听力保护并重…

傻瓜化备份/恢复K8S集群Etcd数据

前言&#xff1a; 备份重要数据&#xff0c;简化重复操作&#xff0c;让一指禅、点点点也能完成运维任务。 脚本呈现界面如下&#xff1a; 1、查看Etcd版本 rootmaster:~# cat /etc/kubernetes/manifests/etcd.yaml | grep image: | awk {print $2} registry.aliyuncs.com/goo…

视频短信时代来临!发送前必知的四大关键要素

随着移动通信技术的迅猛发展&#xff0c;视频短信作为全新的沟通方式&#xff0c;正逐渐融入我们的日常生活。作为行业的先行者&#xff0c;邦之信已率先推出视频短信业务&#xff0c;并获得了市场的广泛认可。 那么&#xff0c;在发送视频短信时&#xff0c;我们需要注意哪些关…

口碑最好的麦克风品牌有哪些?多款高口碑无线领夹麦克风推荐

从直播、拍摄到采访&#xff0c;音频设备对于我们的生活越来越重要&#xff0c;想要拥有更清晰、真实的录音效果&#xff0c;一款优质的无线领夹麦克风肯定是必不可少的&#xff0c;其轻便小巧的特性&#xff0c;不仅适用于手机和相机的直播、录音需求&#xff0c;同时也能满足…

MATLAB支持向量机:函数或变量 ‘svmtrain‘ 无法识别解决方法

我的MATLAB版本是2020a&#xff0c;在运行程序时出现了一下报错 若在运行程序时出现了以下报错&#xff1a; 支持向量机程序在MATLAB执行代码的时候发现有错误。 试一下help&#xff0c;如下图所示&#xff0c;SVM_L和svmtrain均找不到。 打开matlab帮助文档&#xff1a; 可…