目录
引言
对星闪模块WS63E的测试已经有一段时间了,在前面的博客也分享了自己的一些体会。今天要完成最终的成品了:星闪网关。所谓星闪网关就是可以接受客户端的连接和数据发送,并将数据转发给云端的设备。在实际生活中,星闪网关是非常有用的,因为很多星闪设备(如温湿度计、夜灯等)考虑到节能的需要,它们只有星闪一种无线方式,无法直接连接如云端,所以就需要网关设备做中转。
系统结构
我设计的系统的结构组成如下图所示。其中,星闪客户端用于连接温湿度传感器获得环境信息,然后通过星闪联络传递给网关设备,而网关设备通过WiFi链路连接到路由器,并实现和华为云服务器的连接。网关设备作为星闪服务器收到客户端发来的数据将其通过MQTT协议发送到华为IoT云。
考虑到手头的硬件配置,我将星闪派物联网开发套件模拟星闪客户端,这样方便连接传感器,而将单独的星闪派开发板作为星闪网关。
技术实现
有关技术的基本原理在前面的博客已经介绍了:
- 【星闪开发连载】WS63E模块实时显示当前环境温湿度_润和ws63-CSDN博客
- 【星闪开发连载】WS63E模块的WiFi客户端测试_ws63配置发射功率-CSDN博客
- 【星闪开发连载】WS63E模块连接华为IoT云_ws63v100-CSDN博客
- 【星闪开发连载】SLE_UUID_Server和SLE_UUID_Client程序分析_sle广播功能-CSDN博客
- 【星闪开发连载】SLE_UUID_Server和SLE_UUID_Client程序测试_星闪ws63e-CSDN博客
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, ¶m);
}
}
}
完整程序
完整的程序已经提交到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控制台,可以看到上传的数据。由此可见,整个系统功能正常。
结语
经过这段时间的测试,WS63E模块整体的使用和hi3861差不多,星闪的编程模式和蓝牙差不多,读者要是想快速掌握星闪的编程,最好有点蓝牙的基础。
星闪技术应该是华为被蓝牙联盟踢出来之后给出的技术标准,就像OpenHarmony之于Android差不多。还是希望星闪技术不断发展,走出自己的道路。
虽然此次星闪体验官的测试任务完成了,但是后续仍然会继续分享一些对星闪技术的测试和分析,欢迎继续关注我的博客。