【XR806开发板试用】将“米家蓝牙温湿度计2”的数据上传到阿里云
-
一、开发环境搭建
1. 开发平台
- Windows 10
- WSL-Ubuntu-20.04
2. 适配 OpenHarmony 的 xr806 SDK
- 使用 repo + https 从 gitee 获取源码
repo init -u https://gitee.com/openharmony-sig/manifest.git -b OpenHarmony_1.0.1_release --no-repo-verify -m devboard_xr806.xml repo sync -c repo forall -c 'git lfs pull'
- OpenHarmony_1.0.1_release 分支里的 devboard_xr806.xml 中,跟 xr806 相关的部分仓库路径已经失效,所以需要从临时仓库获取xr806的代码
# device/xradio cd device git clone https://gitee.com/moldy-potato-chips/devboard_device_allwinner_xr806 xradio # vendor/xradio cd vendor git clone https://gitee.com/moldy-potato-chips/devboard_vendor_allwinner_xr806 xradio
3. 工具链、LLVM、hb以及其他必要的库和工具,参考鸿蒙的开发文档
4. 遇到的问题:
在 WSL 中编译时,遇到了 mkimage 程序无法执行的问题,原因是 mkimage 是 32-bit的程序,最终按照一篇文章的方法解决了该问题,文章链接如下:
https://www.cnblogs.com/TatuCz/p/10330820.html二、实现思路
1. 所需硬件或者服务
- 米家蓝牙温湿度计2代
- XR806开发板
- 阿里云物联网平台
2. 实现框图
3. 程序实现流程
三、代码实现
1. 主程序以及头文件
#include <stdio.h> #include <stddef.h> #include <string.h> #include <errno.h> #include "ohos_init.h" #include "kernel/os/os.h" #include "mqtt_client.h" #include "net/wlan/wlan.h" #include "net/mqtt/MQTTClient-C/MQTTClient.h" #include <zephyr/types.h> #include <ble/sys/byteorder.h> #include <zephyr.h> #include <settings/settings.h> #include <bluetooth/bluetooth.h> #include <bluetooth/hci.h> #include <bluetooth/conn.h> #include <bluetooth/uuid.h> #include <bluetooth/gatt.h> void TempsensorBleMain(void) { wifi_init(); OS_Sleep(10); mqtt_thread_init(); mqtt_init(); ble_init(); } SYS_RUN(TempsensorBleMain);
2. 连接 WLAN AP
/* Wifi config */ #define WIFI_SSID "your_ssid" #define WIFI_PSK "your_password" static void wifi_init(void) { printf("Enable Wifi STA mode...\n"); /* switch to sta mode */ net_switch_mode(WLAN_MODE_STA); /* set ssid and password to wlan, use WPA2|WPA3 compatible mode to connect AP. */ wlan_sta_set(WIFI_SSID, strlen(WIFI_SSID), WIFI_PSK); /* start scan and connect to ap automatically */ wlan_sta_enable(); }
3. 连接阿里云 MQTT 服务
/* MQTT config */ #define MQTT_HOST "iot-as-mqtt.cn-shanghai.aliyuncs.com" #define MQTT_PORT "1883" #define MQTT_CLIENTID "xxxxxxxx|securemode=2,signmethod=hmacsha256,timestamp=2524608000000|" #define MQTT_USERNAME "xxxxxxxx" #define MQTT_PASSWORD "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" #define MQTT_KEEPALIVE 30 #define MQTT_SSL 0 #define MQTT_CLEAN 0 #define MQTT_SUBSCRIBE_TOPIC "/xxxxxxxx/xr806_ble/user/get" #define MQTT_PUBLISH_TOPIC "/sys/xxxxxxxx/xr806_ble/thing/event/property/post" #define MQTT_BUF_SIZE (1024) typedef enum { MQTT_CMD_CONNECT, MQTT_CMD_DISCONNECT, MQTT_CMD_SUBSCRIBE, MQTT_CMD_UNSUBSCRIBE, MQTT_CMD_PUBLISH, MQTT_CMD_EXIT } MQTT_CMD; struct mqtt_msg { MQTT_CMD type; void *data; int data_len; }; struct publish_info { int qos; int retain; char topic[128]; char message[256]; }; struct subscribe_info { int qos; char topic[128]; }; struct unsubscribe_info { char topic[128]; }; struct cmd_mqtt_common { int ssl; int alive; int clean; int will_qos; int will_retain; char *will_topic; char *will_message; char *username; char *password; char port[8]; char host[128]; char client_id[128]; char *sub_topic[MAX_MESSAGE_HANDLERS]; OS_Thread_t thread; OS_Queue_t queue; }; static struct cmd_mqtt_common cmd_mqtt; static void mqtt_msg_cb(MessageData *data) { printf("[topic: %.*s] %.*s\n", data->topicName->lenstring.len, data->topicName->lenstring.data, data->message->payloadlen, (char *)data->message->payload); } static int mqtt_cmd_connect_handler(Client *client, Network *network, void *data, int data_len) { int ret; unsigned char *send_buf = NULL; unsigned char *recv_buf = NULL; MQTTPacket_connectData connectData = MQTTPacket_connectData_initializer; send_buf = malloc(MQTT_BUF_SIZE); if (send_buf == NULL) { printf("malloc fail.\n"); return -1; } recv_buf = malloc(MQTT_BUF_SIZE); if (recv_buf == NULL) { printf("malloc fail.\n"); goto err; } NewNetwork(network); MQTTClient(client, network, 6000, send_buf, MQTT_BUF_SIZE, recv_buf, MQTT_BUF_SIZE); if (cmd_mqtt.ssl) { /* this test has no ca cert, no client cert, no client pk, no client pwd */ ret = TLSConnectNetwork(network, cmd_mqtt.host, cmd_mqtt.port, NULL, 0, NULL, 0, NULL, 0, NULL, 0); if (ret != 0) { printf("Return code from network ssl connect is -0x%04x\n", -ret); goto err; } } else { ret = ConnectNetwork(network, cmd_mqtt.host, atoi(cmd_mqtt.port)); if (ret != 0) { printf("Return code from network connect is %d\n", ret); goto err; } } connectData.clientID.cstring = cmd_mqtt.client_id; connectData.keepAliveInterval = cmd_mqtt.alive; connectData.cleansession = cmd_mqtt.clean; connectData.MQTTVersion = 4; //Version of MQTT 3.1.1 if (cmd_mqtt.will_topic && cmd_mqtt.will_message) { connectData.willFlag = 1; connectData.will.topicName.cstring = cmd_mqtt.will_topic; connectData.will.message.cstring = cmd_mqtt.will_message; connectData.will.retained = cmd_mqtt.will_retain; connectData.will.qos = cmd_mqtt.will_qos; } if (cmd_mqtt.username) { connectData.username.cstring = cmd_mqtt.username; } if (cmd_mqtt.password) { connectData.password.cstring = cmd_mqtt.password; } ret = MQTTConnect(client, &connectData); if (ret != 0) { printf("Return code from MQTT connect is %d\n", ret); network->disconnect(network); goto err; } printf("MQTT connect success.\n"); return 0; err: free(send_buf); free(recv_buf); return -1; } static int mqtt_cmd_disconnect_handler(Client *client, Network *network, void *data, int data_len) { int i; int ret; ret = MQTTDisconnect(client); if (ret != 0) { printf("Return code from MQTT disconnect is %d\n", ret); network->disconnect(network); return -1; } network->disconnect(network); free(client->buf); free(client->readbuf); for (i = 0; i < MAX_MESSAGE_HANDLERS; i++) { free(cmd_mqtt.sub_topic[i]); cmd_mqtt.sub_topic[i] = NULL; } printf("MQTT disconnect success.\n"); return 0; } static int mqtt_cmd_subscribe_handler(Client *client, Network *network, void *data, int data_len) { int i; int ret; int topic_len; struct subscribe_info *info = data; topic_len = strlen(info->topic) + 1; for (i = 0; i < MAX_MESSAGE_HANDLERS; i++) { if (cmd_mqtt.sub_topic[i] == NULL) { cmd_mqtt.sub_topic[i] = malloc(topic_len); if (cmd_mqtt.sub_topic[i] == NULL) { return -1; } memcpy(cmd_mqtt.sub_topic[i], info->topic, topic_len); break; } } if (i >= MAX_MESSAGE_HANDLERS) { printf("Subscribe topic limit %d\n", MAX_MESSAGE_HANDLERS); return -1; } ret = MQTTSubscribe(client, cmd_mqtt.sub_topic[i], info->qos, mqtt_msg_cb); if (ret != 0) { printf("Return code from MQTT subscribe is %d\n", ret); free(cmd_mqtt.sub_topic[i]); cmd_mqtt.sub_topic[i] = NULL; return -1; } printf("MQTT subscribe success.\n"); return 0; } static int mqtt_cmd_unsubscribe_handler(Client *client, Network *network, void *data, int data_len) { int i; int ret; struct unsubscribe_info *info = data; ret = MQTTUnsubscribe(client, info->topic); if (ret != 0) { printf("Return code from MQTT unsubscribe is %d\n", ret); return -1; } for (i = 0; i < MAX_MESSAGE_HANDLERS; i++) { if (cmd_mqtt.sub_topic[i] != NULL && !strncmp(cmd_mqtt.sub_topic[i], info->topic, strlen(info->topic))) { free(cmd_mqtt.sub_topic[i]); cmd_mqtt.sub_topic[i] = NULL; } } if (i >= MAX_MESSAGE_HANDLERS) { printf("MQTT unsubscribe fail.No such topic\n"); return -1; } printf("MQTT unsubscribe success.\n"); return 0; } static int mqtt_cmd_publish_handler(Client *client, Network *network, void *data, int data_len) { int ret; MQTTMessage message; struct publish_info *info = data; message.qos = info->qos; message.retained = info->retain; message.payload = info->message; message.payloadlen = strlen(info->message); ret = MQTTPublish(client, info->topic, &message); if (ret != 0) { printf("Return code from MQTT publish is %d\n", ret); return -1; } printf("MQTT publish success.\n"); return 0; } static void mqtt_client_task(void *arg) { int exit = 0; int connected = 0; OS_Status status; struct mqtt_msg *msg; Client client; Network network; while (!exit) { status = OS_MsgQueueReceive(&cmd_mqtt.queue, (void **)&msg, 0); if (status != OS_OK) { if (connected) { MQTTYield(&client, 3000); } OS_MSleep(50); continue; } printf("msg type:%d\n", msg->type); switch (msg->type) { case MQTT_CMD_CONNECT: memset(&client, 0, sizeof(Client)); memset(&network, 0, sizeof(Network)); mqtt_cmd_connect_handler(&client, &network, msg->data, msg->data_len); connected = 1; break; case MQTT_CMD_DISCONNECT: mqtt_cmd_disconnect_handler(&client, &network, msg->data, msg->data_len); connected = 0; break; case MQTT_CMD_SUBSCRIBE: mqtt_cmd_subscribe_handler(&client, &network, msg->data, msg->data_len); break; case MQTT_CMD_UNSUBSCRIBE: mqtt_cmd_unsubscribe_handler(&client, &network, msg->data, msg->data_len); break; case MQTT_CMD_PUBLISH: mqtt_cmd_publish_handler(&client, &network, msg->data, msg->data_len); break; case MQTT_CMD_EXIT: exit = 1; break; default: break; } free(msg->data); free(msg); } OS_ThreadDelete(&cmd_mqtt.thread); } /* MQTT Client Thread */ static void mqtt_thread_init(void) { memset(&cmd_mqtt, 0, sizeof(cmd_mqtt)); if (OS_MsgQueueCreate(&cmd_mqtt.queue, 4) != OS_OK) { printf("create queue failed\n"); return; } printf("mqtt msgqueue successed\n"); if (OS_ThreadCreate(&cmd_mqtt.thread, "mqtt client", mqtt_client_task, NULL, OS_THREAD_PRIO_APP, (6 * 1024)) != OS_OK) { printf("create thread failed\n"); OS_MsgQueueDelete(&cmd_mqtt.queue); return; } } static void mqtt_init(void) { OS_Status status; struct mqtt_msg *msg; struct subscribe_info *info; /* MQTT configuration init */ strcpy(cmd_mqtt.host, MQTT_HOST); strcpy(cmd_mqtt.port, MQTT_PORT); strcpy(cmd_mqtt.client_id, MQTT_CLIENTID); cmd_mqtt.username = MQTT_USERNAME; cmd_mqtt.password = MQTT_PASSWORD; cmd_mqtt.alive = MQTT_KEEPALIVE; cmd_mqtt.ssl = MQTT_SSL; cmd_mqtt.clean = MQTT_CLEAN; /* Send 'Connect' message to MQTT thread */ msg = malloc(sizeof(struct mqtt_msg)); if (msg == NULL) { return; } memset(msg, 0, sizeof(struct mqtt_msg)); msg->type = MQTT_CMD_CONNECT; msg->data = NULL; msg->data_len = 0; status = OS_MsgQueueSend(&cmd_mqtt.queue, msg, OS_WAIT_FOREVER); if (status != OS_OK) { free(msg); return; } /* Send 'Subscribe' message to MQTT thread */ info = malloc(sizeof(struct subscribe_info)); if (info == NULL) { return; } memset(info, 0, sizeof(struct subscribe_info)); info->qos = 0; strcpy(info->topic, MQTT_SUBSCRIBE_TOPIC); msg = malloc(sizeof(struct mqtt_msg)); if (msg == NULL) { free(info); return; } memset(msg, 0, sizeof(struct mqtt_msg)); msg->type = MQTT_CMD_SUBSCRIBE; msg->data = info; msg->data_len = sizeof(struct subscribe_info); status = OS_MsgQueueSend(&cmd_mqtt.queue, msg, OS_WAIT_FOREVER); if (status != OS_OK) { free(info); free(msg); return; } } /* Publish Temperature/Humidity/Power to MQTT */ static void mqtt_publish_data(uint16_t temperature, uint16_t humidity, uint16_t power) { OS_Status status; struct mqtt_msg *msg; struct publish_info *info; info = malloc(sizeof(struct publish_info)); if (info == NULL) { return; } memset(info, 0, sizeof(struct publish_info)); info->qos = 0; info->retain = 0; strcpy(info->topic, MQTT_PUBLISH_TOPIC); sprintf(info->message, "{\"id\":\"123\",\"version\":\"1.0\",\"params\":{\"Temperature\":{\"value\":%.2f}, \"Humidity\": {\"value\": %d}, \"Power\": {\"value\": %d}},\"method\":\"thing.event.property.post\"}", temperature / 100.0, humidity, power); msg = malloc(sizeof(struct mqtt_msg)); if (msg == NULL) { free(info); return; } memset(msg, 0, sizeof(struct mqtt_msg)); msg->type = MQTT_CMD_PUBLISH; msg->data = info; msg->data_len = sizeof(struct publish_info); status = OS_MsgQueueSend(&cmd_mqtt.queue, msg, OS_WAIT_FOREVER); if (status != OS_OK) { free(info); free(msg); return; } return; }
4. 连接米家蓝牙温湿度计,读取数据并发送给MQTT任务
/* BLE config */ #define MIJIA_SENSOR_ADDR "A4:C1:38:0E:B4:AB (public)" #define MIJIA_SENSOR_HANDLE 0x36 #define CENTRAL_PERIOD 10000 // 10 seconds /* Global variables */ static struct bt_conn *default_conn; static OS_Timer_t central_timer; static struct bt_gatt_read_params read_params; static void start_scan(void); static void mqtt_publish_data(uint16_t temperature, uint16_t humidity, uint16_t power); /* Data processor - Temperature, Humidity, Power */ static uint8_t read_func(struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, const void *data, uint16_t length) { uint32_t i; uint16_t temperature, humidity, power; printf("Read complete: err 0x%02x length %u\n", err, length); if (length == 5) { /* Print raw data */ printf("Read Value:"); for (i = 0; i < length; i++) { printf("%02x", ((const uint8_t *)data)[i]); } printf("\n"); /* Print processed data */ temperature = (((const uint8_t *)data)[1] << 8) | ((const uint8_t *)data)[0]; humidity = ((const uint8_t *)data)[2]; power = (((const uint8_t *)data)[4] << 8) | ((const uint8_t *)data)[3]; printf("Temperature: %.2f, Humidity: %d%%, Power: %dmV\n", temperature / 100.0, humidity, power); /* Send data to mqtt thread */ mqtt_publish_data(temperature, humidity, power); } if (!data) { (void)memset(params, 0, sizeof(*params)); return BT_GATT_ITER_STOP; } return BT_GATT_ITER_CONTINUE; } /* Read Temperature/Humidity/Power periodically */ static void central_timer_cb(void *arg) { int err; if (!default_conn) { printf("Not connected\n"); return; } if (read_params.func) { printf("Read ongoing\n"); return; } read_params.func = read_func; read_params.handle_count = 1; read_params.single.handle = MIJIA_SENSOR_HANDLE; read_params.single.offset = 0U; err = bt_gatt_read(default_conn, &read_params); if (err) { printf("Read failed (err %d)\n", err); } else { printf("Read pending\n"); } return; } static void disconnected(struct bt_conn *conn, uint8_t reason) { int err; struct bt_conn_info info; err = bt_conn_get_info(conn, &info); if (err) { printf("Failed to get info\n"); return ; } printf("[H] Disconnected %s reason 0x%02x \n", bt_addr_str_real(&info.le.dst->a), reason); if (default_conn != conn) { return ; } default_conn = NULL; start_scan(); } static void connected(struct bt_conn *conn, uint8_t conn_err) { bt_addr_le_t rpa = {0}; int result; struct bt_conn_info info; OS_Status status; if (conn_err) { printf("[H] Connectionfailed reason %u\n", conn_err); default_conn = NULL; start_scan(); } else { result = bt_conn_get_info(conn, &info); if (result) { printf("Failed to get info\n"); return ; } memcpy(&rpa, &info.le.src, sizeof(bt_addr_le_t)); printf("[H] Connected!! \n"); printf("========== Connection Parameter ==========\n"); printf("= Remote Address %s\n", bt_addr_str_real(&rpa.a)); printf("= Internval %d\n", info.le.interval); printf("= Latency %d\n", info.le.latency); printf("= Timeout %d\n", info.le.timeout); printf("==========================================\n"); if (conn == default_conn) { status = OS_TimerCreate(¢ral_timer, OS_TIMER_PERIODIC, central_timer_cb, NULL, CENTRAL_PERIOD); if (status != OS_OK) { printk("central timer create error (err 0x%02x)\n", status); return ; } OS_TimerStart(¢ral_timer); } } } static struct bt_conn_cb conn_callbacks = { .connected = connected, .disconnected = disconnected, }; static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, struct net_buf_simple *ad) { int err; char dev[BT_ADDR_LE_STR_LEN]; bt_addr_le_to_str(addr, dev, sizeof(dev)); printf("[DEVICE]: %s, AD evt type %u, AD data len %u, RSSI %i\n", dev, type, ad->len, rssi); /* Stop scanning and connect to MIJIA sensor while MIJIA sensor is found */ if (!strcmp(dev, MIJIA_SENSOR_ADDR)) { err = bt_le_scan_stop(); if (err) { printf("Stop LE scan failed (err %d)\n", err); return; } err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT, &default_conn); if (err) { printf("Create connection failed (err %d)\n", err); start_scan(); return; } } } static void start_scan(void) { int err; err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, device_found); if (err) { printf("Scanning failed to start (err %d)\n", err); return ; } printf("Scanning successfully started\n"); } static void bt_ready(int err) { if (err) { printf("Bluetooth init failed (err %d)\n", err); return ; } err = settings_load(); if (err) { printf("Settings load failed (err %d)", err); return; } printf("Settings loaded"); bt_conn_cb_register(&conn_callbacks); printf("Bluetooth initialized\n"); start_scan(); } static void ble_init(void) { int err; err = bt_ctrl_enable(); if (err) { printf("Bluetooth Controller init failed (err %d)", err); return; } err = bt_enable(bt_ready); if (err) { printf("Bluetooth init failed (err %d)", err); } return; }
四、调试记录
===================================================================== Hello! OpenHarmony! System tag : OpenHarmony 1.1.2_LTS ==================================================================== use default flash chip mJedec 0x0 [FD I]: mode: 0x10, freq: 96000000Hz, drv: 0 [FD I]: jedec: 0x0, suspend_support: 1 mode select:e wlan information =================================================== firmware: version : R0-XR_C07.08.52.65_02.84 May 27 2021 11:41:33-Y02.84 buffer : 8 driver: version : XR_V02.05 mac address: in use : 4c:a8:89:8c:54:01 in use : 4c:a8:89:8c:54:02 ==================================================================== wlan mode:a [VFS INF] SPIFFS mount success. platform information =============================================== XR806 SDK v1.2.0 Jan 3 2022 20:37:31 heap space [0x229cc8, 0x247c00), size 122680 cpu clock 160000000 Hz HF clock 40000000 Hz sdk option: XIP : enable INT LF OSC : enable SIP flash : enable mac address: efuse : 80:74:84:21:53:51 in use : 4c:a8:89:8c:54:01 ==================================================================== Enable Wifi STA mode... [net INF] no need to switch wlan mode 0 console init success en1: Trying to associate with 2e:15:e1:26:de:c4 (SSID='xxxxxxxx' freq=2412 MHz) [net INF] msg <wlan scan success> en1: Associated with 2e:15:e1:26:de:c4 en1: WPA: Key negotiation completed with 2e:15:e1:26:de:c4 [PTK=CCMP GTK=CCMP] en1: CTRL-EVENT-CONNECTED - Connection to 2e:15:e1:26:de:c4 completed [id=0 id_str=] [net INF] msg <wlan connected> [net INF] netif is link up [net INF] start DHCP... [net INF] netif (IPv4) is up [net INF] address: 192.168.6.189 [net INF] gateway: 192.168.6.1 [net INF] netmask: 255.255.255.0 [net INF] msg <network IPv6 state> [net INF] IPv6 addr state change: 0x0 --> 0x1 [net INF] msg <> mqtt msgqueue successed ble controller open version : 9.1.19 build sha1 : v9.1.19-20210601 build date : Jun 1 2021 build time : 19:32:17 platform : xr806 msg type:0 ble rf_init done! BLE INIT ALL DONE! BT Coex. Init. OK. == XRadio BLE HOST V2.5.0 == hiview init success.[bt] [WRN] set_flow_control: Controller to host flow control not supported WAR drop=1117, fctl=0x00d0. [bt] [INF] bt_init: No ID address. App must call settings_load() [bt] [INF] bt_dev_show_info: Identity: CF:BD:6B:5B:36:BD (random) [bt] [INF] bt_dev_show_info: HCI: version 5.0 (0x09) revision 0x0113, manufacturer 0x063d [bt] [INF] bt_dev_show_info: LMP: version 5.0 (0x09) subver 0x0113 Settings loadedBluetooth initialized ************************************************* [RandomAddress 6D:20:CA:CE:17:E9 ] ************************************************* Scanning successfully started [DEVICE]: 24:95:D5:62:F4:5E (random), AD evt type 3, AD data len 31, RSSI -44 MQTT connect success. msg type:2 MQTT subscribe success. [DEVICE]: A4:C1:38:F6:A5:33 (public), AD evt type 0, AD data len 19, RSSI -77 [DEVICE]: A4:C1:38:0E:B4:AB (public), AD evt type 0, AD data len 19, RSSI -69 [H] Connected!! ========== Connection Parameter ========== = Remote Address 21:69:E0:00:20:F0 = Internval 32 = Latency 0 = Timeout 400 ========================================== Read pending Read complete: err 0x00 length 5 Read Value:5d0637830a Temperature: 16.29, Humidity: 55%, Power: 2691mV Read complete: err 0x00 length 0 msg type:4 MQTT publish success. Read pending Read complete: err 0x00 length 5 Read Value:5d0637830a Temperature: 16.29, Humidity: 55%, Power: 2691mV Read complete: err 0x00 length 0 msg type:4 MQTT publish success. Read pending Read complete: err 0x00 length 5 Read Value:5d0637830a Temperature: 16.29, Humidity: 55%, Power: 2691mV Read complete: err 0x00 length 0 msg type:4 MQTT publish success. Read pending Read complete: err 0x00 length 5 Read Value:5a0637830a Temperature: 16.26, Humidity: 55%, Power: 2691mV Read complete: err 0x00 length 0 msg type:4 MQTT publish success.
五、阿里云数据显示
- 刚开始记录数据时
- 过夜记录数据
-
发现蓝牙连了以后,电池消耗大加快了。
Copyright © 2024 深圳全志在线有限公司 粤ICP备2021084185号 粤公网安备44030502007680号