【星闪开发连载】星闪网关的设计与实现

 

目录

引言

系统结构

技术实现

SLE程序架构

温湿度程序

WiFi程序

MQTT程序

完整程序

测试

结语


 

引言

对星闪模块WS63E的测试已经有一段时间了,在前面的博客也分享了自己的一些体会。今天要完成最终的成品了:星闪网关。所谓星闪网关就是可以接受客户端的连接和数据发送,并将数据转发给云端的设备。在实际生活中,星闪网关是非常有用的,因为很多星闪设备(如温湿度计、夜灯等)考虑到节能的需要,它们只有星闪一种无线方式,无法直接连接如云端,所以就需要网关设备做中转。

系统结构

我设计的系统的结构组成如下图所示。其中,星闪客户端用于连接温湿度传感器获得环境信息,然后通过星闪联络传递给网关设备,而网关设备通过WiFi链路连接到路由器,并实现和华为云服务器的连接。网关设备作为星闪服务器收到客户端发来的数据将其通过MQTT协议发送到华为IoT云。

5d82860b5c5d44d2ae69bbfc8e488277.png

考虑到手头的硬件配置,我将星闪派物联网开发套件模拟星闪客户端,这样方便连接传感器,而将单独的星闪派开发板作为星闪网关。

技术实现

有关技术的基本原理在前面的博客已经介绍了:  

SLE程序架构

程序的基本架构参考了润和提供的SLE控制LED灯的例子。

SLE客户端部分添加了AHT20和SSD1306相关的代码,而网关部分增加了WiFi的程序。CMakeList.txt做了如下修改:

if(DEFINED CONFIG_SAMPLE_SUPPORT_SLE_GATEWAY_SERVER)
    set(SOURCES "${SOURCES}" "${CMAKE_CURRENT_SOURCE_DIR}/sle_server/src/SLE_Server_adv.c" "${CMAKE_CURRENT_SOURCE_DIR}/sle_server/src/SLE_Server.c" "${CMAKE_CURRENT_SOURCE_DIR}/sle_server/src/WiFi_STA.c" "${CMAKE_CURRENT_SOURCE_DIR}/sle_server/inc" PARENT_SCOPE)
elseif(DEFINED CONFIG_SAMPLE_SUPPORT_SLE_GATEWAY_CLIENT)
    set(SOURCES "${SOURCES}" "${CMAKE_CURRENT_SOURCE_DIR}/sle_client/src/aht20.c" "${CMAKE_CURRENT_SOURCE_DIR}/sle_client/src/aht20_test.c" "${CMAKE_CURRENT_SOURCE_DIR}/sle_client/src/ssd1306_fonts.c" "${CMAKE_CURRENT_SOURCE_DIR}/sle_client/src/ssd1306.c" "${CMAKE_CURRENT_SOURCE_DIR}/sle_client/src/SLE_Client.c" "${CMAKE_CURRENT_SOURCE_DIR}/sle_client/inc" PARENT_SCOPE)

endif()

程序的基本流程是这样的:

  •  服务器端程序启动后创建任务,初始化WiFi,WiFi成功后调用MQTT程序连接华为云,同时启动星闪SLE的广播,等待星闪客户端的连接。
  •  服务器端程序启动后创建任务,初始化温湿度传感器和屏幕,然后将温湿度显示在屏幕上,同时启动星闪SLE的客户端,扫描星闪服务器,一旦找到就尝试建立星闪连接。
  • 星闪客户端一旦连接成功,就启动一次读操作(润和原本的程序就是这样的,我保留了该步骤),在读操作的回调函数中将保存在全局变量中的温湿度数据组成JSON数据包,然后发送出去。
  • 星闪服务器收到收据后,就通过MQTT连接将数据转发到华为云。

温湿度程序

温湿度程序改动比较小,只是将用来存储温湿度数据的temp和humi两个变量变成了全局变量,然后供星闪程序读取。由于温湿度程序启动速度比星闪程序快得多,所以当星闪程序去读取这个数据时,它肯定是有效的。

float g_temp = 0.0f;
float g_humi = 0.0f;
…………
void Aht20TestTask(void)
{
    uint32_t retval = 0;

…………
    while (1) {
…………
        retval = AHT20_GetMeasureResult(&g_temp, &g_humi);
…………
    }
}

WiFi程序

WiFi_STA.c程序基本没有修改。这个程序只在网关部分添加。主程序在初始化之后直接调用了WiFi初始化的程序,去实现和路由器之间的连接以及DHCP分配地址工作。在example_sta_function函数中增加了对mqtt_init的调用。这个代码有点瑕疵,就是如果有多个IP地址,mqtt_init会被调用多次,不过对于WS63E来说只有一个IP地址,问题不大。

    for (uint8_t i = 0; i < WIFI_STA_IP_MAX_GET_TIMES; i++) {
        (void)osal_msleep(10); /* 延时10ms */
        if (netif_p->ip_addr.u_addr.ip4.addr != 0) {
            PRINT("STA IP %u.%u.%u.%u\r\n", (netif_p->ip_addr.u_addr.ip4.addr & 0x000000ff),
                  (netif_p->ip_addr.u_addr.ip4.addr & 0x0000ff00) >> 8,
                  (netif_p->ip_addr.u_addr.ip4.addr & 0x00ff0000) >> 16,
                  (netif_p->ip_addr.u_addr.ip4.addr & 0xff000000) >> 24);
            /* 连接成功 */
            PRINT("STA connect success.\r\n");

            mqtt_init();
            return ERRCODE_SUCC;
        }
    }

MQTT程序

MQTT初始化程序改动也很少,只是删除了原有的发布代码。

int mqtt_init(void) 
{
    int rc;
    MQTTResponse ret;
    MQTTClient_init_options ops;
    ops.do_openssl_init = 0;
    MQTTClient_global_init(&ops);
    MQTTClient_createOptions create_opts = { {'M', 'Q', 'C', 'O'}, 0, MQTTVERSION_5 };

    rc = MQTTClient_createWithOptions(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL, &create_opts);
    if (rc != MQTTCLIENT_SUCCESS) {
        printf("Failed to create mqtt client, return code %d\n", rc);
        return -1;
    }
    // 设置MQTT消息接受回调函数
    MQTTClient_setCallbacks(client, NULL, NULL, on_message, NULL);
    MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer5;
    conn_opts.username = USERNAME;
    conn_opts.password = PASSWORD;
    MQTTProperties props = MQTTProperties_initializer;
    MQTTProperties willProps = MQTTProperties_initializer;

    
    ret = MQTTClient_connect5(client, &conn_opts, &props, &willProps);
    if (ret.reasonCode == MQTTREASONCODE_SUCCESS) {
        printf("Connected to MQTT Broker!\n");
    } else {
        printf("failed to connect to MQTT Broker, return code %d\n", (int8_t)ret.reasonCode);
        MQTTResponse_free(ret);
        return -1;
    }
    MQTTResponse_free(ret);

    return 0;
}

在星闪网关服务器端example_ssaps_write_request_cbk中实现了对publish的调用。

/**
 * 处理从SLE收到的数据
 */
static void example_sle_sever_write_request_cbk(ssaps_req_write_cb_t *write_cb_para)
{
    unused(write_cb_para);
    publish(client, 
        "$oc/devices/XXXXXX_test1/sys/properties/report", 
        (char*)write_cb_para->value);    
}

static void example_ssaps_write_request_cbk(uint8_t server_id,
                                            uint16_t conn_id,
                                            ssaps_req_write_cb_t *write_cb_para,
                                            errcode_t status)
{
    PRINT("[SLE Server] ssaps write request cbk server_id:0x%x, conn_id:0x%x, handle:0x%x, status:0x%x\r\n", server_id,
          conn_id, write_cb_para->handle, status);

    PRINT("[SLE Server] data leng = %u\r\n", write_cb_para->length);
    PRINT("[SLE Server] data = %s\r\n", (char*)write_cb_para->value);
    // for (uint16_t idx = 0; idx < write_cb_para->length; idx++) {
    //     PRINT("[SLE Server] write request cbk[%u] 0x%02x\r\n", idx, write_cb_para->value[idx]);
    // }

    if (status == ERRCODE_SUCC) {
        example_sle_sever_write_request_cbk(write_cb_para);
    }
}

MQTT所需要的JSON数据组包工作是在星闪客户端中完成的。

static void example_sle_read_cfm_cbk(uint8_t client_id,
                                     uint16_t conn_id,
                                     ssapc_handle_value_t *read_data,
                                     errcode_t status)
{
    PRINT("[SLE Client] read cfm cbk client id:0x%x conn id:0x%x status:0x%x\r\n", client_id, conn_id, status);
    PRINT("[SLE Client] read cfm cbk handle:0x%x, type:0x%x, len:0x%x\r\n", read_data->handle, read_data->type,
          read_data->data_len);
    for (uint16_t idx = 0; idx < read_data->data_len; idx++) {
        PRINT("[SLE Client] read cfm cbk[0x%x] 0x%02x\r\n", idx, read_data->data[idx]);
    }

    // 发送温湿度数据
    if (status == ERRCODE_SUCC) {
        char write_req_data[500];
        int16_t len;
        len  = sprintf_s(write_req_data, 499, "{\"services\": [{"  \
                        "\"serviceId\": \"温湿度\"," \
                        "\"properties\": {" \
                            "\"温度\": %d, "\
                            "\"湿度\": %d" \
                        "}}]}", (int)g_temp, (int)g_humi);
        printf("data = %s\r\n", write_req_data);
        printf("len = %d\r\n", len);
        if(len != -1)
        {
            ssapc_write_param_t param = {0};
            param.handle = g_find_service_result.start_hdl;
            param.type = SSAP_PROPERTY_TYPE_VALUE;
            param.data_len = (uint16_t)len + 1;
            param.data = (uint8_t*)write_req_data;
            ssapc_write_req(client_id, conn_id, &param);
        }
    }
}

 

完整程序

 完整的程序已经提交到Gitee上了(zealsoft/fbb_ws63 - 码云 - 开源中国),欢迎提出宝贵意见。

目前的程序需要分两次编译,分别产生星闪客户端和星闪服务器的程序。也可以对程序进行一些修改,比如检测不到温湿度传感器就说明是星闪服务器,这样就可以将程序合二为一。

测试

分别将客户端和服务器的程序烧写到开发板上,我们就可以直接测试了。

星闪客户端的部分log如下,从中可以看出客户端连接到星闪服务器后就会把温湿度时间的JSON数据发送给星闪服务器:

AHT20_StartMeasure: 0
temp = : 22.96, humi = : 49.75
[adv_report] event_type: 0x03, addr_type: 0x0000, addr: 02:**:**:**:06:03
[adv_report] data length: 6, data: 0x02 0x01 0x01 0x02 0x02 0x00
APP|[SLE Client] seek result find expected addr:02***0603
[Connected]
addr:02:**:**:**:06:03, handle:00
APP|[SLE Client] conn state changed conn_id:0x0, addr:02***0603
APP|[SLE Client] conn state changed disc_reason:0x0
APP|[SLE Client] pair complete conn_id:0x0, status:0x0, addr:02***0603
ssapc exchange info, conn_id:0, err_code:0
APP|[SLE Client] pair complete client id:0x0, status:0x0
APP|[SLE Client] exchange mtu, mtu size:0x12c, version:0x1.
discovery character cbk complete in
APP|[SLE Client] find structure cbk client:0x0 conn_id:0x0 status:0x0
APP|[SLE Client] find structure start_hdl:[0x0001], end_hdl:[0x0002], uuid len:0x2
APP|[SLE Client] structure uuid:[0xcd][0xab]
APP|[SLE Client] find structure cmp cbk client id:0x0 conn_id:0x0 status:0x0 type:1 uuid len:0x0
APP|[SLE Client] find structure cmp cbk structure uuid[0x0]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0x1]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0x2]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0x3]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0x4]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0x5]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0x6]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0x7]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0x8]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0x9]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0xa]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0xb]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0xc]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0xd]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0xe]:[0x00]
APP|[SLE Client] find structure cmp cbk structure uuid[0xf]:[0x00]
APP|[SLE Client]start get AHT20 status and report task
AHT20_StartMeasure: 0
temp = : 22.97, humi = : 49.53
AHT20_StartMeasure: 0
APP|[SYS INFO] mem: used:98756, free:263856; log: drop/all[0/0], at_recv 0.
temp = : 22.97, humi = : 49.40
AHT20_StartMeasure: 0
temp = : 22.99, humi = : 49.20
AHT20_StartMeasure: 0
temp = : 22.97, humi = : 48.99
AHT20_StartMeasure: 0
temp = : 23.00, humi = : 49.06
AHT20_StartMeasure: 0
temp = : 22.99, humi = : 48.87
AHT20_StartMeasure: 0
temp = : 22.97, humi = : 48.52
AHT20_StartMeasure: 0
temp = : 22.98, humi = : 48.43
AHT20_StartMeasure: 0
temp = : 22.99, humi = : 48.27
ssapc read rsp handle:0, data len:4
APP|[SLE Client] read cfm cbk client id:0x0 conn id:0x0 status:0x0
APP|[SLE Client] read cfm cbk handle:0x0, type:0x0, len:0x4
APP|[SLE Client] read cfm cbk[0x0] 0x01
APP|[SLE Client] read cfm cbk[0x1] 0x01
APP|[SLE Client] read cfm cbk[0x2] 0x00
APP|[SLE Client] read cfm cbk[0x3] 0x0c
data = {"services": [{"serviceId": "温湿度","properties": {"温度": 22, "湿度": 48}}]}
len = 85
ssapc write rsp handle:1
[sle write_req_complete_cbk]conn_id:0, err_code:0
APP|[SLE Client] write cfm cbk client id:0x0 conn_id:0x0 status:0x0.
APP|[SLE Client] write cfm cbk handle:0x1, type:0x0
AHT20_StartMeasure: 0

星闪网关(服务器)的部分log如下,从中可以看到一旦星闪客户端建立连接,很快会收到上传的数据,然后网关就会通过MQTT将其发送的华为云(日志中的服务器地址、设备ID做了特殊处理):

APP|[SLE Adv] local_name[15] = 0x74
APP|[SLE Adv] set announce data success.APP|[SLE Adv] set sle addr SUCC
[ACore] sle start announce in, adv_id:1
[ACore] sle adv cbk in, event:0 status:0
[ACore] sle adv cbk in, event:1 status:0
[ACore] sle adv cbk in, event:2 status:0
[ACore] sle adv cbk in, event:3 status:0
APP|[SLE Adv] sle announce enable id:01, state:00
APP|[SLE Adv] example_sle_server_adv_init out
APP|[SLE Server] init ok
[Connected]
addr:ee:**:**:**:8d:5c, handle:00
[ACore] sle adv cbk in, event:7 status:0
APP|[SLE Server] connect state changed conn_id:0x00, conn_state:0x1, pair_state:0x1,         disc_reason:0x0
APP|[SLE Server] connect state changed addr:ee:**:**:**:8d:5c
APP|[SLE Adv] sle announce terminal id:01
APP|[SLE Server] pair complete conn_id:0x00, status:0x0
APP|[SLE Server] pair complete addr:ee:**:**:**:8d:5c
APP|[SLE Server] ssaps mtu changed cbk server_id:0x0, conn_id:0x0, mtu_size:0x12c, status:0x0
drv_soc_ioctl ioctl_cmd->cmd=14.
hmac_single_hal_device_scan_complete:vap[1] time[762] chan_cnt[13] chan_0[1] back[0] event[6] mode[0]
Scan::vap[1] find bss_num[22] in regdomain, other bss_num[0]
Srv:548:recive event = 1
Srv:221:cb is not init
Srv:1723:sta_scan_results cnt 22
APP|STA try connect.
Srv:find ssid[CU_fjGX] auth type[2] pairwise[1] ft_flag[0]
drv_soc_ioctl ioctl_cmd->cmd=47.
drv_soc_ioctl ioctl_cmd->cmd=47.
drv_soc_ioctl ioctl_cmd->cmd=47.
drv_soc_ioctl ioctl_cmd->cmd=16.
drv_soc_ioctl ioctl_cmd->cmd=6.
drv_soc_ioctl ioctl_cmd->cmd=6.
drv_soc_ioctl ioctl_cmd->cmd=47.
drv_soc_ioctl ioctl_cmd->cmd=6.
drv_soc_ioctl ioctl_cmd->cmd=5.
drv_soc_ioctl ioctl_cmd->cmd=6.
drv_soc_ioctl ioctl_cmd->cmd=6.
drv_soc_ioctl ioctl_cmd->cmd=5.
drv_soc_ioctl ioctl_cmd->cmd=1.
drv_soc_ioctl ioctl_cmd->cmd=3.
drv_soc_ioctl ioctl_cmd->cmd=1.
+NOTICE:CONNECTED
drv_soc_ioctl ioctl_cmd->cmd=6.
Srv:548:recive event = 2
Srv:130:cd is not init
APP|STA DHCP start.
APP|[SYS INFO] mem: used:141560, free:220828; log: drop/all[0/0], at_recv 0.
APP|STA DHCP bound success.
APP|STA IP 192.168.1.176
APP|STA connect success.
Connecting to serverURI xxxxx.st1.iotda-device.cn-north-4.myhuaweicloud.com:1883 with MQTT ver
                                                                                                   New socket 0 for xxxxx.st1.iotda-device.cn-north-4.myhuaweicloud.com:1883, port 1883
                                                           0 xxxxxxxxxxxxxxxxx_test1_0_0_2024102012 -> CONNECT version 5 clean: 0 (0)
           Posting connack semaphore for client xxxxxxxxxxxxxxxxx_test1_0_0_2024102012
                                                                                             0 xxxxxxxxxxxxxxxxx_test1_0_0_2024102012 <- CONNACK rc: 0
                            Connected to MQTT Broker!
                                                     APP|[SLE Server] ssaps read request cbk server_id:0x1, conn_id:0x0, handle:0x1, type:0x0, status:0x0
APP|[SLE Server] ssaps write request cbk server_id:0x1, conn_id:0x0, handle:0x1, status:0x0
APP|[SLE Server] data leng = 86
APP|[SLE Server] data = {"services": [{"serviceId": "温湿度","properties": {"温度": 22, "湿度": 48}}]}
0 xxxxxxxxxxxxxxxxx_test1_0_0_2024102012 -> PUBLISH rc: 0
                                                                Send `{"services": [{"serviceId": "温湿度","properties": {"温度": 22, "湿度": 48}}]}` to topic `$oc/devices/xxxxxxxxxxxxxxxxx_test1/sys/properties/report`
                                                                                                 APP|[SYS INFO] mem: used:137588, free:224800; log: drop/all[0/0], at_recv 0.
APP|[SLE Server] ssaps read request cbk server_id:0x1, conn_id:0x0, handle:0x1, type:0x0, status:0x0
APP|[SLE Server] ssaps write request cbk server_id:0x1, conn_id:0x0, handle:0x1, status:0x0

登录华为IoTDA控制台,可以看到上传的数据。由此可见,整个系统功能正常。

4925fa5cc1a54d1eac95994ecb81c525.png

结语

经过这段时间的测试,WS63E模块整体的使用和hi3861差不多,星闪的编程模式和蓝牙差不多,读者要是想快速掌握星闪的编程,最好有点蓝牙的基础。

星闪技术应该是华为被蓝牙联盟踢出来之后给出的技术标准,就像OpenHarmony之于Android差不多。还是希望星闪技术不断发展,走出自己的道路。

虽然此次星闪体验官的测试任务完成了,但是后续仍然会继续分享一些对星闪技术的测试和分析,欢迎继续关注我的博客。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神一样的老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值