feat: 移植腾讯云物联网开发平台 C SDK
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
file(GLOB src_at_module ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
|
||||
set(inc_at_module ${CMAKE_CURRENT_SOURCE_DIR}/inc/)
|
||||
|
||||
set(src_services ${src_services} ${src_at_module} PARENT_SCOPE)
|
||||
set(inc_services ${inc_services} ${inc_at_module} PARENT_SCOPE)
|
@@ -0,0 +1,327 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2022 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file at_mqtt_client.h
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2022-04-19
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2022-04-19 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#ifndef IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_AT_MODULE_INC_AT_MODULE_H_
|
||||
#define IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_AT_MODULE_INC_AT_MODULE_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "qcloud_iot_mqtt_client.h"
|
||||
|
||||
/**
|
||||
* @brief Max number of topic subscribed
|
||||
*
|
||||
*/
|
||||
#define MAX_MESSAGE_HANDLERS (10)
|
||||
|
||||
/**
|
||||
* @brief Max number of ota fw version
|
||||
*
|
||||
*/
|
||||
#define MAX_FW_VERSION_STRING (32)
|
||||
|
||||
/**
|
||||
* @brief Max publish length
|
||||
*
|
||||
*/
|
||||
#define MAX_PUB_SHORT_LEN 128
|
||||
|
||||
/**
|
||||
* @brief Subscribe status of topic.
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
SUB_ACK_NOT_RECEIVED = 0,
|
||||
SUB_ACK_RECEIVED = 1,
|
||||
} SubStatus;
|
||||
|
||||
/**
|
||||
* @brief data structure for topic subscription handle
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
char *topic_filter; /**< topic name, wildcard filter is supported */
|
||||
SubStatus status; /**< status of sub handle */
|
||||
SubscribeParams params; /**< params needed to subscribe */
|
||||
} SubTopicHandle;
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
TLS_MODE_NO_TLS = 0,
|
||||
TLS_MODE_TLS_PSK = 1,
|
||||
TLS_MODE_TLS_CERT = 2,
|
||||
} QcloudIotTlsMode;
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
MQTT_CONNECT_STATE_CONNECTED = 0,
|
||||
MQTT_CONNECT_STATE_RECONNECTING,
|
||||
MQTT_CONNECT_STATE_RECONNECTED,
|
||||
MQTT_CONNECT_STATE_DISCONNECT,
|
||||
} MQTTConnectState;
|
||||
|
||||
/**
|
||||
* @brief MQTT QCloud IoT Client structure
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
DeviceInfo *device_info; /**< device info needed to connect server */
|
||||
uint32_t command_timeout_ms; /**< MQTT command timeout, unit:ms */
|
||||
MQTTEventHandler event_handle; /**< callback for MQTT event */
|
||||
uint8_t default_subscribe; /**< no subscribe packet send, only add subhandle */
|
||||
void *lock_generic; /**< mutex/lock for this client struture */
|
||||
SubTopicHandle sub_handles[MAX_MESSAGE_HANDLERS]; /**< subscription handle array */
|
||||
uint8_t read_buf[QCLOUD_IOT_MQTT_RX_BUF_LEN]; /**< MQTT read buffer */
|
||||
size_t read_buf_size; /**< size of MQTT read buffer */
|
||||
MQTTConnectState connect_state; /**< connect state */
|
||||
|
||||
// connect options using in module
|
||||
QcloudIotTlsMode tls_mode; /**< 0:no tls; 1:tls+psk; 2:tls+cert */
|
||||
uint8_t auto_connect_enable; /**< enable auto connection or not */
|
||||
uint8_t clean_session; /**< clean session option */
|
||||
uint16_t keep_alive_interval; /**< keepalive option */
|
||||
} QcloudIotClient;
|
||||
|
||||
/**
|
||||
* @brief Cls state.
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
IOT_OTA_CLS_STATE_NO_REPORT = 0,
|
||||
IOT_OTA_CLS_STATE_REPORT_MCU,
|
||||
IOT_OTA_CLS_STATE_REPORT_MODULE, /**< only support in at set 1.8.0 */
|
||||
IOT_OTA_CLS_STATE_REPORT_BOTH /**< only support in at set 1.8.0 */
|
||||
} QcloudIotOtaClsState;
|
||||
|
||||
/**
|
||||
* @brief OTA fw info.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
char version[MAX_FW_VERSION_STRING];
|
||||
uint32_t fw_size;
|
||||
uint32_t fw_max_size;
|
||||
char md5[33];
|
||||
void *get_fw_info_sem;
|
||||
} QcloudIotOtaFwInfo;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// cmd
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Set device info.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_device_info_set(const QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Connect mqtt server.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_mqtt_connect(const QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Disconnect mqtt server.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_mqtt_disconnect(const QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Publish mqtt message.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_name topic name to publish
|
||||
* @param[in] params params for publish
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_mqtt_publish(const QcloudIotClient *client, const char *topic_name, const PublishParams *params);
|
||||
|
||||
/**
|
||||
* @brief Subscribe mqtt topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter to subscribe
|
||||
* @param[in] qos max qos for subscribe
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_mqtt_subscribe(const QcloudIotClient *client, const char *topic_filter, int qos);
|
||||
|
||||
/**
|
||||
* @brief Unsubscribe mqtt topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter to unsubscribe
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_mqtt_unsubscribe(const QcloudIotClient *client, const char *topic_filter);
|
||||
|
||||
/**
|
||||
* @brief Get mqtt connect state
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_mqtt_get_connect_state(const QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Set fw version and report.
|
||||
*
|
||||
* @param[in] cls_state 0: off;1: only report mcu version;2: only report module version;3: report all
|
||||
* @param[in] version mcu fw version
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_ota_set_fw_version(QcloudIotOtaClsState cls_state, const char *version);
|
||||
|
||||
/**
|
||||
* @brief Get fw info.
|
||||
*
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_ota_get_fw_info(void);
|
||||
|
||||
/**
|
||||
* @brief Read fw data.
|
||||
*
|
||||
* @param[out] fw_data_buf data buffer
|
||||
* @param[out] fw_data_len data recv length
|
||||
* @param[in] timeout read timeout
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_ota_read_fw_data(void *fw_data_buf, uint32_t *fw_data_len, uint32_t timeout);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// urc
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Set mqtt urc to at module.
|
||||
*
|
||||
* @return 0 means successful.
|
||||
*/
|
||||
int module_set_mqtt_urc(void);
|
||||
|
||||
/**
|
||||
* @brief Set ota urc to at module.
|
||||
*
|
||||
* @return 0 means successful.
|
||||
*/
|
||||
int module_set_ota_urc(void);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// mqtt
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Get pointer to mqtt client.
|
||||
*
|
||||
* @return pointer to mqtt client.
|
||||
*/
|
||||
QcloudIotClient *qcloud_iot_get_mqtt_client(void);
|
||||
|
||||
/**
|
||||
* @brief Serialize and send subscribe packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic to subscribe
|
||||
* @param[in] params subscribe params
|
||||
* @return >=0 for packet id, < 0 for failed @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_subscribe(QcloudIotClient *client, const char *topic_filter, const SubscribeParams *params);
|
||||
|
||||
/**
|
||||
* @brief Serialize and send unsubscribe packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic to unsubscribe
|
||||
* @return >=0 packet id, < 0 for failed @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_unsubscribe(QcloudIotClient *client, const char *topic_filter);
|
||||
|
||||
/**
|
||||
* @brief Return if topic is sub ready.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter
|
||||
* @return true for ready
|
||||
* @return false for not ready
|
||||
*/
|
||||
bool qcloud_iot_mqtt_is_sub_ready(QcloudIotClient *client, const char *topic_filter);
|
||||
|
||||
/**
|
||||
* @brief Get usr data, usr should handle lock/unlock usrdata itself in callback and caller.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter
|
||||
* @return NULL or user data
|
||||
*/
|
||||
void *qcloud_iot_mqtt_get_subscribe_usr_data(QcloudIotClient *client, const char *topic_filter);
|
||||
|
||||
/**
|
||||
* @brief Clear sub handle array.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
void qcloud_iot_mqtt_sub_handle_array_clear(QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Deliver message to subscribe message handler.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_name topic name, no wildcard
|
||||
* @param[in] payload publish packet payload
|
||||
* @param[in] payload_len payload length
|
||||
*/
|
||||
void qcloud_iot_deliver_message(QcloudIotClient *client, char *topic_name, char *payload, int payload_len);
|
||||
|
||||
/**
|
||||
* @brief Get ota info handle.
|
||||
*
|
||||
* @return @see QcloudIotOtaFwInfo
|
||||
*/
|
||||
QcloudIotOtaFwInfo *qcloud_iot_get_ota_info_handle(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_AT_MODULE_INC_AT_MODULE_H_
|
@@ -0,0 +1,474 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2022 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file at_mqtt_cmd.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2022-04-19
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2022-04-19 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "at_module.h"
|
||||
|
||||
/**
|
||||
* @brief AT Command for Iot Hub
|
||||
* 1. network_register:
|
||||
* AT+TCREGNET=?
|
||||
* AT+TCREGNET?
|
||||
* AT+TCREGNET=<module_type>,<action>[,<ssid>,<pw>][,<apn>]
|
||||
* 2. device_info:
|
||||
* AT+TCDEVINFOSET=?
|
||||
* AT+TCDEVINFOSET?
|
||||
* AT+TCDEVINFOSET=<tlsmode>,<productId>,<devicename>[,<devicesecret>][,<certname>]
|
||||
* 3. cert:
|
||||
* AT+TCCERTADD=?
|
||||
* AT+TCCERTADD?
|
||||
* AT+TCCERTADD=<cert_name>,<cert_size>
|
||||
* AT+TCCERTCHECK=?
|
||||
* AT+TCCERTCHECK?
|
||||
* AT+TCCERTCHECK=<cert_name>
|
||||
* AT+TCCERTDEL=?
|
||||
* AT+TCCERTDEL?
|
||||
* AT+TCCERTDEL=<cert_name>
|
||||
* 4. dynamic register:
|
||||
* AT+TCPRDINFOSET=?
|
||||
* AT+TCPRDINFOSET?
|
||||
* AT+TCPRDINFOSET=<tls_mode>,<product_ID>,<product_secret>,<device_name>
|
||||
* AT+TCDEVREG=?
|
||||
* AT+TCDEVREG
|
||||
* 5. module info
|
||||
* AT+TCMODULE
|
||||
* AT+TCRESTORE=?
|
||||
* 6. mqtt
|
||||
* AT+TCMQTTCONN=?
|
||||
* AT+TCMQTTCONN?
|
||||
* AT+TCMQTTCONN=<tlsmode>,<cmdtimeout>,<keepalive>,<clean_session>,<reconnect>
|
||||
* AT+TCMQTTDISCONN=?
|
||||
* AT+TCMQTTDISCONN
|
||||
* AT+TCMQTTPUB=?
|
||||
* AT+TCMQTTPUB=<topic>,<qos>,<message>
|
||||
* AT+TCMQTTPUBL=?
|
||||
* AT+TCMQTTPUBL= <topic>,<qos>,<msg_length>
|
||||
* AT+TCMQTTSUB=?
|
||||
* AT+TCMQTTSUB?
|
||||
* AT+TCMQTTSUB=<topic>,<qos>
|
||||
* AT+TCMQTTUNSUB=?
|
||||
* AT+TCMQTTUNSUB?
|
||||
* AT+TCMQTTUNSUB=<topic>
|
||||
* AT+TCMQTTSTATE=?
|
||||
* AT+TCMQTTSTATE?
|
||||
* 7. ota
|
||||
* AT+TCOTASET=?
|
||||
* AT+TCOTASET?
|
||||
* AT+TCOTASET=<ctlstate>,<fw_ver>
|
||||
* AT+TCFWINFO=?
|
||||
* AT+TCFWINFO?
|
||||
* AT+TCREADFWDATA=?
|
||||
* AT+TCREADFWDATA=<len>
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief AT module cmd.
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
MODULE_CMD_SET_DEVICE_INFO, // AT+TCDEVINFOSET=<tlsmode>,<productId>,<devicename>[,<devicesecret>][,<certname>]
|
||||
MODULE_CMD_CONNECT, // AT+TCMQTTCONN=<tlsmode>,<cmdtimeout>,<keepalive>,<clean_session>,<reconnect>
|
||||
MODULE_CMD_DISCONNECT, // AT+TCMQTTDISCONN
|
||||
MODULE_CMD_PUBLISH, // AT+TCMQTTPUB=<topic>,<qos>,<message>
|
||||
MODULE_CMD_PUBLISHL, // AT+TCMQTTPUBL=<topic>,<qos>,<msg_length>
|
||||
MODULE_CMD_SUBSCRIBE, // AT+TCMQTTSUB=<topic>,<qos>
|
||||
MODULE_CMD_UNSUBSCRIBE, // AT+TCMQTTUNSUB=<topic>
|
||||
MODULE_CMD_GET_CONNECT_STATUS, // AT+TCMQTTSTATE?
|
||||
MODULE_CMD_SET_OTA_VERSION, // AT+TCOTASET=<ctlstate>,<fw_ver>
|
||||
MODULE_CMD_GET_OTA_FW_INFO, // AT+TCFWINFO?
|
||||
MODULE_CMD_READ_OTA_FW_DATA // AT+TCREADFWDATA=<len>
|
||||
} ModuleCmd;
|
||||
|
||||
/**
|
||||
* @brief Publish cmd params.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
const char *topic_name;
|
||||
const PublishParams *params;
|
||||
} ModuleCmdPublishParams;
|
||||
|
||||
/**
|
||||
* @brief Subscribe cmd params.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
const char *topic_filter;
|
||||
int qos;
|
||||
} ModuleCmdSubscribeParams;
|
||||
|
||||
/**
|
||||
* @brief Unsubscribe cmd params.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
const char *topic_filter;
|
||||
} ModuleCmdUnsubscribeParams;
|
||||
|
||||
/**
|
||||
* @brief Set ota version to report.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
* @brief Control module report
|
||||
* 0: off
|
||||
* 1: only report mcu version
|
||||
* 2: only report module version
|
||||
* 3: report all
|
||||
*/
|
||||
QcloudIotOtaClsState cls_state;
|
||||
const char *version; /* ota version */
|
||||
} ModuleCmdSetOTAVersionParams;
|
||||
|
||||
/**
|
||||
* @brief Read fw data.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
void *fw_data_buf;
|
||||
uint32_t *fw_data_len;
|
||||
uint32_t timeout;
|
||||
} ModuleCmdReadOTFwDataParams;
|
||||
|
||||
/**
|
||||
* @brief Module cmd params.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
const QcloudIotClient *client;
|
||||
union {
|
||||
ModuleCmdPublishParams publish_params;
|
||||
ModuleCmdSubscribeParams subscribe_params;
|
||||
ModuleCmdUnsubscribeParams unsubscribe_params;
|
||||
ModuleCmdSetOTAVersionParams set_ota_version_params;
|
||||
ModuleCmdReadOTFwDataParams read_ota_fw_data_params;
|
||||
};
|
||||
} ModuleCmdParams;
|
||||
|
||||
/**
|
||||
* @brief Transfer '\"' and ','.
|
||||
*
|
||||
* @param[in,out] payload payload to transfer, reuse memory
|
||||
* @return length of payload
|
||||
*/
|
||||
static int _transfer_payload(char *payload, char **payload_transferred)
|
||||
{
|
||||
char *tmp, *src = payload;
|
||||
int count = 0;
|
||||
char c = '\0';
|
||||
|
||||
// 1. calculate '\"' and '.'
|
||||
while ((c = *src++) != '\0') {
|
||||
if (c == '\"' || c == ',') {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. malloc for payload
|
||||
int tmp_len = src - payload + count;
|
||||
tmp = HAL_Malloc(tmp_len);
|
||||
if (!tmp) {
|
||||
return 0;
|
||||
}
|
||||
memset(tmp, 0, tmp_len);
|
||||
|
||||
// 3. replace
|
||||
for (src = payload, count = 0; (c = *src) != '\0'; src++) {
|
||||
if (c == '\"' || c == ',') {
|
||||
tmp[count++] = '\\';
|
||||
}
|
||||
tmp[count++] = c;
|
||||
}
|
||||
|
||||
tmp[count] = '\0';
|
||||
*payload_transferred = tmp;
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send at cmd to at module.
|
||||
*
|
||||
* @param[in] cmd @see ModuleCmd
|
||||
* @param[in] params @see ModuleCmdParams
|
||||
* @return 0 for success
|
||||
*/
|
||||
static int _module_send_cmd(ModuleCmd cmd, ModuleCmdParams *params)
|
||||
{
|
||||
#define DEFAULT_TIMEOUT_MS 5000
|
||||
int rc, len = 0;
|
||||
char at_cmd[1250] = {0};
|
||||
char at_resp[32] = {0};
|
||||
char *payload_transferred = NULL;
|
||||
|
||||
const QcloudIotClient *client = params->client;
|
||||
|
||||
switch (cmd) {
|
||||
case MODULE_CMD_SET_DEVICE_INFO:
|
||||
strncpy(at_resp, "+TCDEVINFOSET:OK", sizeof(at_resp));
|
||||
len = HAL_Snprintf(at_cmd, sizeof(at_cmd), "AT+TCDEVINFOSET=%d,\"%s\",\"%s\",\"%s\"\r\n", client->tls_mode,
|
||||
client->device_info->product_id, client->device_info->device_name,
|
||||
client->device_info->device_secret);
|
||||
break;
|
||||
case MODULE_CMD_CONNECT:
|
||||
strncpy(at_resp, "+TCMQTTCONN:OK", sizeof(at_resp));
|
||||
len = HAL_Snprintf(at_cmd, sizeof(at_cmd), "AT+TCMQTTCONN=%d,%d,%d,%d,%d\r\n", client->tls_mode,
|
||||
client->command_timeout_ms, client->keep_alive_interval, client->clean_session,
|
||||
client->auto_connect_enable);
|
||||
break;
|
||||
case MODULE_CMD_DISCONNECT:
|
||||
len = HAL_Snprintf(at_cmd, sizeof(at_cmd), "AT+TCMQTTDISCONN\r\n");
|
||||
break;
|
||||
case MODULE_CMD_PUBLISHL:
|
||||
len = _transfer_payload(params->publish_params.params->payload, &payload_transferred);
|
||||
if (!len) {
|
||||
return QCLOUD_ERR_MALLOC;
|
||||
}
|
||||
|
||||
strncpy(at_resp, ">", sizeof(at_resp));
|
||||
HAL_Snprintf(at_cmd, sizeof(at_cmd), "AT+TCMQTTPUBL=\"%s\",%d,%d\r\n", params->publish_params.topic_name,
|
||||
params->publish_params.params->qos, len);
|
||||
rc = HAL_Module_SendAtCmdWaitResp(at_cmd, at_resp, client->command_timeout_ms);
|
||||
if (rc) {
|
||||
HAL_Free(payload_transferred);
|
||||
return rc;
|
||||
}
|
||||
|
||||
strncpy(at_resp, "+TCMQTTPUBL:OK", sizeof(at_resp));
|
||||
rc = HAL_Module_SendAtData(payload_transferred, len);
|
||||
HAL_Free(payload_transferred);
|
||||
if (rc) {
|
||||
return rc;
|
||||
}
|
||||
return HAL_Module_SendAtCmdWaitResp("\r\n", at_resp, client->command_timeout_ms);
|
||||
case MODULE_CMD_PUBLISH:
|
||||
strncpy(at_resp, "+TCMQTTPUB:OK", sizeof(at_resp));
|
||||
len = _transfer_payload(params->publish_params.params->payload, &payload_transferred);
|
||||
if (!len) {
|
||||
return QCLOUD_ERR_MALLOC;
|
||||
}
|
||||
len = HAL_Snprintf(at_cmd, sizeof(at_cmd), "AT+TCMQTTPUB=\"%s\",%d,\"%s\"\r\n",
|
||||
params->publish_params.topic_name, params->publish_params.params->qos,
|
||||
payload_transferred);
|
||||
HAL_Free(payload_transferred);
|
||||
break;
|
||||
case MODULE_CMD_SUBSCRIBE:
|
||||
strncpy(at_resp, "+TCMQTTSUB:OK", sizeof(at_resp));
|
||||
len = HAL_Snprintf(at_cmd, sizeof(at_cmd), "AT+TCMQTTSUB=\"%s\",%d\r\n",
|
||||
params->subscribe_params.topic_filter, params->subscribe_params.qos);
|
||||
break;
|
||||
case MODULE_CMD_UNSUBSCRIBE:
|
||||
strncpy(at_resp, "+TCMQTTUNSUB:OK", sizeof(at_resp));
|
||||
len = HAL_Snprintf(at_cmd, sizeof(at_cmd), "AT+TCMQTTUNSUB=\"%s\"\r\n",
|
||||
params->subscribe_params.topic_filter);
|
||||
break;
|
||||
case MODULE_CMD_GET_CONNECT_STATUS:
|
||||
len = HAL_Snprintf(at_cmd, sizeof(at_cmd), "AT+TCMQTTSTATE?\r\n");
|
||||
break;
|
||||
case MODULE_CMD_SET_OTA_VERSION:
|
||||
strncpy(at_resp, "+TCOTASET:OK", sizeof(at_resp));
|
||||
len = HAL_Snprintf(at_cmd, sizeof(at_cmd), "AT+TCOTASET=%d,\"%s\"\r\n",
|
||||
params->set_ota_version_params.cls_state, params->set_ota_version_params.version);
|
||||
break;
|
||||
case MODULE_CMD_GET_OTA_FW_INFO:
|
||||
len = HAL_Snprintf(at_cmd, sizeof(at_cmd), "AT+TCFWINFO?\r\n");
|
||||
break;
|
||||
case MODULE_CMD_READ_OTA_FW_DATA:
|
||||
strncpy(at_resp, "+TCREADFWDATA:%u,", sizeof(at_resp));
|
||||
HAL_Snprintf(at_cmd, sizeof(at_cmd), "AT+TCREADFWDATA=%d\r\n",
|
||||
*(params->read_ota_fw_data_params.fw_data_len));
|
||||
return HAL_Module_SendAtCmdWaitRespWithData(at_cmd, at_resp, params->read_ota_fw_data_params.fw_data_buf,
|
||||
params->read_ota_fw_data_params.fw_data_len,
|
||||
params->read_ota_fw_data_params.timeout);
|
||||
default:
|
||||
return QCLOUD_ERR_INVAL;
|
||||
}
|
||||
return len ? HAL_Module_SendAtCmdWaitResp(at_cmd, at_resp[0] ? at_resp : NULL,
|
||||
client ? client->command_timeout_ms : DEFAULT_TIMEOUT_MS)
|
||||
: QCLOUD_ERR_BUF_TOO_SHORT;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// device info
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Set device info.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_device_info_set(const QcloudIotClient *client)
|
||||
{
|
||||
ModuleCmdParams cmd_params = {0};
|
||||
cmd_params.client = client;
|
||||
return _module_send_cmd(MODULE_CMD_SET_DEVICE_INFO, &cmd_params);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// mqtt
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Connect mqtt server.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_mqtt_connect(const QcloudIotClient *client)
|
||||
{
|
||||
ModuleCmdParams cmd_params = {0};
|
||||
cmd_params.client = client;
|
||||
return _module_send_cmd(MODULE_CMD_CONNECT, &cmd_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnect mqtt server.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_mqtt_disconnect(const QcloudIotClient *client)
|
||||
{
|
||||
ModuleCmdParams cmd_params = {0};
|
||||
cmd_params.client = client;
|
||||
return _module_send_cmd(MODULE_CMD_DISCONNECT, &cmd_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish mqtt message.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_name topic name to publish
|
||||
* @param[in] params params for publish
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_mqtt_publish(const QcloudIotClient *client, const char *topic_name, const PublishParams *params)
|
||||
{
|
||||
ModuleCmdParams cmd_params = {0};
|
||||
cmd_params.client = client;
|
||||
cmd_params.publish_params.params = params;
|
||||
cmd_params.publish_params.topic_name = topic_name;
|
||||
return params->payload_len > MAX_PUB_SHORT_LEN ? _module_send_cmd(MODULE_CMD_PUBLISHL, &cmd_params)
|
||||
: _module_send_cmd(MODULE_CMD_PUBLISH, &cmd_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Subscribe mqtt topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter to subscribe
|
||||
* @param[in] qos max qos for subscribe
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_mqtt_subscribe(const QcloudIotClient *client, const char *topic_filter, int qos)
|
||||
{
|
||||
ModuleCmdParams cmd_params = {0};
|
||||
cmd_params.client = client;
|
||||
cmd_params.subscribe_params.topic_filter = topic_filter;
|
||||
cmd_params.subscribe_params.qos = qos;
|
||||
return _module_send_cmd(MODULE_CMD_SUBSCRIBE, &cmd_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unsubscribe mqtt topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter to unsubscribe
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_mqtt_unsubscribe(const QcloudIotClient *client, const char *topic_filter)
|
||||
{
|
||||
ModuleCmdParams cmd_params = {0};
|
||||
cmd_params.client = client;
|
||||
cmd_params.unsubscribe_params.topic_filter = topic_filter;
|
||||
return _module_send_cmd(MODULE_CMD_UNSUBSCRIBE, &cmd_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get mqtt connect state
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_mqtt_get_connect_state(const QcloudIotClient *client)
|
||||
{
|
||||
ModuleCmdParams cmd_params = {0};
|
||||
cmd_params.client = client;
|
||||
return _module_send_cmd(MODULE_CMD_GET_CONNECT_STATUS, &cmd_params);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ota
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Set fw version and report.
|
||||
*
|
||||
* @param[in] cls_state 0: off;1: only report mcu version;2: only report module version;3: report all
|
||||
* @param[in] version mcu fw version
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_ota_set_fw_version(QcloudIotOtaClsState cls_state, const char *version)
|
||||
{
|
||||
ModuleCmdParams cmd_params = {0};
|
||||
cmd_params.client = NULL;
|
||||
cmd_params.set_ota_version_params.cls_state = cls_state;
|
||||
cmd_params.set_ota_version_params.version = version;
|
||||
return _module_send_cmd(MODULE_CMD_SET_OTA_VERSION, &cmd_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get fw info.
|
||||
*
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_ota_get_fw_info(void)
|
||||
{
|
||||
ModuleCmdParams cmd_params = {0};
|
||||
cmd_params.client = NULL;
|
||||
return _module_send_cmd(MODULE_CMD_GET_OTA_FW_INFO, &cmd_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read fw data.
|
||||
*
|
||||
* @param[out] fw_data_buf data buffer
|
||||
* @param[out] fw_data_len data recv length
|
||||
* @param[in] timeout read timeout
|
||||
* @return 0 for success
|
||||
*/
|
||||
int module_ota_read_fw_data(void *fw_data_buf, uint32_t *fw_data_len, uint32_t timeout)
|
||||
{
|
||||
ModuleCmdParams cmd_params = {0};
|
||||
cmd_params.client = NULL;
|
||||
cmd_params.read_ota_fw_data_params.fw_data_buf = fw_data_buf;
|
||||
cmd_params.read_ota_fw_data_params.fw_data_len = fw_data_len;
|
||||
cmd_params.read_ota_fw_data_params.timeout = timeout;
|
||||
return _module_send_cmd(MODULE_CMD_READ_OTA_FW_DATA, &cmd_params);
|
||||
}
|
@@ -0,0 +1,407 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2022 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file at_module_mqtt_client.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2022-04-25
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2022-04-25 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "at_module.h"
|
||||
|
||||
/**
|
||||
* @brief Init mqtt client.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] params mqtt init params, @see MQTTInitParams
|
||||
* @return @see IotReturnCode
|
||||
*
|
||||
* @note
|
||||
* 1. init device info.
|
||||
* 2. init params.
|
||||
* 3. set mqtt urc to at module.
|
||||
* 4. set device info to at module.
|
||||
*/
|
||||
static int _qcloud_iot_mqtt_client_init(QcloudIotClient *client, const MQTTInitParams *params)
|
||||
{
|
||||
memset(client, 0x0, sizeof(QcloudIotClient));
|
||||
client->device_info = params->device_info;
|
||||
client->command_timeout_ms = params->command_timeout;
|
||||
client->event_handle = params->event_handle;
|
||||
client->default_subscribe = params->default_subscribe;
|
||||
client->connect_state = MQTT_CONNECT_STATE_DISCONNECT;
|
||||
|
||||
client->tls_mode = TLS_MODE_TLS_PSK;
|
||||
client->auto_connect_enable = params->auto_connect_enable;
|
||||
client->clean_session = params->clean_session;
|
||||
client->keep_alive_interval = params->keep_alive_interval > 690 ? 690 : params->keep_alive_interval;
|
||||
|
||||
client->lock_generic = HAL_MutexCreate();
|
||||
if (!client->lock_generic) {
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
// 1. detect module && close echo
|
||||
int rc = HAL_Module_Init();
|
||||
if (rc) {
|
||||
Log_e("module init fail %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// 2. set urc function
|
||||
rc = module_set_mqtt_urc();
|
||||
if (rc) {
|
||||
Log_e("set mqtt urc fail %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// 3. set device info
|
||||
rc = module_device_info_set(client);
|
||||
if (rc) {
|
||||
Log_e("module init fail %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// 4. connect network
|
||||
return HAL_Module_ConnectNetwork();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deinit mqtt client.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
static void _qcloud_iot_mqtt_client_deinit(QcloudIotClient *client)
|
||||
{
|
||||
qcloud_iot_mqtt_sub_handle_array_clear(client);
|
||||
HAL_MutexDestroy(client->lock_generic);
|
||||
HAL_Module_Deinit();
|
||||
Log_i("release mqtt client resources");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// API
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Only support one mqtt client when using at module.
|
||||
*
|
||||
*/
|
||||
static QcloudIotClient *sg_mqtt_client;
|
||||
|
||||
/**
|
||||
* @brief Get pointer to mqtt client.
|
||||
*
|
||||
* @return pointer to mqtt client.
|
||||
*/
|
||||
QcloudIotClient *qcloud_iot_get_mqtt_client(void)
|
||||
{
|
||||
return sg_mqtt_client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create MQTT client and connect to MQTT server. Only one client supportted.
|
||||
*
|
||||
* @param[in] params MQTT init parameters
|
||||
* @return a valid MQTT client handle when success, or NULL otherwise
|
||||
*/
|
||||
void *IOT_MQTT_Construct(const MQTTInitParams *params)
|
||||
{
|
||||
POINTER_SANITY_CHECK(params, NULL);
|
||||
POINTER_SANITY_CHECK(params->device_info, NULL);
|
||||
STRING_PTR_SANITY_CHECK(params->device_info->product_id, NULL);
|
||||
STRING_PTR_SANITY_CHECK(params->device_info->device_name, NULL);
|
||||
#ifdef AUTH_MODE_CERT
|
||||
STRING_PTR_SANITY_CHECK(params->device_info->dev_cert_file_name, NULL);
|
||||
STRING_PTR_SANITY_CHECK(params->device_info->dev_key_file_name, NULL);
|
||||
#else
|
||||
STRING_PTR_SANITY_CHECK(params->device_info->device_secret, NULL);
|
||||
#endif
|
||||
if (sg_mqtt_client) {
|
||||
Log_e("only support one mqtt client in at module!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int rc = 0;
|
||||
|
||||
QcloudIotClient *client = NULL;
|
||||
|
||||
// create and init MQTTClient
|
||||
client = (QcloudIotClient *)HAL_Malloc(sizeof(QcloudIotClient));
|
||||
if (!client) {
|
||||
Log_e("malloc MQTTClient failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rc = _qcloud_iot_mqtt_client_init(client, params);
|
||||
if (rc) {
|
||||
Log_e("mqtt init failed: %d", rc);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!params->connect_when_construct) {
|
||||
return client;
|
||||
}
|
||||
|
||||
rc = IOT_MQTT_Connect(client);
|
||||
if (rc) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
client->connect_state = MQTT_CONNECT_STATE_CONNECTED;
|
||||
Log_i("mqtt connect with success");
|
||||
sg_mqtt_client = client;
|
||||
return client;
|
||||
exit:
|
||||
_qcloud_iot_mqtt_client_deinit(client);
|
||||
HAL_Free(client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connect Mqtt server if not connect.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client pointer, should using the pointer of IOT_MQTT_Construct return.
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int IOT_MQTT_Connect(void *client)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
int rc = 0;
|
||||
|
||||
QcloudIotTimer connect_timer;
|
||||
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
if (mqtt_client->connect_state == MQTT_CONNECT_STATE_DISCONNECT) {
|
||||
IOT_Timer_CountdownMs(&connect_timer, MAX_RECONNECT_WAIT_INTERVAL);
|
||||
|
||||
do { // wait for wifi config
|
||||
rc = module_mqtt_connect(client);
|
||||
if (rc) {
|
||||
Log_e("mqtt connect with failed: %d", rc);
|
||||
HAL_SleepMs(QCLOUD_IOT_MQTT_WAIT_ACK_TIMEOUT);
|
||||
}
|
||||
if (IOT_Timer_Expired(&connect_timer)) {
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
} while (rc);
|
||||
}
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Close connection and destroy MQTT client.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client pointer, should using the pointer of IOT_MQTT_Construct return.
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int IOT_MQTT_Destroy(void **client)
|
||||
{
|
||||
POINTER_SANITY_CHECK(*client, QCLOUD_ERR_INVAL);
|
||||
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)(*client);
|
||||
|
||||
int rc = module_mqtt_disconnect(mqtt_client);
|
||||
_qcloud_iot_mqtt_client_deinit(mqtt_client);
|
||||
HAL_Free(*client);
|
||||
*client = sg_mqtt_client = NULL;
|
||||
Log_i("mqtt release!");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check connection state.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] timeout_ms timeout value (unit: ms) for this operation
|
||||
* @return QCLOUD_RET_SUCCESS when success, others @see IotReturnCode
|
||||
*/
|
||||
int IOT_MQTT_Yield(void *client, uint32_t timeout_ms)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
HAL_SleepMs(timeout_ms);
|
||||
module_mqtt_get_connect_state(mqtt_client);
|
||||
switch (mqtt_client->connect_state) {
|
||||
case MQTT_CONNECT_STATE_CONNECTED:
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
case MQTT_CONNECT_STATE_RECONNECTING:
|
||||
return QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT;
|
||||
case MQTT_CONNECT_STATE_RECONNECTED:
|
||||
return QCLOUD_RET_MQTT_RECONNECTED;
|
||||
case MQTT_CONNECT_STATE_DISCONNECT:
|
||||
return QCLOUD_ERR_MQTT_RECONNECT_TIMEOUT;
|
||||
}
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish MQTT message.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_name topic to publish
|
||||
* @param[in] params @see PublishParams
|
||||
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
int IOT_MQTT_Publish(void *client, const char *topic_name, const PublishParams *params)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(params, QCLOUD_ERR_INVAL);
|
||||
STRING_PTR_SANITY_CHECK(topic_name, QCLOUD_ERR_INVAL);
|
||||
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
|
||||
if (strlen(topic_name) > MAX_SIZE_OF_CLOUD_TOPIC) {
|
||||
return QCLOUD_ERR_MAX_TOPIC_LENGTH;
|
||||
}
|
||||
|
||||
if (QOS2 == params->qos) {
|
||||
Log_e("QoS2 is not supported currently");
|
||||
return QCLOUD_ERR_MQTT_QOS_NOT_SUPPORT;
|
||||
}
|
||||
|
||||
return module_mqtt_publish(mqtt_client, topic_name, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Subscribe MQTT topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter to subscribe
|
||||
* @param[in] params @see SubscribeParams
|
||||
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
int IOT_MQTT_Subscribe(void *client, const char *topic_filter, const SubscribeParams *params)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(params, QCLOUD_ERR_INVAL);
|
||||
STRING_PTR_SANITY_CHECK(topic_filter, QCLOUD_ERR_INVAL);
|
||||
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
|
||||
if (strlen(topic_filter) > MAX_SIZE_OF_CLOUD_TOPIC) {
|
||||
return QCLOUD_ERR_MAX_TOPIC_LENGTH;
|
||||
}
|
||||
|
||||
if (QOS2 == params->qos) {
|
||||
Log_e("QoS2 is not supported currently");
|
||||
return QCLOUD_ERR_MQTT_QOS_NOT_SUPPORT;
|
||||
}
|
||||
|
||||
return qcloud_iot_mqtt_subscribe(mqtt_client, topic_filter, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unsubscribe MQTT topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter to unsubscribe
|
||||
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
int IOT_MQTT_Unsubscribe(void *client, const char *topic_filter)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
STRING_PTR_SANITY_CHECK(topic_filter, QCLOUD_ERR_INVAL);
|
||||
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
|
||||
if (strlen(topic_filter) > MAX_SIZE_OF_CLOUD_TOPIC) {
|
||||
return QCLOUD_ERR_MAX_TOPIC_LENGTH;
|
||||
}
|
||||
|
||||
return qcloud_iot_mqtt_unsubscribe(mqtt_client, topic_filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if MQTT topic has been subscribed or not
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter to subscribe
|
||||
* @return true already subscribed
|
||||
* @return false not ready
|
||||
*/
|
||||
bool IOT_MQTT_IsSubReady(void *client, const char *topic_filter)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, false);
|
||||
STRING_PTR_SANITY_CHECK(topic_filter, false);
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
return qcloud_iot_mqtt_is_sub_ready(mqtt_client, topic_filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get user data in subscribe.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter to subscribe
|
||||
* @return NULL or user data
|
||||
*/
|
||||
void *IOT_MQTT_GetSubUsrData(void *client, const char *topic_filter)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, NULL);
|
||||
STRING_PTR_SANITY_CHECK(topic_filter, NULL);
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
return qcloud_iot_mqtt_get_subscribe_usr_data(mqtt_client, topic_filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Subscribe and wait sub ready.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter to subscribe
|
||||
* @param[in] params @see SubscribeParams
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int IOT_MQTT_SubscribeSync(void *client, const char *topic_filter, const SubscribeParams *params)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(params, QCLOUD_ERR_INVAL);
|
||||
STRING_PTR_SANITY_CHECK(topic_filter, QCLOUD_ERR_INVAL);
|
||||
return IOT_MQTT_Subscribe(client, topic_filter, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if MQTT is connected.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return true connected
|
||||
* @return false no connected
|
||||
*/
|
||||
bool IOT_MQTT_IsConnected(void *client)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
module_mqtt_get_connect_state(mqtt_client);
|
||||
return mqtt_client->connect_state != MQTT_CONNECT_STATE_DISCONNECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get device info using to connect mqtt server.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see DeviceInfo
|
||||
*/
|
||||
DeviceInfo *IOT_MQTT_GetDeviceInfo(void *client)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, NULL);
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
return mqtt_client->device_info;
|
||||
}
|
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2022 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file at_module_mqtt_publish.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2022-04-25
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2022-04-25 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "at_module.h"
|
||||
|
||||
/**
|
||||
* @brief Check if topic match
|
||||
*
|
||||
* @param[in] topic_filter topic name filter, wildcard is supported
|
||||
* @param[in] topic_name topic name, no wildcard
|
||||
* @param[in] topic_name_len length of topic name
|
||||
* @return 1 for matched, 0 for no matched
|
||||
*
|
||||
* @note assume topic filter and name is in correct format;
|
||||
* # can only be at end;
|
||||
* + and # can only be next to separator.
|
||||
*/
|
||||
static uint8_t _is_topic_matched(char *topic_filter, const char *topic_name, uint16_t topic_name_len)
|
||||
{
|
||||
char *curf = topic_filter;
|
||||
char *curn = (char *)topic_name;
|
||||
char *curn_end = curn + topic_name_len;
|
||||
|
||||
while (*curf && curn < curn_end) {
|
||||
if (*curn == '/' && *curf != '/')
|
||||
break;
|
||||
if (*curf != '+' && *curf != '#' && *curf != *curn)
|
||||
break;
|
||||
if (*curf == '+') { // skip until we meet the next separator, or end of string
|
||||
char *nextpos = curn + 1;
|
||||
while (nextpos < curn_end && *nextpos != '/') nextpos = ++curn + 1;
|
||||
} else if (*curf == '#')
|
||||
curn = curn_end - 1; // skip until end of string
|
||||
curf++;
|
||||
curn++;
|
||||
};
|
||||
|
||||
return (curn == curn_end) && (*curf == '\0');
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deliver message to subscribe message handler.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_name topic name, no wildcard
|
||||
* @param[in] payload publish packet payload
|
||||
* @param[in] payload_len payload length
|
||||
*/
|
||||
void qcloud_iot_deliver_message(QcloudIotClient *client, char *topic_name, char *payload, int payload_len)
|
||||
{
|
||||
int i;
|
||||
|
||||
MQTTMessage message = {
|
||||
.qos = QOS0, // no use
|
||||
.retain = 0,
|
||||
.dup = 0,
|
||||
.packet_id = 0,
|
||||
.topic_name = topic_name,
|
||||
.topic_len = strlen(topic_name),
|
||||
.payload_str = payload,
|
||||
.payload_len = payload_len,
|
||||
};
|
||||
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
|
||||
if (client->sub_handles[i].topic_filter &&
|
||||
_is_topic_matched(client->sub_handles[i].topic_filter, message.topic_name, message.topic_len)) {
|
||||
if (client->sub_handles[i].params.on_message_handler) {
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
// if found, then handle it, then return
|
||||
client->sub_handles[i].params.on_message_handler(client, &message,
|
||||
client->sub_handles[i].params.user_data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
|
||||
/* Message handler not found for topic */
|
||||
/* May be we do not care change FAILURE use SUCCESS*/
|
||||
Log_d("no matching any topic, call default handle function");
|
||||
|
||||
MQTTEventMsg msg;
|
||||
if (client->event_handle.h_fp) {
|
||||
msg.event_type = MQTT_EVENT_PUBLISH_RECEIVED;
|
||||
msg.msg = &message;
|
||||
client->event_handle.h_fp(client, client->event_handle.context, &msg);
|
||||
}
|
||||
}
|
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2022 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file at_module_mqtt_subscribe.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2022-04-25
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2022-04-25 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "at_module.h"
|
||||
|
||||
/**
|
||||
* @brief Free topic_filter and user_data
|
||||
*
|
||||
* @param[in] handler subtopic handle
|
||||
*/
|
||||
static void _clear_sub_handle(SubTopicHandle *handler)
|
||||
{
|
||||
if (handler->topic_filter) {
|
||||
HAL_Free(handler->topic_filter);
|
||||
handler->topic_filter = NULL;
|
||||
}
|
||||
|
||||
if (handler->params.user_data && handler->params.user_data_free) {
|
||||
handler->params.user_data_free(handler->params.user_data);
|
||||
handler->params.user_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove sub handle when unsubscribe.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] topic_filter topic to remove
|
||||
* @return true topic exist
|
||||
* @return false topic no exist
|
||||
*/
|
||||
static bool _remove_sub_handle_from_array(QcloudIotClient *client, const char *topic_filter)
|
||||
{
|
||||
int i;
|
||||
bool topic_exists = false;
|
||||
|
||||
// remove from message handler array
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
|
||||
if ((client->sub_handles[i].topic_filter && !strcmp(client->sub_handles[i].topic_filter, topic_filter)) ||
|
||||
strstr(topic_filter, "/#") || strstr(topic_filter, "/+")) {
|
||||
// notify this event to topic subscriber
|
||||
if (client->sub_handles[i].params.on_sub_event_handler) {
|
||||
client->sub_handles[i].params.on_sub_event_handler(client, MQTT_EVENT_UNSUBSCRIBE,
|
||||
client->sub_handles[i].params.user_data);
|
||||
}
|
||||
_clear_sub_handle(&client->sub_handles[i]);
|
||||
// we don't want to break here, if the same topic is registered*with 2 callbacks.Unlikely scenario
|
||||
topic_exists = true;
|
||||
}
|
||||
}
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
return topic_exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add sub handle when subscribe.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] sub_handle sub_handle to be add to array
|
||||
* @return true topic exist
|
||||
* @return false topic no exist
|
||||
*/
|
||||
static int _add_sub_handle_to_array(QcloudIotClient *client, const SubTopicHandle *sub_handle)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int i, i_free = -1;
|
||||
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
|
||||
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
|
||||
if (client->sub_handles[i].topic_filter) {
|
||||
if (!strcmp(client->sub_handles[i].topic_filter, sub_handle->topic_filter)) {
|
||||
i_free = i;
|
||||
// free the memory before
|
||||
_clear_sub_handle(&client->sub_handles[i]);
|
||||
Log_w("Identical topic found: %s", sub_handle->topic_filter);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
i_free = i_free == -1 ? i : i_free;
|
||||
}
|
||||
}
|
||||
|
||||
if (-1 == i_free) {
|
||||
Log_e("NO more @sub_handles space!");
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
|
||||
}
|
||||
|
||||
client->sub_handles[i_free] = *sub_handle;
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Serialize and send subscribe packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic to subscribe
|
||||
* @param[in] params subscribe params
|
||||
* @return >=0 for packet id, < 0 for failed @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_subscribe(QcloudIotClient *client, const char *topic_filter, const SubscribeParams *params)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc, qos = params->qos;
|
||||
char *topic_filter_stored;
|
||||
SubTopicHandle sub_handle;
|
||||
|
||||
// topic filter should be valid in the whole sub life
|
||||
topic_filter_stored = HAL_Malloc(strlen(topic_filter) + 1);
|
||||
if (!topic_filter_stored) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MALLOC);
|
||||
}
|
||||
strncpy(topic_filter_stored, topic_filter, strlen(topic_filter) + 1);
|
||||
|
||||
sub_handle.topic_filter = topic_filter_stored;
|
||||
sub_handle.params = *params;
|
||||
sub_handle.status = client->default_subscribe ? SUB_ACK_RECEIVED : SUB_ACK_NOT_RECEIVED;
|
||||
|
||||
// add sub handle first to process
|
||||
rc = _add_sub_handle_to_array(client, &sub_handle);
|
||||
if (rc) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (client->default_subscribe) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
rc = module_mqtt_subscribe(client, topic_filter, qos);
|
||||
if (!rc) {
|
||||
sub_handle.status = SUB_ACK_RECEIVED;
|
||||
}
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
exit:
|
||||
_remove_sub_handle_from_array(client, topic_filter_stored);
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Serialize and send unsubscribe packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic to unsubscribe
|
||||
* @return >=0 packet id, < 0 for failed @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_unsubscribe(QcloudIotClient *client, const char *topic_filter)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
SubTopicHandle sub_handle;
|
||||
memset(&sub_handle, 0, sizeof(SubTopicHandle));
|
||||
|
||||
// remove from sub handle
|
||||
if (!_remove_sub_handle_from_array(client, topic_filter)) {
|
||||
Log_w("subscription does not exists: %s", topic_filter);
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_UNSUB_FAIL);
|
||||
}
|
||||
|
||||
IOT_FUNC_EXIT_RC(module_mqtt_unsubscribe(client, topic_filter));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return if topic is sub ready.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter
|
||||
* @return true for ready
|
||||
* @return false for not ready
|
||||
*/
|
||||
bool qcloud_iot_mqtt_is_sub_ready(QcloudIotClient *client, const char *topic_filter)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int i = 0;
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
|
||||
if ((client->sub_handles[i].topic_filter && !strcmp(client->sub_handles[i].topic_filter, topic_filter)) ||
|
||||
strstr(topic_filter, "/#") || strstr(topic_filter, "/+")) {
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
return client->sub_handles[i].status == SUB_ACK_RECEIVED;
|
||||
}
|
||||
}
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
IOT_FUNC_EXIT_RC(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get usr data, usr should handle lock/unlock usrdata itself in callback and caller.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter
|
||||
* @return NULL or user data
|
||||
*/
|
||||
void *qcloud_iot_mqtt_get_subscribe_usr_data(QcloudIotClient *client, const char *topic_filter)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int i = 0;
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
|
||||
if ((client->sub_handles[i].topic_filter && !strcmp(client->sub_handles[i].topic_filter, topic_filter)) ||
|
||||
strstr(topic_filter, "/#") || strstr(topic_filter, "/+")) {
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
return client->sub_handles[i].params.user_data;
|
||||
}
|
||||
}
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
IOT_FUNC_EXIT_RC(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear sub handle array.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
void qcloud_iot_mqtt_sub_handle_array_clear(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int i;
|
||||
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
|
||||
/* notify this event to topic subscriber */
|
||||
if (client->sub_handles[i].topic_filter && client->sub_handles[i].params.on_sub_event_handler) {
|
||||
client->sub_handles[i].params.on_sub_event_handler(client, MQTT_EVENT_CLIENT_DESTROY,
|
||||
client->sub_handles[i].params.user_data);
|
||||
}
|
||||
_clear_sub_handle(&client->sub_handles[i]);
|
||||
}
|
||||
|
||||
IOT_FUNC_EXIT;
|
||||
}
|
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* @file at_module_ota.c
|
||||
* @author {hubert} ({hubertxxu@tencent.com})
|
||||
* @brief
|
||||
* @version 1.0
|
||||
* @date 2022-06-07
|
||||
*
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* Date Version Author Description
|
||||
* 2022-06-07 1.0 hubertxxu first commit
|
||||
* </table>
|
||||
*/
|
||||
#include "at_module.h"
|
||||
|
||||
/**
|
||||
* @brief OTA info handle.
|
||||
*
|
||||
*/
|
||||
static QcloudIotOtaFwInfo sg_iot_ota_info_handle;
|
||||
|
||||
/**
|
||||
* @brief Get ota info handle.
|
||||
*
|
||||
* @return @see QcloudIotOtaFwInfo
|
||||
*/
|
||||
QcloudIotOtaFwInfo *qcloud_iot_get_ota_info_handle(void)
|
||||
{
|
||||
return &sg_iot_ota_info_handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init ota && report mcu & at version.
|
||||
*
|
||||
* @param[in] version mcu version.
|
||||
* @return 0 for success
|
||||
*/
|
||||
int IOT_OTA_Init(const char *version)
|
||||
{
|
||||
sg_iot_ota_info_handle.get_fw_info_sem = HAL_SemaphoreCreate();
|
||||
if (!sg_iot_ota_info_handle.get_fw_info_sem) {
|
||||
Log_e("create sem fail.");
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
module_set_ota_urc();
|
||||
// TODO: get at module version and change cls state.
|
||||
return module_ota_set_fw_version(IOT_OTA_CLS_STATE_REPORT_MCU, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deinit ota.
|
||||
*
|
||||
*/
|
||||
void IOT_OTA_Deinit(void)
|
||||
{
|
||||
HAL_SemaphoreDestroy(sg_iot_ota_info_handle.get_fw_info_sem);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read fw info from at module.
|
||||
*
|
||||
* @param[out] version mcu fw version
|
||||
* @param[out] fw_size mcu fw size
|
||||
* @param[out] md5 mcu fw md5
|
||||
* @param[in] timeout_ms timeout
|
||||
* @return 0 for success
|
||||
*/
|
||||
int IOT_OTA_ReadFwInfo(char **version, uint32_t *fw_size, char **md5, uint32_t timeout_ms)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
rc = module_ota_get_fw_info();
|
||||
if (rc) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = HAL_SemaphoreWait(sg_iot_ota_info_handle.get_fw_info_sem, timeout_ms);
|
||||
if (rc) {
|
||||
*version = NULL;
|
||||
*fw_size = 0;
|
||||
*md5 = NULL;
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
*version = sg_iot_ota_info_handle.version;
|
||||
*fw_size = sg_iot_ota_info_handle.fw_size;
|
||||
*md5 = sg_iot_ota_info_handle.md5;
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read fw data from at module.
|
||||
*
|
||||
* @param[out] fw_data fw data
|
||||
* @param[out] fw_data_len fw data length
|
||||
* @param[in] timeout_ms timeout
|
||||
* @return 0 for success
|
||||
*/
|
||||
int IOT_OTA_ReadFWData(uint8_t *fw_data, uint32_t *fw_data_len, uint32_t timeout_ms)
|
||||
{
|
||||
return module_ota_read_fw_data(fw_data, fw_data_len, timeout_ms);
|
||||
}
|
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2022 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file at_module_urc.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2022-04-24
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2022-04-24 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "at_module.h"
|
||||
|
||||
/**
|
||||
* @brief URC for iot hub
|
||||
* 1. urc for at command
|
||||
* +TCMQTTRCVPUB
|
||||
* +TCMQTTDISCON
|
||||
* +TCMQTTRECONNECTING
|
||||
* +TCMQTTRECONNECTED
|
||||
* +TCOTASTATUS
|
||||
* 2. urc cmd response
|
||||
* +TCMQTTSTATE
|
||||
* 3. urc for ota
|
||||
* +TCREADFWDATA
|
||||
* +TCFWINFO
|
||||
* +TCOTASTATUS
|
||||
*
|
||||
*/
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// mqtt urc handle
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Discconect by mqtt server.
|
||||
*
|
||||
* @param[in] data data recv from at module
|
||||
* @param[in] data_len data len
|
||||
*/
|
||||
static void _urc_mqtt_disconnect_handle(const char *data, size_t data_len)
|
||||
{
|
||||
// +TCMQTTSTATE:-3
|
||||
int error_code = 0;
|
||||
|
||||
QcloudIotClient *mqtt_client = qcloud_iot_get_mqtt_client();
|
||||
if (!mqtt_client) {
|
||||
return;
|
||||
}
|
||||
mqtt_client->connect_state = MQTT_CONNECT_STATE_DISCONNECT;
|
||||
|
||||
sscanf(data, "+TCMQTTDISCON,%d", &error_code);
|
||||
Log_e("disconnect %d", error_code);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reconnecting.
|
||||
*
|
||||
* @param[in] data data recv from at module
|
||||
* @param[in] data_len data len
|
||||
*/
|
||||
static void _urc_mqtt_reconnect_handle(const char *data, size_t data_len)
|
||||
{
|
||||
// +TCMQTTRECONNECTING
|
||||
QcloudIotClient *mqtt_client = qcloud_iot_get_mqtt_client();
|
||||
if (!mqtt_client) {
|
||||
return;
|
||||
}
|
||||
mqtt_client->connect_state = MQTT_CONNECT_STATE_RECONNECTING;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reconnected.
|
||||
*
|
||||
* @param[in] data data recv from at module
|
||||
* @param[in] data_len data len
|
||||
*/
|
||||
static void _urc_mqtt_reconnected_handle(const char *data, size_t data_len)
|
||||
{
|
||||
// +TCMQTTRECONNECTED
|
||||
QcloudIotClient *mqtt_client = qcloud_iot_get_mqtt_client();
|
||||
if (!mqtt_client) {
|
||||
return;
|
||||
}
|
||||
mqtt_client->connect_state = MQTT_CONNECT_STATE_RECONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connect state.
|
||||
*
|
||||
* @param[in] data data recv from at module
|
||||
* @param[in] data_len data len
|
||||
*/
|
||||
static void _urc_mqtt_state_handle(const char *data, size_t data_len)
|
||||
{
|
||||
// +TCMQTTSTATE:1
|
||||
int connect_state = 1;
|
||||
|
||||
QcloudIotClient *mqtt_client = qcloud_iot_get_mqtt_client();
|
||||
if (!mqtt_client) {
|
||||
return;
|
||||
}
|
||||
sscanf(data, "+TCMQTTSTATE:%d", &connect_state);
|
||||
Log_d("mqtt state %d", connect_state);
|
||||
if (connect_state) {
|
||||
mqtt_client->connect_state = MQTT_CONNECT_STATE_CONNECTED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recv publish packet.
|
||||
*
|
||||
* @param[in] data data recv from at module
|
||||
* @param[in] data_len data len
|
||||
*/
|
||||
static void _urc_mqtt_publish_handle(const char *data, size_t data_len)
|
||||
{
|
||||
// +TCMQTTRCVPUB:"$thing/down/property/Q5NNWVC2Z8/test1",81,"{"method":"report_reply","clientToken":"property-69","code":0,"status":"success"}"
|
||||
|
||||
QcloudIotClient *mqtt_client = qcloud_iot_get_mqtt_client();
|
||||
if (!mqtt_client) {
|
||||
return;
|
||||
}
|
||||
|
||||
char *head, *topic_name, *payload;
|
||||
char *src = (char *)data;
|
||||
|
||||
src[data_len - 1] = '\0';
|
||||
|
||||
head = strtok((char *)data, "\"");
|
||||
if (!head) {
|
||||
return;
|
||||
}
|
||||
|
||||
topic_name = strtok(NULL, "\"");
|
||||
if (!topic_name) {
|
||||
return;
|
||||
}
|
||||
|
||||
strtok(NULL, "\"");
|
||||
payload = strtok(NULL, "");
|
||||
if (!payload) {
|
||||
return;
|
||||
}
|
||||
qcloud_iot_deliver_message(mqtt_client, topic_name, payload, strlen(payload));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ota urc handle
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
static void _delete_char(char *str, char ch)
|
||||
{
|
||||
char *p;
|
||||
for (p = str; *p != '\0'; p++)
|
||||
if (*p != ch)
|
||||
*str++ = *p;
|
||||
*str = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recv fw info.
|
||||
*
|
||||
* @param[in] data data recv from at module
|
||||
* @param[in] data_len data len
|
||||
*/
|
||||
static void _urc_ota_fw_info_handle(const char *data, size_t data_len)
|
||||
{
|
||||
// +TCFWINFO:"1.2.0",17300,"a2aa3c261ebfc1322edafd37edb6b183",262144
|
||||
QcloudIotOtaFwInfo *ota_info = qcloud_iot_get_ota_info_handle();
|
||||
_delete_char((char *)data, '"');
|
||||
sscanf((char *)data, "+TCFWINFO:%[^,],%u,%[^,],%u", ota_info->version, &(ota_info->fw_size), ota_info->md5,
|
||||
&ota_info->fw_max_size);
|
||||
HAL_SemaphorePost(ota_info->get_fw_info_sem);
|
||||
}
|
||||
|
||||
static void _urc_ota_state_handle(const char *data, size_t data_len)
|
||||
{
|
||||
// Todo
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// API
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Set mqtt urc to at module.
|
||||
*
|
||||
* @return 0 means successful.
|
||||
*/
|
||||
int module_set_mqtt_urc(void)
|
||||
{
|
||||
int rc;
|
||||
rc = HAL_Module_SetUrc("+TCMQTTRCVPUB", _urc_mqtt_publish_handle);
|
||||
rc |= HAL_Module_SetUrc("+TCMQTTDISCON", _urc_mqtt_disconnect_handle);
|
||||
rc |= HAL_Module_SetUrc("+TCMQTTRECONNECTING", _urc_mqtt_reconnect_handle);
|
||||
rc |= HAL_Module_SetUrc("+TCMQTTRECONNECTED", _urc_mqtt_reconnected_handle);
|
||||
rc |= HAL_Module_SetUrc("+TCMQTTSTATE", _urc_mqtt_state_handle);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set ota urc to at module.
|
||||
*
|
||||
* @return 0 means successful.
|
||||
*/
|
||||
int module_set_ota_urc(void)
|
||||
{
|
||||
int rc;
|
||||
rc = HAL_Module_SetUrc("+TCFWINFO", _urc_ota_fw_info_handle);
|
||||
rc |= HAL_Module_SetUrc("+TCOTASTATUS", _urc_ota_state_handle);
|
||||
return rc;
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
file(GLOB src_cos ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
|
||||
set(src_services ${src_services} ${src_cos} PARENT_SCOPE)
|
||||
|
||||
if( ${CONFIG_COMPILE_SAMPLE} STREQUAL "ON")
|
||||
file(GLOB src_cos_download_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/cos_download_sample.c)
|
||||
add_executable(cos_download_sample ${src_cos_download_sample})
|
||||
target_link_libraries(cos_download_sample ${libsdk})
|
||||
endif()
|
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file cos_download_sample.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-10-27
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-10-27 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "qcloud_iot_cos.h"
|
||||
|
||||
#include "utils_log.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Main
|
||||
// ----------------------------------------------------------------------------
|
||||
static int sg_main_exit = 0;
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void _main_exit(int sig)
|
||||
{
|
||||
Log_e("demo exit by signal:%d\n", sig);
|
||||
sg_main_exit = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
#ifdef __linux__
|
||||
signal(SIGINT, _main_exit);
|
||||
#endif
|
||||
|
||||
int rc = 0;
|
||||
|
||||
// init log level
|
||||
LogHandleFunc func = DEFAULT_LOG_HANDLE_FUNCS;
|
||||
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
|
||||
|
||||
IotCosDownloadParams connect_params = {
|
||||
.url = "http://localhost", // your cos url
|
||||
.file_size = 0, // your cos file size
|
||||
.offset = 0,
|
||||
.is_fragmentation = false,
|
||||
.is_https_enabled = false,
|
||||
};
|
||||
|
||||
uint8_t buf[1024];
|
||||
|
||||
void *handle = IOT_COS_DownloadInit(&connect_params);
|
||||
if (!handle) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
while (!IOT_COS_DownloadIsFinished(handle)) {
|
||||
rc = IOT_COS_DownloadFetch(handle, buf, sizeof(buf), 5000);
|
||||
if (rc < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
IOT_COS_DownloadDeinit(handle);
|
||||
exit:
|
||||
utils_log_deinit();
|
||||
return rc;
|
||||
}
|
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file cos_download.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-10-25
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-10-25 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "qcloud_iot_cos.h"
|
||||
|
||||
/**
|
||||
* @brief COS request download header.
|
||||
*
|
||||
*/
|
||||
#define HTTP_COS_DOWNLOAD_REQUEST_HEADER_LEN 256
|
||||
|
||||
/**
|
||||
* @brief Download handle.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
IotCosDownloadParams params;
|
||||
void *http_client;
|
||||
IotHTTPRequestParams http_request;
|
||||
int download_size;
|
||||
int is_first_run;
|
||||
} HTTPCosDownloadHandle;
|
||||
|
||||
/**
|
||||
* @brief Connect cos http server.
|
||||
*
|
||||
* @param[in,out] handle pointer to cos download handle, @see HTTPCosDownloadHandle
|
||||
* @return 0 for success. others @see IotReturnCode
|
||||
*/
|
||||
static int _cos_download_connect(HTTPCosDownloadHandle *handle)
|
||||
{
|
||||
IotHTTPConnectParams connect_params = {
|
||||
.url = handle->params.url,
|
||||
.port = handle->params.is_https_enabled ? "443" : "80",
|
||||
.ca_crt = NULL, // TODO: support cert
|
||||
|
||||
};
|
||||
return IOT_HTTP_Connect(handle->http_client, &connect_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct cos download request header.
|
||||
*
|
||||
* @param[out] header pointer to request header
|
||||
* @param[in] is_fragmentation http fragmentation
|
||||
* @param[in] begin_byte download begin byte
|
||||
* @param[in] end_byte download end byte
|
||||
* @return > 0 for header len, others fail
|
||||
*/
|
||||
static int _cos_download_request_header_construct(char *header, int is_fragmentation, int begin_byte, int end_byte)
|
||||
{
|
||||
int len = HAL_Snprintf(header, HTTP_COS_DOWNLOAD_REQUEST_HEADER_LEN,
|
||||
"Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
|
||||
"Accept-Encoding:gzip, deflate\r\nRange:bytes=%d-%d\r\n",
|
||||
begin_byte, end_byte);
|
||||
if (len <= 0) {
|
||||
return QCLOUD_ERR_BUF_TOO_SHORT;
|
||||
}
|
||||
|
||||
if (is_fragmentation) {
|
||||
const char *keep_alive = "Connection:keep-alive\r\n";
|
||||
int keep_alive_len = strlen(keep_alive);
|
||||
if (HTTP_COS_DOWNLOAD_REQUEST_HEADER_LEN - len - 1 <= keep_alive_len) {
|
||||
return QCLOUD_ERR_BUF_TOO_SHORT;
|
||||
}
|
||||
strncpy(header + len, keep_alive, keep_alive_len);
|
||||
header[len + keep_alive_len] = '\0';
|
||||
len += keep_alive_len;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Request cos download.
|
||||
*
|
||||
* @param[in,out] handle pointer to cos download handle, @see HTTPCosDownloadHandle
|
||||
* @param[in] max_len max_len for buffer to download in fragmentation
|
||||
* @return 0 for success. others @see IotReturnCode
|
||||
*/
|
||||
static int _cos_download_request(HTTPCosDownloadHandle *handle, int max_len)
|
||||
{
|
||||
int rc, begin_byte, end_byte = 0;
|
||||
|
||||
begin_byte = handle->download_size;
|
||||
#define min_http(x, y) (((x) < (y)) ? (x) : (y))
|
||||
end_byte = handle->params.is_fragmentation
|
||||
? begin_byte + min_http(max_len, handle->params.file_size - handle->download_size) - 1
|
||||
: handle->params.file_size - 1;
|
||||
#undef min_http
|
||||
|
||||
rc = _cos_download_request_header_construct(handle->http_request.header, handle->params.is_fragmentation,
|
||||
begin_byte, end_byte);
|
||||
if (rc <= 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
handle->http_request.url = handle->params.url;
|
||||
handle->http_request.method = IOT_HTTP_METHOD_GET;
|
||||
handle->http_request.content_length = 0;
|
||||
handle->http_request.content = handle->http_request.content_type = NULL;
|
||||
return IOT_HTTP_Request(handle->http_client, &handle->http_request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HTTP recv data.
|
||||
*
|
||||
* @param[in,out] handle pointer to cos download handle, @see HTTPCosDownloadHandle
|
||||
* @param[out] buf buf to store received data
|
||||
* @param[in] buf_len buf_len
|
||||
* @param[in] timeout_ms read socket timeout
|
||||
* @return >= 0 for recv data len. others @see IotReturnCode
|
||||
*/
|
||||
static int _cos_download_recv_data(HTTPCosDownloadHandle *handle, uint8_t *buf, int buf_len, uint32_t timeout_ms)
|
||||
{
|
||||
int rc = IOT_HTTP_Recv(handle->http_client, buf, buf_len, timeout_ms);
|
||||
if (rc > 0) {
|
||||
handle->download_size += rc;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**************************************************************************************
|
||||
* API
|
||||
**************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Init cos download handle.
|
||||
*
|
||||
* @param[in] params @see IotCosDownloadParams
|
||||
* @return pointer to cos download handle
|
||||
*/
|
||||
void *IOT_COS_DownloadInit(IotCosDownloadParams *params)
|
||||
{
|
||||
POINTER_SANITY_CHECK(params, NULL);
|
||||
|
||||
HTTPCosDownloadHandle *handle = HAL_Malloc(sizeof(HTTPCosDownloadHandle));
|
||||
if (!handle) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
handle->http_client = IOT_HTTP_Init();
|
||||
if (!handle->http_client) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
handle->http_request.header = HAL_Malloc(HTTP_COS_DOWNLOAD_REQUEST_HEADER_LEN);
|
||||
if (!handle->http_request.header) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
handle->params = *params;
|
||||
handle->download_size = params->offset;
|
||||
|
||||
if (_cos_download_connect(handle)) {
|
||||
goto exit;
|
||||
}
|
||||
handle->is_first_run = 1;
|
||||
return handle;
|
||||
exit:
|
||||
if (handle) {
|
||||
HAL_Free(handle->http_request.header);
|
||||
IOT_HTTP_Deinit(handle->http_client);
|
||||
HAL_Free(handle);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Fetch data from cos.
|
||||
*
|
||||
* @param[in,out] handle pointer to cos download handle, @see HTTPCosDownloadHandle
|
||||
* @param[out] buf buffer to store data
|
||||
* @param[in] buf_len buffer length
|
||||
* @param timeout_ms timeout for fetching
|
||||
* @return >= 0 for recv data len. others @see IotReturnCode
|
||||
*/
|
||||
int IOT_COS_DownloadFetch(void *handle, uint8_t *buf, uint32_t buf_len, uint32_t timeout_ms)
|
||||
{
|
||||
POINTER_SANITY_CHECK(handle, QCLOUD_ERR_INVAL);
|
||||
int rc = 0;
|
||||
|
||||
HTTPCosDownloadHandle *download_handle = (HTTPCosDownloadHandle *)handle;
|
||||
|
||||
// download finish
|
||||
if (IOT_COS_DownloadIsFinished(handle)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (download_handle->is_first_run) {
|
||||
rc = _cos_download_request(download_handle, buf_len);
|
||||
if (rc) {
|
||||
Log_e("cos request failed %d", rc);
|
||||
return rc;
|
||||
}
|
||||
download_handle->is_first_run = 0;
|
||||
return _cos_download_recv_data(download_handle, buf, buf_len, timeout_ms);
|
||||
}
|
||||
|
||||
if (download_handle->params.is_fragmentation && IOT_HTTP_IsRecvFinished(download_handle->http_client)) {
|
||||
rc = _cos_download_request(download_handle, buf_len);
|
||||
if (rc) {
|
||||
Log_e("cos request failed %d", rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
return _cos_download_recv_data(download_handle, buf, buf_len, timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Is download finished.
|
||||
*
|
||||
* @param[in,out] handle pointer to cos download handle, @see HTTPCosDownloadHandle
|
||||
* @return true for finished
|
||||
*/
|
||||
int IOT_COS_DownloadIsFinished(void *handle)
|
||||
{
|
||||
POINTER_SANITY_CHECK(handle, QCLOUD_ERR_INVAL);
|
||||
HTTPCosDownloadHandle *download_handle = (HTTPCosDownloadHandle *)handle;
|
||||
return download_handle->download_size == download_handle->params.file_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deinit cos download.
|
||||
*
|
||||
* @param[in,out] handle pointer to cos download handle, @see HTTPCosDownloadHandle
|
||||
*/
|
||||
void IOT_COS_DownloadDeinit(void *handle)
|
||||
{
|
||||
POINTER_SANITY_CHECK_RTN(handle);
|
||||
HTTPCosDownloadHandle *download_handle = (HTTPCosDownloadHandle *)handle;
|
||||
IOT_HTTP_Disconnect(download_handle->http_client);
|
||||
IOT_HTTP_Deinit(download_handle->http_client);
|
||||
HAL_Free(download_handle->http_request.header);
|
||||
HAL_Free(download_handle);
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
file(GLOB src_dynreg ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
|
||||
set(src_services ${src_services} ${src_dynreg} PARENT_SCOPE)
|
||||
|
||||
if( ${CONFIG_COMPILE_SAMPLE} STREQUAL "ON")
|
||||
file(GLOB src_dynreg_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/dynreg_sample.c)
|
||||
add_executable(dynreg_sample ${src_dynreg_sample})
|
||||
target_link_libraries(dynreg_sample ${libsdk})
|
||||
endif()
|
||||
|
||||
if( ${CONFIG_IOT_TEST} STREQUAL "ON")
|
||||
file(GLOB src_unit_test ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc)
|
||||
set(src_test ${src_test} ${src_unit_test} PARENT_SCOPE)
|
||||
endif()
|
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file cos_download_sample.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-10-27
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-10-27 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "qcloud_iot_common.h"
|
||||
#include "utils_log.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Main
|
||||
// ----------------------------------------------------------------------------
|
||||
static int sg_main_exit = 0;
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void _main_exit(int sig)
|
||||
{
|
||||
Log_e("demo exit by signal:%d\n", sig);
|
||||
sg_main_exit = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
#ifdef __linux__
|
||||
signal(SIGINT, _main_exit);
|
||||
#endif
|
||||
|
||||
int rc = 0;
|
||||
|
||||
// init log level
|
||||
LogHandleFunc func = DEFAULT_LOG_HANDLE_FUNCS;
|
||||
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
|
||||
|
||||
DeviceInfo device_info;
|
||||
// 1. get device info
|
||||
rc = HAL_GetDevInfo((void *)&device_info);
|
||||
if (rc) {
|
||||
Log_e("get device info failed: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
// 2. do dynreg
|
||||
rc = IOT_DynReg_Device(&device_info);
|
||||
if (rc) {
|
||||
Log_e("dynreg error");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// 3. get device secret just save it. then you can do mqtt
|
||||
HAL_SetDevInfo(&device_info);
|
||||
Log_i("device secret : %s", device_info.device_secret);
|
||||
|
||||
exit:
|
||||
utils_log_deinit();
|
||||
return rc;
|
||||
}
|
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* @file dynreg.c
|
||||
* @author {hubert} ({hubertxxu@tencent.com})
|
||||
* @brief
|
||||
* @version 1.0
|
||||
* @date 2022-01-26
|
||||
*
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* Date Version Author Description
|
||||
* 2022-01-26 1.0 hubertxxu first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "qcloud_iot_common.h"
|
||||
#include "utils_json.h"
|
||||
#include "utils_base64.h"
|
||||
#include "mbedtls/aes.h"
|
||||
|
||||
#define DYN_RESPONSE_BUFF_LEN (256)
|
||||
#define DECODE_BUFF_LEN (256)
|
||||
#define UTILS_AES_BLOCK_LEN (16)
|
||||
|
||||
/**
|
||||
* @brief aes cbc handle
|
||||
*
|
||||
* @return int 0 is success other is error.
|
||||
*/
|
||||
static int _aes_cbc(uint8_t *input_data, uint32_t input_data_len, uint8_t *output_data, uint32_t output_data_len,
|
||||
DeviceInfo *device_info)
|
||||
{
|
||||
int i;
|
||||
uint8_t iv[16] = {0};
|
||||
char key[UTILS_AES_BLOCK_LEN + 1] = {0};
|
||||
for (i = 0; i < 16; i++) {
|
||||
iv[i] = '0';
|
||||
}
|
||||
strncpy(key, device_info->product_secret, UTILS_AES_BLOCK_LEN);
|
||||
mbedtls_aes_context aes_ctx;
|
||||
mbedtls_aes_init(&aes_ctx);
|
||||
mbedtls_aes_setkey_dec(&aes_ctx, (const unsigned char *)key, 128);
|
||||
if (mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, input_data_len, iv, (unsigned char *)input_data,
|
||||
(unsigned char *)output_data)) {
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
mbedtls_aes_free(&aes_ctx);
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief parse device register response buffer
|
||||
*
|
||||
* @param response_buf
|
||||
* @param response_len
|
||||
* @return int 0 success other error
|
||||
*/
|
||||
static int _parse_response_result(char *response_buf, int response_len, DeviceInfo *device_info)
|
||||
{
|
||||
int rc = 0;
|
||||
UtilsJsonValue value;
|
||||
char base64_decode_buf[DECODE_BUFF_LEN] = {0};
|
||||
size_t olen = 0;
|
||||
|
||||
// 1. find payload
|
||||
rc = utils_json_value_get("Response.Payload", strlen("Response.Payload"), response_buf, response_len, &value);
|
||||
if (rc) {
|
||||
Log_e("can not find payload. json : %s", response_buf);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// 2. base64 decode
|
||||
rc = utils_base64decode((uint8_t *)base64_decode_buf, DECODE_BUFF_LEN, &olen, (const uint8_t *)value.value,
|
||||
value.value_len);
|
||||
if (rc) {
|
||||
Log_e("base64 decode error.");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// 3. aes cbc
|
||||
olen = olen + (16 - olen % 16);
|
||||
rc = _aes_cbc((uint8_t *)base64_decode_buf, (uint32_t)olen, (uint8_t *)base64_decode_buf, DECODE_BUFF_LEN,
|
||||
device_info);
|
||||
if (rc) {
|
||||
Log_e("aes cbc error. ");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// 4. find psk
|
||||
uint32_t encryption_type = 0;
|
||||
rc = utils_json_get_uint32("encryptionType", strlen("encryptionType"), base64_decode_buf, DECODE_BUFF_LEN,
|
||||
&encryption_type);
|
||||
if (rc) {
|
||||
Log_e("parse encryptionType error. json : %s", base64_decode_buf);
|
||||
goto exit;
|
||||
}
|
||||
rc = utils_json_value_get("psk", strlen("psk"), base64_decode_buf, DECODE_BUFF_LEN, &value);
|
||||
if (rc) {
|
||||
Log_e("parse psk error. json : %s", base64_decode_buf);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// 5. copy psk to device info
|
||||
strncpy(device_info->device_secret, value.value, value.value_len);
|
||||
|
||||
exit:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief dynreg device, get device secret or device cert file and private key file from iot platform
|
||||
*
|
||||
* @param[in] params @see DeviceInfo
|
||||
* @return 0 is success other is failed
|
||||
*/
|
||||
int IOT_DynReg_Device(DeviceInfo *device_info)
|
||||
{
|
||||
int rc = 0;
|
||||
POINTER_SANITY_CHECK(device_info, QCLOUD_ERR_INVAL);
|
||||
|
||||
if (strlen(device_info->product_secret) < UTILS_AES_BLOCK_LEN) {
|
||||
Log_e("product key illegal");
|
||||
rc = QCLOUD_ERR_FAILURE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
char dynreg_buf[DYN_RESPONSE_BUFF_LEN] = {0};
|
||||
|
||||
/* constructor dynreg http body */
|
||||
int request_body_len =
|
||||
HAL_Snprintf(dynreg_buf, DYN_RESPONSE_BUFF_LEN, "{\"ProductId\":\"%s\",\"DeviceName\":\"%s\"}",
|
||||
device_info->product_id, device_info->device_name);
|
||||
|
||||
HttpSignedParams params = {
|
||||
.host = DYN_REG_SERVER_URL,
|
||||
.uri = DYN_REG_URI_PATH,
|
||||
.need_recv = true,
|
||||
.recv_timeout_ms = 2000,
|
||||
.secretkey = device_info->product_secret,
|
||||
};
|
||||
rc = IOT_HTTP_SignedRequest(¶ms, dynreg_buf, request_body_len, (uint8_t *)dynreg_buf, DYN_RESPONSE_BUFF_LEN);
|
||||
if (rc < 0) {
|
||||
goto exit;
|
||||
}
|
||||
rc = _parse_response_result(dynreg_buf, rc, device_info);
|
||||
exit:
|
||||
return rc;
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
ColumnLimit: 120
|
||||
DerivePointerAlignment: true
|
||||
PointerAlignment: Left
|
||||
SortIncludes: true
|
||||
IncludeBlocks: Preserve
|
||||
IndentPPDirectives: AfterHash
|
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @file test_dynreg.cc
|
||||
* @author {hubert} ({hubertxxu@tencent.com})
|
||||
* @brief
|
||||
* @version 1.0
|
||||
* @date 2022-01-26
|
||||
*
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* Date Version Author Description
|
||||
* 2022-01-26 1.0 hubertxxu first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "mqtt_client_test.h"
|
||||
#include "qcloud_iot_common.h"
|
||||
|
||||
namespace mqtt_client_unittest {
|
||||
|
||||
TEST_F(MqttClientTest, dynreg_test) {
|
||||
DeviceInfo device_info;
|
||||
ASSERT_EQ(HAL_GetDevInfo(&device_info), 0);
|
||||
strncpy(device_info.device_name, "dynreg_test", 12);
|
||||
EXPECT_EQ(IOT_DynReg_Device(&device_info), 0);
|
||||
}
|
||||
|
||||
} // namespace mqtt_client_unittest
|
@@ -0,0 +1,15 @@
|
||||
file(GLOB src_gateway ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
|
||||
set(src_services ${src_services} ${src_gateway} PARENT_SCOPE)
|
||||
|
||||
if( ${CONFIG_COMPILE_SAMPLE} STREQUAL "ON")
|
||||
file(GLOB src_gateway_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/*.c)
|
||||
add_executable(gateway_sample ${src_gateway_sample})
|
||||
target_link_libraries(gateway_sample ${libsdk})
|
||||
endif()
|
||||
|
||||
if( ${CONFIG_IOT_TEST} STREQUAL "ON")
|
||||
file(GLOB src_unit_test ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc)
|
||||
set(inc_gateway_test ${CMAKE_CURRENT_SOURCE_DIR}/test)
|
||||
set(src_test ${src_test} ${src_unit_test} PARENT_SCOPE)
|
||||
set(inc_test ${inc_test} ${inc_gateway_test} PARENT_SCOPE)
|
||||
endif()
|
@@ -0,0 +1,312 @@
|
||||
/**
|
||||
* @file gateway_sample.c
|
||||
* @author {hubert} ({hubertxxu@tencent.com})
|
||||
* @brief
|
||||
* @version 1.0
|
||||
* @date 2022-05-25
|
||||
*
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* Date Version Author Description
|
||||
* 2022-05-25 1.0 hubertxxu first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "qcloud_iot_common.h"
|
||||
#include "qcloud_iot_explorer.h"
|
||||
|
||||
#include "utils_log.h"
|
||||
|
||||
#include "gateway_subdev_handle.h"
|
||||
|
||||
/**
|
||||
* @brief MQTT event callback, @see MQTTEventHandleFun
|
||||
*
|
||||
* @param[in] client pointer to mqtt client
|
||||
* @param[in] handle_context context
|
||||
* @param[in] msg msg
|
||||
*/
|
||||
static void _mqtt_event_handler(void *client, void *handle_context, MQTTEventMsg *msg)
|
||||
{
|
||||
MQTTMessage *mqtt_message = (MQTTMessage *)msg->msg;
|
||||
uintptr_t packet_id = (uintptr_t)msg->msg;
|
||||
|
||||
switch (msg->event_type) {
|
||||
case MQTT_EVENT_UNDEF:
|
||||
Log_i("undefined event occur.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_DISCONNECT:
|
||||
Log_i("MQTT disconnect.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_RECONNECT:
|
||||
Log_i("MQTT reconnect.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_RECEIVED:
|
||||
Log_i("topic message arrived but without any related handle: topic=%.*s, topic_msg=%.*s",
|
||||
mqtt_message->topic_len, STRING_PTR_PRINT_SANITY_CHECK(mqtt_message->topic_name),
|
||||
mqtt_message->payload_len, STRING_PTR_PRINT_SANITY_CHECK((char *)mqtt_message->payload));
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBE_SUCCESS:
|
||||
Log_i("subscribe success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBE_TIMEOUT:
|
||||
Log_i("subscribe wait ack timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBE_NACK:
|
||||
Log_i("subscribe nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_SUCCESS:
|
||||
Log_i("unsubscribe success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_TIMEOUT:
|
||||
Log_i("unsubscribe timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_NACK:
|
||||
Log_i("unsubscribe nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_SUCCESS:
|
||||
Log_i("publish success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_TIMEOUT:
|
||||
Log_i("publish timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_NACK:
|
||||
Log_i("publish nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log_i("Should NOT arrive here.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Setup MQTT construct parameters.
|
||||
*
|
||||
* @param[in,out] initParams @see MQTTInitParams
|
||||
* @param[in] device_info @see DeviceInfo
|
||||
*/
|
||||
static void _setup_connect_init_params(MQTTInitParams *init_params, DeviceInfo *device_info)
|
||||
{
|
||||
init_params->device_info = device_info;
|
||||
init_params->event_handle.h_fp = _mqtt_event_handler;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Data template callback
|
||||
// ----------------------------------------------------------------------------
|
||||
#if 0
|
||||
static void _method_control_callback(UtilsJsonValue client_token, UtilsJsonValue params, void *usr_data)
|
||||
{
|
||||
char buf[256];
|
||||
Log_i("recv msg[%.*s]: params=%.*s", client_token.value_len, client_token.value, params.value_len, params.value);
|
||||
IOT_DataTemplate_PropertyControlReply(usr_data, buf, sizeof(buf), 0, client_token);
|
||||
}
|
||||
|
||||
static void _method_report_reply_callback(UtilsJsonValue client_token, int code, void *usr_data)
|
||||
{
|
||||
Log_i("recv msg[%.*s]: code=%d", client_token.value_len, client_token.value, code);
|
||||
}
|
||||
|
||||
static void _method_get_status_reply_callback(UtilsJsonValue client_token, int code, UtilsJsonValue reported,
|
||||
UtilsJsonValue control, void *usr_data)
|
||||
{
|
||||
char buf[256];
|
||||
Log_i("recv msg[%.*s]: code=%d|reported=%.*s|control=%.*s", client_token.value_len, client_token.value, code,
|
||||
reported.value_len, STRING_PTR_PRINT_SANITY_CHECK(reported.value), control.value_len,
|
||||
STRING_PTR_PRINT_SANITY_CHECK(control.value));
|
||||
IOT_DataTemplate_PropertyClearControl(usr_data, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
static void _method_report_info_reply_callback(UtilsJsonValue client_token, int code, void *usr_data)
|
||||
{
|
||||
Log_i("recv msg[%.*s]: code=%d", client_token.value_len, client_token.value, code);
|
||||
}
|
||||
|
||||
static void _method_clear_control_reply_callback(UtilsJsonValue client_token, int code, void *usr_data)
|
||||
{
|
||||
Log_i("recv msg[%.*s]: code=%d", client_token.value_len, client_token.value, code);
|
||||
}
|
||||
|
||||
static void _method_event_reply_callback(UtilsJsonValue client_token, int code, void *usr_data)
|
||||
{
|
||||
Log_i("recv msg[%.*s]: code=%d", client_token.value_len, client_token.value, code);
|
||||
}
|
||||
|
||||
static void _method_action_callback(UtilsJsonValue client_token, UtilsJsonValue action_id, UtilsJsonValue params,
|
||||
void *usr_data)
|
||||
{
|
||||
char buf[256];
|
||||
|
||||
Log_i("recv msg[%.*s]: action_id=%.*s|params=%.*s", client_token.value_len, client_token.value, action_id.value_len,
|
||||
action_id.value, params.value_len, params.value);
|
||||
IotDataTemplateActionReply reply = {
|
||||
.code = 0,
|
||||
.client_token = client_token,
|
||||
.response = "{\"err_code\":0}",
|
||||
};
|
||||
IOT_DataTemplate_ActionReply(usr_data, buf, sizeof(buf), reply);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Data template upstream
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
static void _cycle_report(void *client)
|
||||
{
|
||||
char buf[256];
|
||||
const char *report_property = "{\"power_switch\":0}";
|
||||
|
||||
IotDataTemplateEventData event_data = {
|
||||
.event_id = "status_report",
|
||||
.type = IOT_DATA_TEMPLATE_EVENT_TYPE_INFO,
|
||||
.params = "{\"status\":0,\"message\":\"ok\"}",
|
||||
|
||||
};
|
||||
|
||||
static QcloudIotTimer sg_cycle_report_timer;
|
||||
if (IOT_Timer_Expired(&sg_cycle_report_timer)) {
|
||||
IOT_DataTemplate_PropertyReport(client, buf, sizeof(buf), report_property);
|
||||
IOT_DataTemplate_EventPost(client, buf, sizeof(buf), event_data);
|
||||
IOT_Timer_Countdown(&sg_cycle_report_timer, 500);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// ----------------------------------------------------------------------------
|
||||
// Main
|
||||
// ----------------------------------------------------------------------------
|
||||
static int sg_main_exit = 0;
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void _main_exit(int sig)
|
||||
{
|
||||
Log_e("demo exit by signal:%d\n", sig);
|
||||
sg_main_exit = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
#ifdef __linux__
|
||||
signal(SIGINT, _main_exit);
|
||||
#endif
|
||||
|
||||
int rc;
|
||||
|
||||
char buf[1024];
|
||||
|
||||
// init log level
|
||||
LogHandleFunc func = DEFAULT_LOG_HANDLE_FUNCS;
|
||||
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
|
||||
|
||||
DeviceInfo device_info;
|
||||
|
||||
rc = HAL_GetDevInfo((void *)&device_info);
|
||||
if (rc) {
|
||||
Log_e("get device info failed: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// init connection
|
||||
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
|
||||
_setup_connect_init_params(&init_params, &device_info);
|
||||
|
||||
// create MQTT client and connect with server
|
||||
void *client = IOT_MQTT_Construct(&init_params);
|
||||
if (client) {
|
||||
Log_i("Cloud Device Construct Success");
|
||||
} else {
|
||||
Log_e("MQTT Construct failed!");
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
#if 0
|
||||
// subscribe normal topics and wait result
|
||||
IotDataTemplateCallback callback = {
|
||||
.property_callback = {.method_control_callback = _method_control_callback,
|
||||
.method_clear_control_reply_callback = _method_clear_control_reply_callback,
|
||||
.method_get_status_reply_callback = _method_get_status_reply_callback,
|
||||
.method_report_info_reply_callback = _method_report_info_reply_callback,
|
||||
.method_report_reply_callback = _method_report_reply_callback},
|
||||
.event_callback = {.method_event_reply_callback = _method_event_reply_callback},
|
||||
.action_callback = {.method_action_callback = _method_action_callback},
|
||||
};
|
||||
|
||||
rc = IOT_DataTemplate_Init(client, callback, client);
|
||||
if (rc) {
|
||||
Log_e("Client Subscribe Topic Failed: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
const char *report_info =
|
||||
"{\"module_hardinfo\":\"ESP8266\",\"module_softinfo\":\"V1.0\", \"fw_ver\":\"4.0.0\", "
|
||||
"\"imei\":\"11-22-33-44\",\"lat\":\"22.546015\",\"lon\":\"113.941125\",\"mac\":\"11:22:33:44:55:66\",\"device_"
|
||||
"label\":{\"append_"
|
||||
"info\":\"your self defined info\"}}";
|
||||
|
||||
IOT_DataTemplate_PropertyReportInfo(client, buf, sizeof(buf), report_info);
|
||||
IOT_DataTemplate_PropertyGetStatus(client, buf, sizeof(buf));
|
||||
#endif
|
||||
// gateway init
|
||||
|
||||
iot_gateway_init(client, 32);
|
||||
|
||||
do {
|
||||
rc = IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT);
|
||||
switch (rc) {
|
||||
case QCLOUD_RET_SUCCESS:
|
||||
break;
|
||||
case QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT:
|
||||
continue;
|
||||
case QCLOUD_RET_MQTT_RECONNECTED:
|
||||
IOT_DataTemplate_PropertyGetStatus(client, buf, sizeof(buf));
|
||||
break;
|
||||
default:
|
||||
Log_e("Exit loop caused of errCode:%d", rc);
|
||||
goto exit;
|
||||
}
|
||||
// _cycle_report(client);
|
||||
} while (!sg_main_exit);
|
||||
|
||||
exit:
|
||||
iot_gateway_deinit(client);
|
||||
// IOT_DataTemplate_Deinit(client);
|
||||
rc |= IOT_MQTT_Destroy(&client);
|
||||
utils_log_deinit();
|
||||
return rc;
|
||||
}
|
@@ -0,0 +1,514 @@
|
||||
/**
|
||||
* @file gateway_subdev_handle.c
|
||||
* @author {hubert} ({hubertxxu@tencent.com})
|
||||
* @brief
|
||||
* @version 1.0
|
||||
* @date 2022-05-25
|
||||
*
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* Date Version Author Description
|
||||
* 2022-05-25 1.0 hubertxxu first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "qcloud_iot_gateway.h"
|
||||
#include "utils_list.h"
|
||||
|
||||
typedef struct {
|
||||
void *subdev_list;
|
||||
void *mqtt_client;
|
||||
} GatewaySubDevHandle;
|
||||
|
||||
typedef enum {
|
||||
GATEWAY_SUBDEV_OFFLINE = 0, // 离线
|
||||
GATEWAY_SUBDEV_OFFLINEING = 1, // 发起离线
|
||||
GATEWAY_SUBDEV_ONLINING = 2, // 发起上线
|
||||
GATEWAY_SUBDEV_ONLINE = 3, // 在线
|
||||
} GatewaySubDevOnlineStatus;
|
||||
|
||||
typedef enum {
|
||||
GATEWAY_SUBDEV_UNBIND = 0,
|
||||
GATEWAY_SUBDEV_UNBINDING = 2,
|
||||
GATEWAY_SUBDEV_BINDING = 3,
|
||||
GATEWAY_SUBDEV_BINDED = 4,
|
||||
|
||||
} GatewaySubDevBindStatus;
|
||||
|
||||
typedef struct {
|
||||
DeviceInfo dev_info;
|
||||
int online_status;
|
||||
int bind_status;
|
||||
bool is_sub_property;
|
||||
void *mqtt_client;
|
||||
} GatewaySubDev;
|
||||
|
||||
typedef struct {
|
||||
GatewaySubDev *pair_sub;
|
||||
const char *product_id;
|
||||
const char *device_name;
|
||||
} SubDevPair;
|
||||
|
||||
typedef enum {
|
||||
DEV_ARRAY_HANDLE_DESCRIBE = 0,
|
||||
DEV_ARRAY_HANDEL_ONLINE = 1,
|
||||
DEV_ARRAY_HANDLE_OFFLINE = 2,
|
||||
DEV_ARRAY_HANDLE_BIND = 3,
|
||||
DEV_ARRAY_HANDLE_UNBIND = 4,
|
||||
DEV_ARRAY_HANDLE_CHANGE_BIND = 5,
|
||||
} DevArrayHandleType;
|
||||
|
||||
typedef struct {
|
||||
GatewaySubDevHandle *gw_subdev_handle;
|
||||
DevArrayHandleType type;
|
||||
} DevArrayHandle;
|
||||
|
||||
static GatewaySubDevHandle *sg_gw_subdev_handle = NULL;
|
||||
|
||||
GatewaySubDev *iot_gateway_find_sub(GatewaySubDevHandle *sub_handle, const char *sub_pid, const char *sub_dname);
|
||||
|
||||
static UtilsListResult _find_subdev_by_name_str(void *list, void *node, void *val, void *usr_data);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// sub device control && report
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
int ext_subdev_control(const char *product_id, const char *device_name, int power_switch, int brightness)
|
||||
{
|
||||
Log_d("control device [%s/%s] power_switch : %d, brightness : %d", product_id, device_name, power_switch,
|
||||
brightness);
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
int ext_subdev_report(const char *product_id, const char *device_name, int power_switch, int brightness)
|
||||
{
|
||||
static int token_num = 0;
|
||||
GatewaySubDevHandle *gw_subdev_handle = sg_gw_subdev_handle;
|
||||
POINTER_SANITY_CHECK(gw_subdev_handle, QCLOUD_ERR_FAILURE);
|
||||
POINTER_SANITY_CHECK(product_id, QCLOUD_ERR_FAILURE);
|
||||
POINTER_SANITY_CHECK(device_name, QCLOUD_ERR_FAILURE);
|
||||
|
||||
GatewaySubDev *sub = iot_gateway_find_sub(gw_subdev_handle, product_id, device_name);
|
||||
if (!sub || sub->bind_status != GATEWAY_SUBDEV_BINDED || sub->online_status != GATEWAY_SUBDEV_ONLINE) {
|
||||
if (sub) {
|
||||
Log_e("subdev [%s/%s] illegal, sub->bind_status : %d, sub->online_status : %d", sub->dev_info.product_id,
|
||||
sub->dev_info.device_name, sub->bind_status, sub->online_status);
|
||||
} else {
|
||||
Log_e("subdev [%s/%s] illegal.", product_id, device_name);
|
||||
}
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
char property_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
char report_buf[256];
|
||||
HAL_Snprintf(property_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$thing/up/property/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(sub->dev_info.product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(sub->dev_info.device_name));
|
||||
int len = HAL_Snprintf(report_buf, sizeof(report_buf),
|
||||
"{\"method\":\"report\",\"clientToken\":\"property-%s-%u\",\"params\":{\"power_switch\":%d, "
|
||||
"\"brightness\":%d}}",
|
||||
device_name, token_num++, power_switch, brightness);
|
||||
|
||||
PublishParams pub_params = DEFAULT_PUB_PARAMS;
|
||||
pub_params.qos = 0;
|
||||
pub_params.payload = (void *)report_buf;
|
||||
pub_params.payload_len = len;
|
||||
return IOT_MQTT_Publish(gw_subdev_handle->mqtt_client, property_topic, &pub_params);
|
||||
}
|
||||
|
||||
static void _data_template_property_message_handler(void *client, const MQTTMessage *message, void *usr_data)
|
||||
{
|
||||
int rc = 0;
|
||||
GatewaySubDev *sub = (GatewaySubDev *)usr_data;
|
||||
char buf[256];
|
||||
|
||||
Log_d("receive subdev [%s/%s] property message:%.*s", sub->dev_info.product_id, sub->dev_info.device_name,
|
||||
message->payload_len, message->payload_str);
|
||||
|
||||
UtilsJsonValue client_token, method;
|
||||
rc = utils_json_value_get("clientToken", strlen("clientToken"), (const char *)message->payload_str,
|
||||
message->payload_len, &client_token);
|
||||
if (rc) {
|
||||
return;
|
||||
}
|
||||
rc = utils_json_value_get("method", strlen("method"), message->payload_str, message->payload_len, &method);
|
||||
if (rc) {
|
||||
return;
|
||||
}
|
||||
|
||||
// only parse control
|
||||
if (strncmp(method.value, "control", method.value_len)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// parse params
|
||||
int power_switch = -1, brightness = -1;
|
||||
utils_json_get_int32("params.power_switch", strlen("params.power_switch"), message->payload_str,
|
||||
message->payload_len, &power_switch);
|
||||
utils_json_get_int32("params.brightness", strlen("params.brightness"), message->payload_str, message->payload_len,
|
||||
&brightness);
|
||||
|
||||
rc = ext_subdev_control(sub->dev_info.product_id, sub->dev_info.device_name, power_switch, brightness);
|
||||
|
||||
char property_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
HAL_Snprintf(property_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$thing/up/property/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(sub->dev_info.product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(sub->dev_info.device_name));
|
||||
int len = HAL_Snprintf(buf, sizeof(buf), "{\"method\":\"control_reply\",\"clientToken\":\"%.*s\",\"code\":%d}",
|
||||
client_token.value_len, client_token.value, rc);
|
||||
|
||||
PublishParams pub_params = DEFAULT_PUB_PARAMS;
|
||||
pub_params.qos = 0;
|
||||
pub_params.payload = (void *)buf;
|
||||
pub_params.payload_len = len;
|
||||
IOT_MQTT_Publish(client, property_topic, &pub_params);
|
||||
ext_subdev_report(sub->dev_info.product_id, sub->dev_info.device_name, power_switch, brightness);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// subdev handle
|
||||
// -----------------------------------------------------
|
||||
|
||||
static UtilsListResult _find_subdev_by_name_str(void *list, void *node, void *val, void *usr_data)
|
||||
{
|
||||
GatewaySubDev *sub = (GatewaySubDev *)val;
|
||||
SubDevPair *march_sub = (SubDevPair *)usr_data;
|
||||
|
||||
if (!strncmp(sub->dev_info.product_id, march_sub->product_id, MAX_SIZE_OF_PRODUCT_ID) &&
|
||||
!strncmp(sub->dev_info.device_name, march_sub->device_name, MAX_SIZE_OF_DEVICE_NAME)) {
|
||||
march_sub->pair_sub = sub;
|
||||
return LIST_TRAVERSE_BREAK;
|
||||
}
|
||||
return LIST_TRAVERSE_CONTINUE;
|
||||
}
|
||||
|
||||
GatewaySubDev *iot_gateway_find_sub(GatewaySubDevHandle *sub_handle, const char *sub_pid, const char *sub_dname)
|
||||
{
|
||||
POINTER_SANITY_CHECK(sub_handle, NULL);
|
||||
POINTER_SANITY_CHECK(sub_pid, NULL);
|
||||
POINTER_SANITY_CHECK(sub_dname, NULL);
|
||||
|
||||
SubDevPair sub_info = {NULL, sub_pid, sub_dname};
|
||||
|
||||
utils_list_process(sub_handle->subdev_list, LIST_HEAD, _find_subdev_by_name_str, &sub_info);
|
||||
|
||||
return sub_info.pair_sub;
|
||||
}
|
||||
|
||||
static UtilsListResult _remove_subdev_by_name_str(void *list, void *node, void *val, void *usr_data)
|
||||
{
|
||||
GatewaySubDev *sub = (GatewaySubDev *)val;
|
||||
SubDevPair *march_sub = (SubDevPair *)usr_data;
|
||||
|
||||
if (!strncmp(sub->dev_info.product_id, march_sub->product_id, MAX_SIZE_OF_PRODUCT_ID) &&
|
||||
!strncmp(sub->dev_info.device_name, march_sub->device_name, MAX_SIZE_OF_DEVICE_NAME)) {
|
||||
march_sub->pair_sub = sub;
|
||||
Log_w("remove sub[%s/%s]", sub->dev_info.product_id, sub->dev_info.device_name);
|
||||
// 1. unsub property topic
|
||||
char property_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
HAL_Snprintf(property_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$thing/down/property/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(sub->dev_info.product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(sub->dev_info.device_name));
|
||||
|
||||
IOT_MQTT_Unsubscribe(sub->mqtt_client, property_topic);
|
||||
// 2. offline?
|
||||
|
||||
// 3. remove
|
||||
utils_list_remove(list, node);
|
||||
return LIST_TRAVERSE_BREAK;
|
||||
}
|
||||
return LIST_TRAVERSE_CONTINUE;
|
||||
}
|
||||
|
||||
void iot_gateway_remove_subdev_from_sublist(GatewaySubDevHandle *sub_handle, DeviceInfo *sub_dev_info)
|
||||
{
|
||||
SubDevPair pair_sub = {
|
||||
.pair_sub = NULL, .device_name = sub_dev_info->device_name, .product_id = sub_dev_info->product_id};
|
||||
utils_list_process(sub_handle->subdev_list, LIST_HEAD, _remove_subdev_by_name_str, &pair_sub);
|
||||
}
|
||||
|
||||
int iot_gateway_add_sub_to_sublist(GatewaySubDevHandle *sub_handle, DeviceInfo *sub_dev_info, int bind_status)
|
||||
{
|
||||
GatewaySubDev *new_sub = iot_gateway_find_sub(sub_handle, sub_dev_info->product_id, sub_dev_info->device_name);
|
||||
if (new_sub) {
|
||||
new_sub->bind_status = bind_status;
|
||||
Log_w("subdev[%s/%s] already exists.", sub_dev_info->product_id, sub_dev_info->device_name);
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
new_sub = HAL_Malloc(sizeof(GatewaySubDev));
|
||||
POINTER_SANITY_CHECK(new_sub, QCLOUD_ERR_MALLOC);
|
||||
memset(new_sub, 0, sizeof(GatewaySubDev));
|
||||
memcpy(&(new_sub->dev_info), sub_dev_info, sizeof(DeviceInfo));
|
||||
void *node = utils_list_push(sub_handle->subdev_list, new_sub);
|
||||
if (!node) {
|
||||
HAL_Free(new_sub);
|
||||
Log_e("add subdev[%s/%s] error.", sub_dev_info->product_id, sub_dev_info->device_name);
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
new_sub->bind_status = bind_status;
|
||||
new_sub->mqtt_client = sub_handle->mqtt_client;
|
||||
Log_i("add subdev[%s/%s] in subdev list[total subdev : %d].", sub_dev_info->product_id, sub_dev_info->device_name,
|
||||
utils_list_len_get(sub_handle->subdev_list));
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
int iot_gateway_bind_subdev(DeviceInfo *sub_dev_info)
|
||||
{
|
||||
GatewaySubDevHandle *gw_subdev_handle = sg_gw_subdev_handle;
|
||||
POINTER_SANITY_CHECK(sub_dev_info, QCLOUD_ERR_FAILURE);
|
||||
POINTER_SANITY_CHECK(gw_subdev_handle, QCLOUD_ERR_FAILURE);
|
||||
|
||||
// 1. check if in subdev list
|
||||
GatewaySubDev *sub = iot_gateway_find_sub(gw_subdev_handle, sub_dev_info->product_id, sub_dev_info->device_name);
|
||||
if (sub && sub->bind_status == GATEWAY_SUBDEV_BINDED) {
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
// 2. add to subdev list
|
||||
iot_gateway_add_sub_to_sublist(gw_subdev_handle, sub_dev_info, GATEWAY_SUBDEV_BINDING);
|
||||
|
||||
// 3. sync cloud
|
||||
char bind_buf[256];
|
||||
const DeviceInfo *bind_dev_list[] = {sub_dev_info};
|
||||
return IOT_Gateway_BindUnbind(gw_subdev_handle->mqtt_client, bind_buf, 256, bind_dev_list, 1, true);
|
||||
}
|
||||
|
||||
int iot_gateway_subdev_on_or_offline(GatewaySubDevHandle *sub_handle, DeviceInfo *sub_dev_info, bool is_online)
|
||||
{
|
||||
char sub_dev_online_buffer[512];
|
||||
const DeviceInfo *dev_online_list[] = {sub_dev_info};
|
||||
// check device in dev list && binded
|
||||
GatewaySubDev *sub_dev = iot_gateway_find_sub(sub_handle, sub_dev_info->product_id, sub_dev_info->device_name);
|
||||
|
||||
if (sub_dev && sub_dev->bind_status == GATEWAY_SUBDEV_BINDED && sub_dev->online_status != GATEWAY_SUBDEV_ONLINE) {
|
||||
sub_dev->online_status = GATEWAY_SUBDEV_ONLINING;
|
||||
return IOT_Gateway_SubOnOffLine(sub_handle->mqtt_client, sub_dev_online_buffer, 512, dev_online_list, 1,
|
||||
is_online);
|
||||
}
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
int iot_gateway_subdev_subscribe_property(GatewaySubDevHandle *sub_handle, DeviceInfo *sub_dev_info)
|
||||
{
|
||||
POINTER_SANITY_CHECK(sub_handle, QCLOUD_ERR_FAILURE);
|
||||
POINTER_SANITY_CHECK(sub_dev_info, QCLOUD_ERR_FAILURE);
|
||||
int rc;
|
||||
// find subdev && check binded && online
|
||||
GatewaySubDev *sub = iot_gateway_find_sub(sub_handle, sub_dev_info->product_id, sub_dev_info->device_name);
|
||||
POINTER_SANITY_CHECK(sub, QCLOUD_ERR_FAILURE);
|
||||
if (sub->bind_status != GATEWAY_SUBDEV_BINDED || sub->online_status != GATEWAY_SUBDEV_ONLINE ||
|
||||
sub->is_sub_property == true) {
|
||||
Log_e("subdev [%s/%s] illegal. bind status : %d online status : %d is_sub_property : %s",
|
||||
sub_dev_info->product_id, sub_dev_info->device_name, sub->bind_status, sub->online_status,
|
||||
sub->is_sub_property ? "true" : "false");
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
char property_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
HAL_Snprintf(property_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$thing/down/property/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(sub->dev_info.product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(sub->dev_info.device_name));
|
||||
// subscribe normal topics and wait result
|
||||
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
|
||||
sub_params.on_message_handler = _data_template_property_message_handler;
|
||||
sub_params.qos = QOS1;
|
||||
sub_params.user_data = sub;
|
||||
sub_params.user_data_free = NULL;
|
||||
|
||||
rc = IOT_MQTT_SubscribeSync(sub_handle->mqtt_client, property_topic, &sub_params);
|
||||
if (rc > 0) {
|
||||
sub->is_sub_property = true;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int qcloud_gateway_subdev_unsubscribe_property(GatewaySubDevHandle *sub_handle, DeviceInfo *sub_dev_info)
|
||||
{
|
||||
POINTER_SANITY_CHECK(sub_handle, QCLOUD_ERR_FAILURE);
|
||||
POINTER_SANITY_CHECK(sub_dev_info, QCLOUD_ERR_FAILURE);
|
||||
|
||||
char property_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
HAL_Snprintf(property_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$thing/down/property/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(sub_dev_info->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(sub_dev_info->device_name));
|
||||
return IOT_MQTT_Unsubscribe(sub_handle->mqtt_client, property_topic);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// gateway callback function
|
||||
// -----------------------------------------------------
|
||||
|
||||
UtilsJsonArrayIterResult _parse_subdev_array_cb(const char *subdev, int subdev_len, void *usr_data)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
UtilsJsonValue device_name;
|
||||
UtilsJsonValue product_id;
|
||||
utils_json_value_get("device_name", strlen("device_name"), subdev, subdev_len, &device_name);
|
||||
utils_json_value_get("product_id", strlen("product_id"), subdev, subdev_len, &product_id);
|
||||
|
||||
DeviceInfo dev_info;
|
||||
memset(&dev_info, 0, sizeof(DeviceInfo));
|
||||
strncpy(dev_info.product_id, product_id.value, product_id.value_len);
|
||||
strncpy(dev_info.device_name, device_name.value, device_name.value_len);
|
||||
DevArrayHandle *dev_array_handle = (DevArrayHandle *)usr_data;
|
||||
|
||||
switch (dev_array_handle->type) {
|
||||
case DEV_ARRAY_HANDLE_CHANGE_BIND:
|
||||
case DEV_ARRAY_HANDLE_DESCRIBE:
|
||||
// subdev has be bind in cloud. add this to subdev list
|
||||
rc = iot_gateway_add_sub_to_sublist(dev_array_handle->gw_subdev_handle, &dev_info, GATEWAY_SUBDEV_BINDED);
|
||||
if (!rc) {
|
||||
iot_gateway_subdev_on_or_offline(dev_array_handle->gw_subdev_handle, &dev_info, true);
|
||||
}
|
||||
break;
|
||||
case DEV_ARRAY_HANDEL_ONLINE: {
|
||||
int result = -1;
|
||||
GatewaySubDev *sub_dev =
|
||||
iot_gateway_find_sub(dev_array_handle->gw_subdev_handle, dev_info.product_id, dev_info.device_name);
|
||||
utils_json_get_int32("result", strlen("result"), subdev, subdev_len, &result);
|
||||
if (sub_dev && 0 == result) {
|
||||
sub_dev->online_status = GATEWAY_SUBDEV_ONLINE;
|
||||
iot_gateway_subdev_subscribe_property(dev_array_handle->gw_subdev_handle, &dev_info);
|
||||
} else if (sub_dev) {
|
||||
sub_dev->online_status = result;
|
||||
}
|
||||
} break;
|
||||
case DEV_ARRAY_HANDLE_BIND: {
|
||||
int result = -1;
|
||||
GatewaySubDev *sub_dev =
|
||||
iot_gateway_find_sub(dev_array_handle->gw_subdev_handle, dev_info.product_id, dev_info.device_name);
|
||||
if (!sub_dev) {
|
||||
break;
|
||||
}
|
||||
|
||||
utils_json_get_int32("result", strlen("result"), subdev, subdev_len, &result);
|
||||
if (IOT_GATEWAY_RET_SUCCESS == result || IOT_GATEWAY_ERR_BIND_REPEAT == result) {
|
||||
sub_dev->bind_status = GATEWAY_SUBDEV_BINDED;
|
||||
iot_gateway_subdev_on_or_offline(dev_array_handle->gw_subdev_handle, &dev_info, true);
|
||||
} else {
|
||||
sub_dev->bind_status = result;
|
||||
}
|
||||
} break;
|
||||
case DEV_ARRAY_HANDLE_UNBIND: {
|
||||
iot_gateway_remove_subdev_from_sublist(dev_array_handle->gw_subdev_handle, &dev_info);
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return UTILS_JSON_ARRAY_ITER_CONTINUE;
|
||||
}
|
||||
|
||||
static void _gateway_bind_unbind_reply_callback(UtilsJsonValue sub_devices, bool is_bind, void *usr_data)
|
||||
{
|
||||
GatewaySubDevHandle *gw_subdev_handle = (GatewaySubDevHandle *)usr_data;
|
||||
Log_d("bind status : %d %.*s", is_bind, sub_devices.value_len, sub_devices.value);
|
||||
DevArrayHandle dev_array_handle = {.gw_subdev_handle = gw_subdev_handle,
|
||||
.type = is_bind ? DEV_ARRAY_HANDLE_BIND : DEV_ARRAY_HANDLE_UNBIND};
|
||||
utils_json_array_parse(sub_devices.value, sub_devices.value_len, _parse_subdev_array_cb, &dev_array_handle);
|
||||
}
|
||||
|
||||
static void _gateway_unbind_all_callback(void *usr_data)
|
||||
{
|
||||
// GatewaySubDevHandle *gw_subdev_handle = (GatewaySubDevHandle *)usr_data;
|
||||
Log_d("unbind all.");
|
||||
}
|
||||
|
||||
static void _gateway_online_offline_reply_callback(UtilsJsonValue sub_devices, bool is_online, void *usr_data)
|
||||
{
|
||||
GatewaySubDevHandle *gw_subdev_handle = (GatewaySubDevHandle *)usr_data;
|
||||
Log_d("online status : %d %.*s", is_online, sub_devices.value_len, sub_devices.value);
|
||||
DevArrayHandle dev_array_handle = {.gw_subdev_handle = gw_subdev_handle,
|
||||
.type = is_online ? DEV_ARRAY_HANDEL_ONLINE : DEV_ARRAY_HANDLE_OFFLINE};
|
||||
utils_json_array_parse(sub_devices.value, sub_devices.value_len, _parse_subdev_array_cb, &dev_array_handle);
|
||||
}
|
||||
|
||||
static void _gateway_search_device_callback(bool is_on, void *usr_data)
|
||||
{
|
||||
// GatewaySubDevHandle *gw_subdev_handle = (GatewaySubDevHandle *)usr_data;
|
||||
Log_d("search : %d", is_on);
|
||||
}
|
||||
|
||||
static void _gateway_describe_subdevices_reply_callback(UtilsJsonValue sub_devices, void *usr_data)
|
||||
{
|
||||
GatewaySubDevHandle *gw_subdev_handle = (GatewaySubDevHandle *)usr_data;
|
||||
DevArrayHandle dev_array_handle = {.gw_subdev_handle = gw_subdev_handle, .type = DEV_ARRAY_HANDLE_DESCRIBE};
|
||||
Log_d("describe %.*s", sub_devices.value_len, sub_devices.value);
|
||||
utils_json_array_parse(sub_devices.value, sub_devices.value_len, _parse_subdev_array_cb, &dev_array_handle);
|
||||
}
|
||||
|
||||
static void _gateway_change_subdevices_status_callback(UtilsJsonValue sub_devices, bool is_bind, void *usr_data)
|
||||
{
|
||||
GatewaySubDevHandle *gw_subdev_handle = (GatewaySubDevHandle *)usr_data;
|
||||
Log_d("change sub devices is bind %d %.*s", is_bind, sub_devices.value_len, sub_devices.value);
|
||||
DevArrayHandle dev_array_handle = {.gw_subdev_handle = gw_subdev_handle,
|
||||
.type = is_bind ? DEV_ARRAY_HANDLE_CHANGE_BIND : DEV_ARRAY_HANDLE_UNBIND};
|
||||
utils_json_array_parse(sub_devices.value, sub_devices.value_len, _parse_subdev_array_cb, &dev_array_handle);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// export api
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
int iot_gateway_init(void *client, int sub_dev_max)
|
||||
{
|
||||
int rc = 0;
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_FAILURE);
|
||||
// 1. init gateway subdev handle
|
||||
GatewaySubDevHandle *gw_subdev_handle = HAL_Malloc(sizeof(GatewaySubDevHandle));
|
||||
POINTER_SANITY_CHECK(gw_subdev_handle, QCLOUD_ERR_MALLOC);
|
||||
UtilsListFunc func = DEFAULT_LIST_FUNCS;
|
||||
gw_subdev_handle->subdev_list = utils_list_create(func, sub_dev_max);
|
||||
if (!gw_subdev_handle->subdev_list) {
|
||||
goto _exit;
|
||||
}
|
||||
gw_subdev_handle->mqtt_client = client;
|
||||
sg_gw_subdev_handle = gw_subdev_handle;
|
||||
// 2. sub gateway topic
|
||||
IotGatewayMessageCallback callback = {
|
||||
.bind_unbind_reply_callback = _gateway_bind_unbind_reply_callback,
|
||||
.change_subdevices_status_callback = _gateway_change_subdevices_status_callback,
|
||||
.describe_subdevices_reply_callback = _gateway_describe_subdevices_reply_callback,
|
||||
.online_offline_reply_callback = _gateway_online_offline_reply_callback,
|
||||
.search_device_callback = _gateway_search_device_callback,
|
||||
.unbind_all_callback = _gateway_unbind_all_callback,
|
||||
};
|
||||
rc = IOT_Gateway_Init(client, callback, gw_subdev_handle);
|
||||
if (rc < 0) {
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
// 3. sync subdev list from cloud
|
||||
return IOT_Gateway_Describe(client);
|
||||
|
||||
_exit:
|
||||
utils_list_destroy(gw_subdev_handle->subdev_list);
|
||||
HAL_Free(gw_subdev_handle);
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
int iot_gateway_deinit(void *client)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_FAILURE);
|
||||
GatewaySubDevHandle *gw_subdev_handle = IOT_Gateway_GetUsrData(client);
|
||||
IOT_Gateway_Deinit(client);
|
||||
POINTER_SANITY_CHECK(gw_subdev_handle, QCLOUD_ERR_FAILURE);
|
||||
utils_list_destroy(gw_subdev_handle->subdev_list);
|
||||
HAL_Free(gw_subdev_handle);
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @file gateway_subdev_handle.h
|
||||
* @author {hubert} ({hubertxxu@tencent.com})
|
||||
* @brief
|
||||
* @version 1.0
|
||||
* @date 2022-05-25
|
||||
*
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* Date Version Author Description
|
||||
* 2022-05-25 1.0 hubertxxu first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#ifndef IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_GATEWAY_SAMPLE_GATEWAY_SUBDEV_HANDLE_H_
|
||||
#define IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_GATEWAY_SAMPLE_GATEWAY_SUBDEV_HANDLE_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int iot_gateway_init(void *client, int sub_dev_max);
|
||||
|
||||
int iot_gateway_deinit(void *client);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_GATEWAY_SAMPLE_GATEWAY_SUBDEV_HANDLE_H_
|
@@ -0,0 +1,470 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2022 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file gateway_mqtt.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2022-05-10
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2022-05-10 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "qcloud_iot_gateway.h"
|
||||
|
||||
#include "utils_hmac.h"
|
||||
#include "utils_base64.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// downstream message
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Context of gateway callback and user data.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
IotGatewayMessageCallback callback;
|
||||
void *usr_data;
|
||||
} GatewayContext;
|
||||
|
||||
/**
|
||||
* @brief Gateway message type.
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
TYPE_BIND = 0,
|
||||
TYPE_UNBIND,
|
||||
TYPE_UNBIND_ALL,
|
||||
TYPE_ONLINE,
|
||||
TYPE_OFFLINE,
|
||||
TYPE_SEARCH_DEVICES,
|
||||
TYPE_DESCRIBE_SUB_DEVICES,
|
||||
TYPE_CHANGE,
|
||||
} GatewayMessageType;
|
||||
|
||||
/**
|
||||
* @brief Get message type.
|
||||
*
|
||||
* @param[in] json_buf
|
||||
* @param[in] buf_len
|
||||
* @return < 0 for fail, others @see GatewayMessageType
|
||||
*/
|
||||
static int _parse_gateway_message_type(const char *json_buf, int buf_len)
|
||||
{
|
||||
UtilsJsonValue type;
|
||||
// parse type
|
||||
int rc = utils_json_value_get("type", sizeof("type") - 1, json_buf, buf_len, &type);
|
||||
if (rc) {
|
||||
Log_e("parse gateway msg type error");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Order @see GatewayMessageType
|
||||
*
|
||||
*/
|
||||
const char *gateway_type_str[] = {
|
||||
[TYPE_BIND] = "bind",
|
||||
[TYPE_UNBIND] = "unbind",
|
||||
[TYPE_UNBIND_ALL] = "unbind_all",
|
||||
[TYPE_ONLINE] = "online",
|
||||
[TYPE_OFFLINE] = "offline",
|
||||
[TYPE_SEARCH_DEVICES] = "search_devices",
|
||||
[TYPE_DESCRIBE_SUB_DEVICES] = "describe_sub_devices",
|
||||
[TYPE_CHANGE] = "change",
|
||||
};
|
||||
|
||||
for (int i = 0; i < sizeof(gateway_type_str) / sizeof(const char *); i++) {
|
||||
if (!strncmp(type.value, gateway_type_str[i], type.value_len)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
static void _callback_gateway(GatewayMessageType msg_type, IotGatewayMessageCallback *callback,
|
||||
UtilsJsonValue sub_devices, int status, void *usr_data)
|
||||
{
|
||||
switch (msg_type) {
|
||||
case TYPE_BIND:
|
||||
case TYPE_UNBIND:
|
||||
if (callback->bind_unbind_reply_callback) {
|
||||
callback->bind_unbind_reply_callback(sub_devices, msg_type == TYPE_BIND, usr_data);
|
||||
}
|
||||
break;
|
||||
case TYPE_UNBIND_ALL:
|
||||
if (callback->unbind_all_callback) {
|
||||
callback->unbind_all_callback(usr_data);
|
||||
}
|
||||
break;
|
||||
case TYPE_ONLINE:
|
||||
case TYPE_OFFLINE:
|
||||
if (callback->online_offline_reply_callback) {
|
||||
callback->online_offline_reply_callback(sub_devices, msg_type == TYPE_ONLINE, usr_data);
|
||||
}
|
||||
break;
|
||||
case TYPE_SEARCH_DEVICES:
|
||||
if (callback->search_device_callback) {
|
||||
callback->search_device_callback(status, usr_data);
|
||||
}
|
||||
break;
|
||||
case TYPE_DESCRIBE_SUB_DEVICES:
|
||||
if (callback->describe_subdevices_reply_callback) {
|
||||
callback->describe_subdevices_reply_callback(sub_devices, usr_data);
|
||||
}
|
||||
break;
|
||||
case TYPE_CHANGE:
|
||||
// status : 0:unbind 1:bind
|
||||
if (callback->change_subdevices_status_callback) {
|
||||
callback->change_subdevices_status_callback(sub_devices, status, usr_data);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log_e("unsupported now");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gateway downstream messsage handle.
|
||||
*
|
||||
* @param[in] client pointer to mqtt client
|
||||
* @param[in] message @see MQTTMessage
|
||||
* @param[in] user_data
|
||||
*/
|
||||
static void _gateway_mqtt_message_handler(void *client, const MQTTMessage *message, void *user_data)
|
||||
{
|
||||
int rc = 0;
|
||||
Log_d("receive gateway message %.*s", message->payload_len, message->payload_str);
|
||||
|
||||
int msg_type = _parse_gateway_message_type(message->payload_str, message->payload_len);
|
||||
if (msg_type < 0) {
|
||||
return;
|
||||
}
|
||||
GatewayContext *gateway_context = user_data;
|
||||
Log_i("get type %d from server", msg_type);
|
||||
|
||||
UtilsJsonValue sub_devices;
|
||||
int32_t status;
|
||||
|
||||
if (msg_type != TYPE_SEARCH_DEVICES && msg_type != TYPE_UNBIND_ALL) {
|
||||
rc = utils_json_value_get("payload.devices", sizeof("payload.devices") - 1, message->payload_str,
|
||||
message->payload_len, &sub_devices);
|
||||
if (rc) {
|
||||
Log_e("invalid gateway payload.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg_type == TYPE_SEARCH_DEVICES || msg_type == TYPE_CHANGE) {
|
||||
rc = utils_json_get_int32("payload.status", sizeof("payload.status") - 1, message->payload_str,
|
||||
message->payload_len, &status);
|
||||
if (rc) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_callback_gateway(msg_type, &gateway_context->callback, sub_devices, status, gateway_context->usr_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Subscribe gateway result topic.
|
||||
*
|
||||
* @param[in] client pointer to mqtt client
|
||||
* @param[in] callback @see IotGatewayMessageCallback
|
||||
* @param[in] usr_data
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _gateway_mqtt_subscribe(void *client, IotGatewayMessageCallback callback, void *usr_data)
|
||||
{
|
||||
GatewayContext *gateway_context = HAL_Malloc(sizeof(GatewayContext));
|
||||
if (!gateway_context) {
|
||||
return QCLOUD_ERR_MALLOC;
|
||||
}
|
||||
gateway_context->callback = callback;
|
||||
gateway_context->usr_data = usr_data;
|
||||
|
||||
char gateway_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
HAL_Snprintf(gateway_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$gateway/operation/result/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
|
||||
|
||||
SubscribeParams sub_param = DEFAULT_SUB_PARAMS;
|
||||
sub_param.on_message_handler = _gateway_mqtt_message_handler;
|
||||
sub_param.user_data = gateway_context;
|
||||
sub_param.user_data_free = HAL_Free;
|
||||
|
||||
int rc = IOT_MQTT_SubscribeSync(client, gateway_topic, &sub_param);
|
||||
if (rc) {
|
||||
HAL_Free(gateway_context);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// server mqtt context
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Calculate bind signature
|
||||
*
|
||||
* @param[in] subdev_info subdev info
|
||||
* @param[in] nonce random
|
||||
* @param[in] timestamp timestamp
|
||||
* @param[out] signature signature
|
||||
* @return 0 for success
|
||||
*/
|
||||
static int _subdev_bind_hmac_sha1_cal(const DeviceInfo *subdev_info, int nonce, uint64_t timestamp, char *signature)
|
||||
{
|
||||
#define MAX_SIGN_BUF_SIZE (MAX_SIZE_OF_CLIENT_ID + 48)
|
||||
size_t olen = 0;
|
||||
|
||||
char sign_buf[MAX_SIGN_BUF_SIZE] = {0};
|
||||
int sign_buf_len;
|
||||
char sign[SHA1_DIGEST_SIZE] = {0};
|
||||
|
||||
/* 1. sign fmt : ${product_id}${device_name};${random};${expiration_time} */
|
||||
sign_buf_len = HAL_Snprintf(sign_buf, sizeof(sign_buf), "%s%s;%d;%llu", subdev_info->product_id,
|
||||
subdev_info->device_name, nonce, timestamp);
|
||||
/* 2. cal hmac sha1 */
|
||||
utils_hmac_sha1(sign_buf, sign_buf_len, (const uint8_t *)subdev_info->device_secret,
|
||||
strlen(subdev_info->device_secret), sign);
|
||||
/* 3. base64 encode */
|
||||
return utils_base64encode((uint8_t *)signature, 31, &olen, (const uint8_t *)sign, SHA1_DIGEST_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish gateway message.
|
||||
*
|
||||
* @param[in] client pointer to mqtt client
|
||||
* @param[in] qos @see QoS
|
||||
* @param[in] payload message payload
|
||||
* @param[in] payload_len payload length
|
||||
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
static int _gateway_mqtt_publish(void *client, QoS qos, const char *payload, int payload_len)
|
||||
{
|
||||
char gateway_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
HAL_Snprintf(gateway_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$gateway/operation/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
|
||||
|
||||
PublishParams pub_params = DEFAULT_PUB_PARAMS;
|
||||
pub_params.qos = qos;
|
||||
pub_params.payload = (void *)payload;
|
||||
pub_params.payload_len = payload_len;
|
||||
return IOT_MQTT_Publish(client, gateway_topic, &pub_params);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// API
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Subscribe gateway topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] callback @see IotGatewayMessageCallback
|
||||
* @param[in] usr_data usr data using in callback
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int IOT_Gateway_Init(void *client, IotGatewayMessageCallback callback, void *usr_data)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
return _gateway_mqtt_subscribe(client, callback, usr_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unsubscribe gateway topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
void IOT_Gateway_Deinit(void *client)
|
||||
{
|
||||
POINTER_SANITY_CHECK_RTN(client);
|
||||
|
||||
char gateway_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
HAL_Snprintf(gateway_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$gateway/operation/result/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
|
||||
IOT_MQTT_Unsubscribe(client, gateway_topic);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get gateway init usr data.
|
||||
*
|
||||
* @param [in,out] client pointer to mqtt client
|
||||
* @return usr data or NULL
|
||||
*/
|
||||
void *IOT_Gateway_GetUsrData(void *client)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, NULL);
|
||||
char gateway_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
HAL_Snprintf(gateway_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$gateway/operation/result/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
|
||||
GatewayContext *gateway_context = IOT_MQTT_GetSubUsrData(client, gateway_topic);
|
||||
if (gateway_context && gateway_context->usr_data) {
|
||||
return gateway_context->usr_data;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish subdevice online/offline message.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] buf buffer to hold publish message
|
||||
* @param[in] buf_len buffer length
|
||||
* @param[in] sub_dev_list subdevice list
|
||||
* @param[in] num num of subdevice, @note only one subdevice is supported now.
|
||||
* @param[in] is_online 1: online; 0: offline
|
||||
* @return @see packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
int IOT_Gateway_SubOnOffLine(void *client, char *buf, int buf_len, const DeviceInfo *sub_dev_list[], int num,
|
||||
bool is_online)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(buf, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(sub_dev_list, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(*sub_dev_list, QCLOUD_ERR_INVAL);
|
||||
NUMBERIC_SANITY_CHECK(num, QCLOUD_ERR_INVAL);
|
||||
if (num > 1) {
|
||||
Log_e("Only one subdevice is supported!");
|
||||
return QCLOUD_ERR_INVAL;
|
||||
}
|
||||
|
||||
int len;
|
||||
len = HAL_Snprintf(buf, buf_len, "{\"type\":\"%s\",\"payload\":{\"devices\":[", is_online ? "online" : "offline");
|
||||
for (int i = 0; i < num; i++) {
|
||||
len += HAL_Snprintf(buf + len, buf_len - len, "{\"product_id\":\"%s\",\"device_name\":\"%s\"},",
|
||||
sub_dev_list[i]->product_id, sub_dev_list[i]->device_name);
|
||||
}
|
||||
len += HAL_Snprintf(buf + len - 1, buf_len - len + 1, "]}}"); // -1 remove ','
|
||||
return _gateway_mqtt_publish(client, QOS0, buf, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish subdevice bind/unbind message.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] buf buffer to hold publish message
|
||||
* @param[in] buf_len buffer length
|
||||
* @param[in] sub_dev_list subdevice list
|
||||
* @param[in] num num of subdevice, @note only one subdevice is supported now.
|
||||
* @param[in] is_bind 1: bind; 0: unbind
|
||||
* @return @see packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
int IOT_Gateway_BindUnbind(void *client, char *buf, int buf_len, const DeviceInfo *sub_dev_list[], int num,
|
||||
bool is_bind)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(buf, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(sub_dev_list, QCLOUD_ERR_INVAL);
|
||||
NUMBERIC_SANITY_CHECK(num, QCLOUD_ERR_INVAL);
|
||||
|
||||
if (num > 1) {
|
||||
Log_e("Only one subdevice is supported!");
|
||||
return QCLOUD_ERR_INVAL;
|
||||
}
|
||||
|
||||
srand(IOT_Timer_CurrentSec());
|
||||
int nonce = rand();
|
||||
uint64_t timestamp = IOT_Timer_CurrentSec();
|
||||
|
||||
char signature[31] = {0};
|
||||
int rc, len;
|
||||
len = HAL_Snprintf(buf, buf_len, "{\"type\":\"%s\",\"payload\":{\"devices\":[", is_bind ? "bind" : "unbind");
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (is_bind) {
|
||||
rc = _subdev_bind_hmac_sha1_cal(sub_dev_list[i] + i, nonce, timestamp, signature);
|
||||
if (rc) {
|
||||
Log_e("calculate signature fail");
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
len += HAL_Snprintf(buf + len, buf_len - len,
|
||||
"{\"product_id\":\"%s\",\"device_name\":\"%s\",\"signature\":\"%s\",\"random\":%d,"
|
||||
"\"timestamp\":%lld,\"signmethod\":\"hmacsha1\",\"authtype\":\"psk\"},",
|
||||
sub_dev_list[i]->product_id, sub_dev_list[i]->device_name, signature, nonce, timestamp);
|
||||
} else {
|
||||
len += HAL_Snprintf(buf + len, buf_len - len, "{\"product_id\":\"%s\",\"device_name\":\"%s\"},",
|
||||
sub_dev_list[i]->product_id, sub_dev_list[i]->device_name);
|
||||
}
|
||||
}
|
||||
len += HAL_Snprintf(buf + len - 1, buf_len - len + 1, "]}}"); // -1 remove ','
|
||||
return _gateway_mqtt_publish(client, QOS0, buf, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish subdevice describe message.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
int IOT_Gateway_Describe(void *client)
|
||||
{
|
||||
#define GATEWAY_UP_DESCRIBE "{\"type\":\"describe_sub_devices\"}"
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
return _gateway_mqtt_publish(client, QOS0, GATEWAY_UP_DESCRIBE, sizeof(GATEWAY_UP_DESCRIBE) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish search device reply message.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] is_on 1: on; 0: off
|
||||
* @param[in] result 0: success; 1: fail
|
||||
* @return @see packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
int IOT_Gateway_SearchDeviceReply(void *client, bool is_on, int result)
|
||||
{
|
||||
char buf[64];
|
||||
int len = HAL_Snprintf(buf, sizeof(buf), "{\"type\":\"search_devices\",\"payload\":{\"status\":%d,\"result\":%d}}",
|
||||
is_on, result);
|
||||
return _gateway_mqtt_publish(client, QOS0, buf, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish unbind all reply message.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] result 0: success; 1: fail
|
||||
* @return @see packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
int IOT_Gateway_UnbindAllReply(void *client, int result)
|
||||
{
|
||||
char buf[64];
|
||||
int len = HAL_Snprintf(buf, sizeof(buf), "{\"type\":\"unbind_all\",\"payload\":{\"result\":%d}}", result);
|
||||
return _gateway_mqtt_publish(client, QOS0, buf, len);
|
||||
}
|
||||
|
||||
#if 0 // TODO: no need now for useless
|
||||
int IOT_Gateway_ChangeReply(void *client, char *buf, int buf_len, DeviceInfo *sub_dev_list, int num, int status)
|
||||
{
|
||||
char buf[64];
|
||||
int len = HAL_Snprintf(buf, sizeof(buf), "{\"type\":\"change\",\"payload\":{\"status\":%d,\"devices\":[", status);
|
||||
for (int i = 0; i < num; i++) {
|
||||
len += HAL_Snprintf(buf + len, buf_len - len, "{\"product_id\":\"%s\",\"device_name\":\"%s\"},",
|
||||
sub_dev_list[i].product_id, sub_dev_list[i].device_name);
|
||||
}
|
||||
len += HAL_Snprintf(buf + len - 1, buf_len - len + 1, "]}}"); // -1 remove ','
|
||||
return _gateway_mqtt_publish(client, QOS0, buf, len);
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,8 @@
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
ColumnLimit: 120
|
||||
DerivePointerAlignment: true
|
||||
PointerAlignment: Left
|
||||
SortIncludes: true
|
||||
IncludeBlocks: Preserve
|
||||
IndentPPDirectives: AfterHash
|
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file test_data_template.cc
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-09-29
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-09-29 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "mqtt_client_test.h"
|
||||
#include "qcloud_iot_common.h"
|
||||
#include "qcloud_iot_explorer.h"
|
||||
|
||||
namespace mqtt_client_unittest {
|
||||
|
||||
static void _bind_unbind_reply_callback(UtilsJsonValue sub_devices, bool is_bind, void *usr_data) { Log_d("recv msg"); }
|
||||
static void _unbind_all_callback(void *usr_data) { Log_d("recv msg"); }
|
||||
static void _online_offline_reply_callback(UtilsJsonValue sub_devices, bool is_online, void *usr_data) {
|
||||
Log_d("recv msg");
|
||||
}
|
||||
static void _search_device_callback(bool is_on, void *usr_data) { Log_d("recv msg"); }
|
||||
static void _describe_subdevices_reply_callback(UtilsJsonValue sub_devices, void *usr_data) { Log_d("recv msg"); }
|
||||
static void _change_subdevices_status_callback(UtilsJsonValue sub_devices, bool is_bind, void *usr_data) {
|
||||
Log_d("recv msg");
|
||||
}
|
||||
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2 * !!(condition)]))
|
||||
/**
|
||||
* @brief Test data template action.
|
||||
*
|
||||
*/
|
||||
TEST_F(MqttClientTest, gateway) {
|
||||
char buf[1024];
|
||||
IotGatewayMessageCallback callback = {
|
||||
.bind_unbind_reply_callback = _bind_unbind_reply_callback,
|
||||
.unbind_all_callback = _unbind_all_callback,
|
||||
.online_offline_reply_callback = _online_offline_reply_callback,
|
||||
.search_device_callback = _search_device_callback,
|
||||
.describe_subdevices_reply_callback = _describe_subdevices_reply_callback,
|
||||
.change_subdevices_status_callback = _change_subdevices_status_callback,
|
||||
};
|
||||
|
||||
DeviceInfo sub_dev0;
|
||||
HAL_GetDevInfo(&sub_dev0);
|
||||
|
||||
const DeviceInfo *subdev[1] = {0};
|
||||
subdev[0] = &sub_dev0;
|
||||
|
||||
ASSERT_EQ(IOT_Gateway_Init(client, callback, client), 0);
|
||||
ASSERT_GE(IOT_Gateway_BindUnbind(client, buf, sizeof(buf), subdev, sizeof(subdev) / sizeof(DeviceInfo *), true), 0);
|
||||
IOT_MQTT_Yield(client, 100);
|
||||
ASSERT_GE(IOT_Gateway_Describe(client), 0);
|
||||
IOT_MQTT_Yield(client, 100);
|
||||
ASSERT_GE(IOT_Gateway_SubOnOffLine(client, buf, sizeof(buf), subdev, sizeof(subdev) / sizeof(DeviceInfo *), 1), 0);
|
||||
IOT_MQTT_Yield(client, 100);
|
||||
ASSERT_GE(IOT_Gateway_SubOnOffLine(client, buf, sizeof(buf), subdev, sizeof(subdev) / sizeof(DeviceInfo *), 0), 0);
|
||||
IOT_MQTT_Yield(client, 100);
|
||||
ASSERT_GE(IOT_Gateway_BindUnbind(client, buf, sizeof(buf), subdev, sizeof(subdev) / sizeof(DeviceInfo *), false), 0);
|
||||
IOT_MQTT_Yield(client, 100);
|
||||
|
||||
ASSERT_GE(IOT_Gateway_SearchDeviceReply(client, true, 0), 0);
|
||||
ASSERT_GE(IOT_Gateway_UnbindAllReply(client, 0), 0);
|
||||
IOT_Gateway_Deinit(client);
|
||||
}
|
||||
|
||||
} // namespace mqtt_client_unittest
|
@@ -0,0 +1,2 @@
|
||||
file(GLOB src_http_client ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
|
||||
set(src_services ${src_services} ${src_http_client} PARENT_SCOPE)
|
@@ -0,0 +1,629 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file http_client.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-10-22
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-10-22 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "qcloud_iot_http_client.h"
|
||||
|
||||
#include "network_interface.h"
|
||||
|
||||
/**
|
||||
* @brief Http data.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
const char *data;
|
||||
int data_len;
|
||||
} HTTPData;
|
||||
|
||||
/**
|
||||
* @brief Only focus on response code and content,
|
||||
* @ref https://datatracker.ietf.org/doc/html/rfc7231
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
int status_code; /**< https://en.wikipedia.org/wiki/List_of_HTTP_status_codes */
|
||||
int content_length; /**< content length */
|
||||
uint8_t *content_buf; /**< content buffer */
|
||||
int content_buf_len; /**< content buffer length */
|
||||
int recv_len; /**< already recv length */
|
||||
int need_recv_len; /**< need recv length */
|
||||
int is_chunked; /**< support chunked */
|
||||
} IotHTTPResponseData;
|
||||
|
||||
/**
|
||||
* @brief HTTP client.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
IotHTTPResponseData response;
|
||||
IotNetwork network;
|
||||
} IotHTTPClient;
|
||||
|
||||
/**************************************************************************************
|
||||
* network
|
||||
**************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Connect with host.
|
||||
*
|
||||
* @param[in,out] client pointer to http client. @see IotHTTPClient
|
||||
* @param[in] host http host
|
||||
* @param[in] port http port, 80(http)/443(https)
|
||||
* @param[in] ca_crt https ca
|
||||
* @return 0 for success. others @see IotReturnCode
|
||||
*/
|
||||
static int _http_client_connect(IotHTTPClient *client, const char *host, const char *port, const char *ca_crt)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
client->network.type = IOT_NETWORK_TYPE_TCP;
|
||||
#if !defined(AUTH_WITH_NO_TLS) && defined(AUTH_MODE_CERT)
|
||||
client->network.ssl_connect_params.ca_crt = ca_crt;
|
||||
client->network.ssl_connect_params.ca_crt_len = strlen(ca_crt);
|
||||
client->network.ssl_connect_params.cert_file = NULL;
|
||||
client->network.ssl_connect_params.key_file = NULL;
|
||||
client->network.ssl_connect_params.timeout_ms = HTTPS_READ_TIMEOUT_MS;
|
||||
#endif
|
||||
client->network.host = host;
|
||||
client->network.port = port;
|
||||
|
||||
rc = qcloud_iot_network_init(&(client->network));
|
||||
return rc ? rc : client->network.connect(&(client->network));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnect with host.
|
||||
*
|
||||
* @param[in,out] client pointer to http client. @see IotHTTPClient
|
||||
*/
|
||||
static void _http_client_disconnect(IotHTTPClient *client)
|
||||
{
|
||||
return client->network.disconnect(&(client->network));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send data to http server.
|
||||
*
|
||||
* @param[in,out] client pointer to http client. @see IotHTTPClient
|
||||
* @param[in] buf buffer tot send
|
||||
* @param[in] len buffer length
|
||||
* @return 0 for success. others @see IotReturnCode
|
||||
*/
|
||||
static int _http_client_send(IotHTTPClient *client, char *buf, uint32_t len)
|
||||
{
|
||||
size_t written_len;
|
||||
return client->network.write(&(client->network), (uint8_t *)buf, len, HTTP_WRITE_TIMEOUT_MS, &written_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recv data from http server.
|
||||
*
|
||||
* @param[in,out] client pointer to http client. @see IotHTTPClient
|
||||
* @param[out] buf
|
||||
* @param[in] len
|
||||
* @param[in] timeout_ms
|
||||
* @return >0 for read data length, others @see IotReturnCode
|
||||
*/
|
||||
static int _http_client_recv(IotHTTPClient *client, uint8_t *buf, uint32_t len, uint32_t timeout_ms)
|
||||
{
|
||||
size_t read_len;
|
||||
|
||||
int rc = client->network.read(&client->network, buf, len, timeout_ms, &read_len);
|
||||
switch (rc) {
|
||||
case QCLOUD_ERR_TCP_NOTHING_TO_READ:
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
default:
|
||||
return rc ? rc : read_len;
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************************
|
||||
* request
|
||||
**************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Parse url and get host & path.
|
||||
*
|
||||
* @param[in] url url str
|
||||
* @param[out] host pointer to host str and length. @see HTTPData
|
||||
* @param[out] path pointer to path str and length. @see HTTPData
|
||||
* @return 0 for success. others @see IotReturnCode
|
||||
*/
|
||||
static int _http_client_parse_url(const char *url, HTTPData *host, HTTPData *path)
|
||||
{
|
||||
// start from: http:// or https://
|
||||
char *host_ptr = (char *)strstr(url, "://");
|
||||
if (!host_ptr) {
|
||||
return QCLOUD_ERR_HTTP_PARSE;
|
||||
}
|
||||
host->data = host_ptr + 3;
|
||||
|
||||
path->data = strchr(host->data, '/');
|
||||
if (!path->data) {
|
||||
host->data_len = url + strlen(url) - host->data;
|
||||
path->data_len = 1;
|
||||
path->data = "/";
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
host->data_len = path->data - host->data;
|
||||
|
||||
char *fragment_ptr = strchr(host_ptr, '#');
|
||||
path->data_len = fragment_ptr ? fragment_ptr - path->data : strlen(path->data);
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send http request line.
|
||||
*
|
||||
* @param[in,out] client pointer to http client. @see IotHTTPClient
|
||||
* @param[in] params params needed to send request to http server, @see IotHTTPRequestParams
|
||||
* @return int
|
||||
*/
|
||||
static int _http_client_send_request_line(IotHTTPClient *client, const IotHTTPRequestParams *params)
|
||||
{
|
||||
int len, rc, buf_len;
|
||||
char *buf;
|
||||
HTTPData host, path;
|
||||
|
||||
/**
|
||||
* @brief order @see IotHTTPMethod
|
||||
*
|
||||
*/
|
||||
const char *method_str[] = {
|
||||
[IOT_HTTP_METHOD_GET] = "GET", [IOT_HTTP_METHOD_POST] = "POST", [IOT_HTTP_METHOD_PUT] = "PUT",
|
||||
[IOT_HTTP_METHOD_DELETE] = "DELETE", [IOT_HTTP_METHOD_HEAD] = "HEAD",
|
||||
};
|
||||
|
||||
rc = _http_client_parse_url(params->url, &host, &path);
|
||||
if (rc) {
|
||||
Log_e("http parse url failed %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
buf_len = path.data_len + host.data_len + 128;
|
||||
buf = HAL_Malloc(buf_len);
|
||||
if (!buf) {
|
||||
Log_e("http malloc request line failed %d", rc);
|
||||
return QCLOUD_ERR_MALLOC;
|
||||
}
|
||||
|
||||
len = HAL_Snprintf(buf, buf_len, "%s %.*s HTTP/1.1\r\nHost:%.*s\r\n", method_str[params->method], path.data_len,
|
||||
path.data, host.data_len, host.data);
|
||||
rc = _http_client_send(client, buf, len);
|
||||
HAL_Free(buf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send http request content(optional).
|
||||
*
|
||||
* @param[in,out] client pointer to http client. @see IotHTTPClient
|
||||
* @param[in] params params needed to send request to http server, @see IotHTTPRequestParams
|
||||
* @return int
|
||||
*/
|
||||
static int _http_client_send_request_content(IotHTTPClient *client, const IotHTTPRequestParams *params)
|
||||
{
|
||||
int rc = 0, len, buf_len;
|
||||
char *buf;
|
||||
|
||||
buf_len = params->content_type ? 32 + strlen(params->content_type) : 32;
|
||||
buf = HAL_Malloc(buf_len);
|
||||
if (!buf) {
|
||||
Log_e("http malloc request line failed %d", rc);
|
||||
return QCLOUD_ERR_MALLOC;
|
||||
}
|
||||
|
||||
len = HAL_Snprintf(buf, buf_len, "Content-Length:%d\r\n", params->content_length);
|
||||
rc = _http_client_send(client, buf, len);
|
||||
if (rc) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (params->content_type) {
|
||||
len = HAL_Snprintf(buf, buf_len, "Content-Type:%s\r\n", params->content_type);
|
||||
rc = _http_client_send(client, buf, len);
|
||||
if (rc) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
rc = _http_client_send(client, "\r\n", 2);
|
||||
if (rc) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
HAL_Free(buf);
|
||||
return _http_client_send(client, (char *)params->content, params->content_length);
|
||||
exit:
|
||||
HAL_Free(buf);
|
||||
Log_e("send request content failed %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send http request to http server.
|
||||
*
|
||||
* @param[in,out] client pointer to http client. @see IotHTTPClient
|
||||
* @param[in] params @see IotHTTPRequestParams
|
||||
* @return 0 for success. others @see IotReturnCode
|
||||
*/
|
||||
static int _http_client_send_request(IotHTTPClient *client, const IotHTTPRequestParams *params)
|
||||
{
|
||||
int rc;
|
||||
|
||||
// 1. send request line
|
||||
rc = _http_client_send_request_line(client, params);
|
||||
if (rc) {
|
||||
Log_e("http send request line failed, rc=%d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// 2. send request header
|
||||
if (params->header) {
|
||||
rc = _http_client_send(client, (char *)params->header, strlen(params->header));
|
||||
if (rc) {
|
||||
Log_e("write failed %d", rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
if (!params->content || !params->content_length) { // no payload body
|
||||
return _http_client_send(client, "\r\n", 2);
|
||||
}
|
||||
|
||||
// 3. send payload body
|
||||
return _http_client_send_request_content(client, params);
|
||||
}
|
||||
|
||||
/**************************************************************************************
|
||||
* response
|
||||
**************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Recv data chunked.
|
||||
*
|
||||
* @param[in,out] client pointer to http client. @see IotHTTPClient
|
||||
* @param[in] timeout_ms timeout for recv
|
||||
* @return length of content data recv stored in content buf
|
||||
*/
|
||||
static int _http_client_chunked_recv(IotHTTPClient *client, uint32_t timeout_ms)
|
||||
{
|
||||
uint8_t *buf = client->response.content_buf;
|
||||
int buf_len = client->response.content_buf_len;
|
||||
|
||||
IotHTTPResponseData *response = &client->response;
|
||||
|
||||
char *crlf_pointer = NULL;
|
||||
char *find_from = (char *)buf;
|
||||
int rc = 0, read_size = 0;
|
||||
int read_size_flag = 1;
|
||||
|
||||
QcloudIotTimer read_timer;
|
||||
IOT_Timer_CountdownMs(&read_timer, timeout_ms);
|
||||
|
||||
do {
|
||||
while (1) {
|
||||
// find \r\n from find_from, start with beginning
|
||||
crlf_pointer = strstr(find_from, "\r\n");
|
||||
if (crlf_pointer) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (IOT_Timer_Expired(&read_timer)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// try to read more to found \r\n
|
||||
rc = _http_client_recv(client, buf + response->recv_len, buf_len - response->recv_len, 100);
|
||||
if (rc < 0) {
|
||||
return response->recv_len;
|
||||
}
|
||||
response->recv_len += rc;
|
||||
}
|
||||
|
||||
// read size before \r\n
|
||||
if (read_size_flag) {
|
||||
*crlf_pointer = '\0';
|
||||
read_size = strtoul(find_from, NULL, 16);
|
||||
response->recv_len -= crlf_pointer - find_from + 2;
|
||||
if (!read_size) { // last chunk
|
||||
response->need_recv_len = 0;
|
||||
return response->recv_len;
|
||||
}
|
||||
memmove(find_from, crlf_pointer + 2, buf + response->recv_len - (uint8_t *)find_from);
|
||||
read_size_flag = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// remove /r/n and set find from
|
||||
response->recv_len -= 2;
|
||||
memmove(crlf_pointer, crlf_pointer + 2, buf + response->recv_len - (uint8_t *)crlf_pointer);
|
||||
find_from += read_size;
|
||||
read_size_flag = true;
|
||||
} while (!IOT_Timer_Expired(&read_timer));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recv data according content length.
|
||||
*
|
||||
* @param[in,out] client pointer to http client. @see IotHTTPClient
|
||||
* @param[in] offset offset of content buf
|
||||
* @param[in] timeout_ms timeout for recv
|
||||
* @return length of content data recv stored in content buf
|
||||
*/
|
||||
static int _http_client_content_length_recv(IotHTTPClient *client, int offset, uint32_t timeout_ms)
|
||||
{
|
||||
int rc, len = 0;
|
||||
|
||||
uint8_t *buf = client->response.content_buf;
|
||||
int buf_len = client->response.content_buf_len;
|
||||
|
||||
IotHTTPResponseData *response = &client->response;
|
||||
|
||||
if (response->need_recv_len <= 0) {
|
||||
// return offset for data already received
|
||||
return offset;
|
||||
}
|
||||
|
||||
// need recv length may much longger than buffer length
|
||||
len = response->need_recv_len > buf_len - offset ? buf_len - offset : response->need_recv_len;
|
||||
rc = _http_client_recv(client, buf + offset, len, timeout_ms);
|
||||
if (rc <= 0) {
|
||||
return offset ? offset : rc;
|
||||
}
|
||||
response->recv_len += rc;
|
||||
response->need_recv_len -= rc;
|
||||
return rc + offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recv content data.
|
||||
*
|
||||
* @param[in,out] client pointer to http client. @see IotHTTPClient
|
||||
* @param[in] offset offset of content buf
|
||||
* @param[in] timeout_ms timeout for recv
|
||||
* @return length of content data recv stored in content buf
|
||||
*/
|
||||
static int _http_client_recv_content(IotHTTPClient *client, int offset, uint32_t timeout_ms)
|
||||
{
|
||||
return client->response.is_chunked ? _http_client_chunked_recv(client, timeout_ms)
|
||||
: _http_client_content_length_recv(client, offset, timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recv response from http server.
|
||||
*
|
||||
* @param[in,out] client pointer to http client. @see IotHTTPClient
|
||||
* @param[in] timeout_ms timeout for recv
|
||||
* @return 0 for success. others @see IotReturnCode
|
||||
*/
|
||||
static int _http_client_recv_response(IotHTTPClient *client, uint32_t timeout_ms)
|
||||
{
|
||||
QcloudIotTimer timer;
|
||||
char *content_length, *body_end;
|
||||
int rc, len = 0;
|
||||
char *buf = (char *)client->response.content_buf;
|
||||
int buf_len = client->response.content_buf_len;
|
||||
|
||||
IotHTTPResponseData *response = &client->response;
|
||||
memset(buf, 0, buf_len);
|
||||
IOT_Timer_CountdownMs(&timer, timeout_ms);
|
||||
|
||||
// 1. found body end
|
||||
while (NULL == (body_end = strstr(buf, "\r\n\r\n"))) {
|
||||
// timeout
|
||||
if (IOT_Timer_Expired(&timer)) {
|
||||
return QCLOUD_ERR_HTTP_TIMEOUT;
|
||||
}
|
||||
// timeout 100ms for header less than buff len
|
||||
rc = _http_client_recv(client, (uint8_t *)buf + len, buf_len - len - 1, 100);
|
||||
if (rc < 0) {
|
||||
if (rc == QCLOUD_ERR_TCP_READ_TIMEOUT || rc == QCLOUD_ERR_SSL_READ_TIMEOUT) {
|
||||
continue;
|
||||
}
|
||||
Log_e("read failed, rc %d", rc);
|
||||
return rc;
|
||||
}
|
||||
len += rc;
|
||||
}
|
||||
|
||||
body_end += 4; // \r\n\r\n
|
||||
len = len - (body_end - buf); // body length
|
||||
|
||||
// 2. get response code
|
||||
response->status_code = atoi(buf + 9);
|
||||
switch (response->status_code) {
|
||||
case 403:
|
||||
return QCLOUD_ERR_HTTP_AUTH;
|
||||
case 404:
|
||||
return QCLOUD_ERR_HTTP_NOT_FOUND;
|
||||
default:
|
||||
if (response->status_code < 200 || response->status_code >= 400) {
|
||||
Log_w("HTTP status code %d", response->status_code);
|
||||
return QCLOUD_ERR_HTTP;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 3. parse header
|
||||
// content length
|
||||
content_length = strstr(buf, "Content-Length");
|
||||
if (content_length) {
|
||||
response->is_chunked = 0;
|
||||
response->content_length = atoi(content_length + strlen("Content-Length: "));
|
||||
response->need_recv_len = response->content_length - len;
|
||||
goto recv_content;
|
||||
}
|
||||
|
||||
// chunked
|
||||
if (strstr(buf, "Transfer-Encoding: chunked")) {
|
||||
response->is_chunked = 1;
|
||||
response->need_recv_len = 1; // means always need recv
|
||||
goto recv_content;
|
||||
}
|
||||
|
||||
Log_e("Could not parse header");
|
||||
response->content_length = -1;
|
||||
return QCLOUD_ERR_HTTP;
|
||||
|
||||
// 4. remove data and receive body data
|
||||
recv_content:
|
||||
memmove(buf, body_end, len);
|
||||
response->recv_len = len;
|
||||
return _http_client_recv_content(client, len, IOT_Timer_Remain(&timer));
|
||||
}
|
||||
|
||||
/**************************************************************************************
|
||||
* API
|
||||
**************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Malloc http client.
|
||||
*
|
||||
* @return pointer to http client
|
||||
*/
|
||||
void *IOT_HTTP_Init(void)
|
||||
{
|
||||
return HAL_Malloc(sizeof(IotHTTPClient));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Free http client.
|
||||
*
|
||||
*/
|
||||
void IOT_HTTP_Deinit(void *client)
|
||||
{
|
||||
HAL_Free(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connect http server.
|
||||
*
|
||||
* @param[in,out] client pointer to http client
|
||||
* @param[in] params params needed to connect http server, @see IotHTTPConnectParams
|
||||
* @return 0 for success. others @see IotReturnCode
|
||||
*/
|
||||
int IOT_HTTP_Connect(void *client, IotHTTPConnectParams *params)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
IotHTTPClient *http_client = (IotHTTPClient *)client;
|
||||
memset(http_client, 0, sizeof(IotHTTPClient));
|
||||
|
||||
int rc;
|
||||
|
||||
// parse url
|
||||
HTTPData host, path;
|
||||
rc = _http_client_parse_url(params->url, &host, &path);
|
||||
if (rc) {
|
||||
Log_e("http parse url failed %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// copy host
|
||||
char *host_str = HAL_Malloc(host.data_len + 1);
|
||||
if (!host_str) {
|
||||
return QCLOUD_ERR_MALLOC;
|
||||
}
|
||||
strncpy(host_str, host.data, host.data_len);
|
||||
host_str[host.data_len] = '\0';
|
||||
|
||||
// http connect
|
||||
rc = _http_client_connect(http_client, host_str, params->port, params->ca_crt);
|
||||
HAL_Free(host_str);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Request http server.
|
||||
*
|
||||
* @param[in,out] client pointer to http client
|
||||
* @param[in] params params needed to send request to http server, @see IotHTTPRequestParams
|
||||
* @return 0 for success. others @see IotReturnCode
|
||||
*/
|
||||
int IOT_HTTP_Request(void *client, IotHTTPRequestParams *params)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
return _http_client_send_request((IotHTTPClient *)client, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send data to http server.
|
||||
*
|
||||
* @param[in,out] client pointer to http client
|
||||
* @param[in] data data to send
|
||||
* @param[out] data_len data len
|
||||
* @return 0 for success. others @see IotReturnCode
|
||||
*/
|
||||
int IOT_HTTP_Send(void *client, uint8_t *data, int data_len)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
return _http_client_send((IotHTTPClient *)client, (char *)data, data_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recv data from http server.
|
||||
*
|
||||
* @param[in,out] client pointer to http client
|
||||
* @param[out] buf buffer to store recv data
|
||||
* @param[in] buf_len buffer len
|
||||
* @param timeout_ms timeout for recv
|
||||
* @return >= 0 for recv data len. others @see IotReturnCode
|
||||
*/
|
||||
int IOT_HTTP_Recv(void *client, uint8_t *buf, int buf_len, uint32_t timeout_ms)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
IotHTTPClient *http_client = (IotHTTPClient *)client;
|
||||
http_client->response.content_buf = buf;
|
||||
http_client->response.content_buf_len = buf_len;
|
||||
return http_client->response.need_recv_len ? _http_client_recv_content(http_client, 0, timeout_ms)
|
||||
: _http_client_recv_response(http_client, timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check is recv finished.
|
||||
*
|
||||
* @param[in,out] client pointer to http client
|
||||
* @return true for finished.
|
||||
*/
|
||||
int IOT_HTTP_IsRecvFinished(void *client)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
IotHTTPClient *http_client = (IotHTTPClient *)client;
|
||||
return http_client->response.need_recv_len == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnect http server.
|
||||
*
|
||||
* @param[in,out] client pointer to http client
|
||||
*/
|
||||
void IOT_HTTP_Disconnect(void *client)
|
||||
{
|
||||
POINTER_SANITY_CHECK_RTN(client);
|
||||
_http_client_disconnect((IotHTTPClient *)client);
|
||||
}
|
@@ -0,0 +1,2 @@
|
||||
file(GLOB src_http_signed ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
|
||||
set(src_services ${src_services} ${src_http_signed} PARENT_SCOPE)
|
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* @file http_signed.c
|
||||
* @author {hubert} ({hubertxxu@tencent.com})
|
||||
* @brief
|
||||
* @version 1.0
|
||||
* @date 2022-01-21
|
||||
*
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* Date Version Author Description
|
||||
* 2022-01-21 1.0 hubertxxu first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "qcloud_iot_http_signed.h"
|
||||
|
||||
#include "utils_hmac.h"
|
||||
#include "utils_base64.h"
|
||||
|
||||
/**
|
||||
* @brief http signed request header.
|
||||
*
|
||||
*/
|
||||
#define HTTP_SIGNED_REQUEST_HEADER_LEN (1024)
|
||||
|
||||
/**
|
||||
* @brief http signed request header signed length
|
||||
*
|
||||
*/
|
||||
#define HTTP_SIGNED_STRING_BUFFER_LEN (256)
|
||||
|
||||
/**
|
||||
* @brief http signed request url length
|
||||
*
|
||||
*/
|
||||
#define HTTP_SIGNED_URL_LEN (128)
|
||||
|
||||
#define QCLOUD_SHA256_RESULT_LEN (32)
|
||||
#define QCLOUD_SHA1_RESULT_LEN (20)
|
||||
|
||||
typedef struct {
|
||||
HttpSignedParams params;
|
||||
void *http_client;
|
||||
IotHTTPRequestParams http_request;
|
||||
char *sign_string;
|
||||
char *url;
|
||||
} HTTPSignedHandle;
|
||||
|
||||
/**
|
||||
* @brief get rand
|
||||
*
|
||||
* @param[in, out] seed
|
||||
* @return int
|
||||
*/
|
||||
static int _get_http_header_nonce(void)
|
||||
{
|
||||
srand(IOT_Timer_CurrentSec());
|
||||
return rand();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connect http server.
|
||||
*
|
||||
* @param[in,out] handle pointer to http signed handle, @see HTTPSignedHandle
|
||||
* @return 0 for success. others @see IotReturnCode
|
||||
*/
|
||||
static int _http_signed_connect(HTTPSignedHandle *handle)
|
||||
{
|
||||
IotHTTPConnectParams connect_params = {
|
||||
.url = handle->url,
|
||||
.port = "80",
|
||||
.ca_crt = NULL, // TODO: support cert
|
||||
|
||||
};
|
||||
return IOT_HTTP_Connect(handle->http_client, &connect_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief http signed header construct
|
||||
*
|
||||
* @param[in,out] handle pointer to http signed handle, @see HTTPSignedHandle
|
||||
* @param request_body_buf
|
||||
* @param request_body_buf_len
|
||||
* @return 0 if success
|
||||
*/
|
||||
static void _http_signed_upload_header_construct(HTTPSignedHandle *handle, const char *request_body_buf,
|
||||
int request_body_buf_len)
|
||||
{
|
||||
/**
|
||||
* @brief http signed header format
|
||||
*
|
||||
*/
|
||||
#define QCLOUD_HTTP_HEADER_FORMAT \
|
||||
"Accept: %s*/*\r\n" \
|
||||
"X-TC-Algorithm: %s\r\n" \
|
||||
"X-TC-Timestamp: %d\r\n" \
|
||||
"X-TC-Nonce: %d\r\n" \
|
||||
"X-TC-Signature: %s\r\n"
|
||||
|
||||
#define QCLOUD_SUPPORT_HMACSHA1 "hmacsha1"
|
||||
#define QCLOUD_SUPPORT_RSASHA256 "rsa-sha256"
|
||||
#define QCLOUD_SUPPORT_ACCEPT_HEADER "application/json;"
|
||||
|
||||
char sign[QCLOUD_SHA1_RESULT_LEN] = {0};
|
||||
char signout[QCLOUD_SHA1_RESULT_LEN * 2] = {0};
|
||||
char request_buf_sha1[QCLOUD_SHA1_RESULT_LEN * 2 + 1] = {0};
|
||||
size_t olen = 0;
|
||||
int nonce = _get_http_header_nonce();
|
||||
uint32_t timestamp = IOT_Timer_CurrentSec();
|
||||
|
||||
memset(handle->sign_string, 0, HTTP_SIGNED_STRING_BUFFER_LEN);
|
||||
/* cal hmac sha1 */
|
||||
utils_sha1_hex((const uint8_t *)request_body_buf, request_body_buf_len, (uint8_t *)request_buf_sha1);
|
||||
/* create sign string */
|
||||
HAL_Snprintf(handle->sign_string, HTTP_SIGNED_STRING_BUFFER_LEN, "%s\n%s\n%s\n%s\n%s\n%d\n%d\n%s", "POST",
|
||||
handle->params.host, handle->params.uri, "", QCLOUD_SUPPORT_HMACSHA1, timestamp, nonce,
|
||||
request_buf_sha1);
|
||||
|
||||
utils_hmac_sha1(handle->sign_string, strlen(handle->sign_string), (uint8_t *)handle->params.secretkey,
|
||||
strlen(handle->params.secretkey), sign);
|
||||
|
||||
/* base64 encode */
|
||||
utils_base64encode((uint8_t *)signout, QCLOUD_SHA1_RESULT_LEN * 2, &olen, (const uint8_t *)sign,
|
||||
QCLOUD_SHA1_RESULT_LEN);
|
||||
|
||||
HAL_Snprintf(handle->http_request.header, HTTP_SIGNED_REQUEST_HEADER_LEN, QCLOUD_HTTP_HEADER_FORMAT,
|
||||
QCLOUD_SUPPORT_ACCEPT_HEADER, QCLOUD_SUPPORT_HMACSHA1, timestamp, nonce, signout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief http signed init.
|
||||
*
|
||||
* @param params @see HttpSignedParams
|
||||
* @return pointer to http signed @see HTTPSignedHandle
|
||||
*/
|
||||
void *_http_signed_init(HttpSignedParams *params)
|
||||
{
|
||||
HTTPSignedHandle *handle = HAL_Malloc(sizeof(HTTPSignedHandle));
|
||||
if (!handle) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
handle->http_client = IOT_HTTP_Init();
|
||||
if (!handle->http_client) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
handle->http_request.header = HAL_Malloc(HTTP_SIGNED_REQUEST_HEADER_LEN);
|
||||
if (!handle->http_request.header) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
handle->sign_string = HAL_Malloc(HTTP_SIGNED_STRING_BUFFER_LEN);
|
||||
if (!handle->sign_string) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
handle->url = HAL_Malloc(HTTP_SIGNED_URL_LEN);
|
||||
if (!handle->url) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
handle->params = *params;
|
||||
memset(handle->url, 0, HTTP_SIGNED_URL_LEN);
|
||||
HAL_Snprintf(handle->url, HTTP_SIGNED_URL_LEN, "%s://%s%s", "http", handle->params.host, handle->params.uri);
|
||||
|
||||
return handle;
|
||||
exit:
|
||||
if (handle) {
|
||||
HAL_Free(handle->http_request.header);
|
||||
HAL_Free(handle->sign_string);
|
||||
HAL_Free(handle->url);
|
||||
IOT_HTTP_Deinit(handle->http_client);
|
||||
HAL_Free(handle);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief send to server
|
||||
*
|
||||
* @param[in] handle pointer to http signed @see HTTPSignedHandle
|
||||
* @param[in] upload_buf need send data
|
||||
* @param[in] upload_len send length
|
||||
* @return int 0 for success. others @see IotReturnCode
|
||||
*/
|
||||
int _http_signed_upload(HTTPSignedHandle *handle, const char *upload_buf, size_t upload_len)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
rc = _http_signed_connect(handle);
|
||||
if (rc) {
|
||||
return rc;
|
||||
}
|
||||
_http_signed_upload_header_construct(handle, upload_buf, upload_len);
|
||||
|
||||
handle->http_request.url = handle->url;
|
||||
handle->http_request.method = IOT_HTTP_METHOD_POST;
|
||||
handle->http_request.content_type = "application/json;charset=utf-8";
|
||||
handle->http_request.content = (char *)upload_buf;
|
||||
handle->http_request.content_length = upload_len;
|
||||
|
||||
rc = IOT_HTTP_Request(handle->http_client, &handle->http_request);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief http signed deinit
|
||||
*
|
||||
* @param[in] handle pointer to http signed @see HTTPSignedHandle
|
||||
*/
|
||||
static void _http_signed_deinit(HTTPSignedHandle *signed_handle)
|
||||
{
|
||||
POINTER_SANITY_CHECK_RTN(signed_handle);
|
||||
IOT_HTTP_Disconnect(signed_handle->http_client);
|
||||
IOT_HTTP_Deinit(signed_handle->http_client);
|
||||
HAL_Free(signed_handle->http_request.header);
|
||||
HAL_Free(signed_handle->sign_string);
|
||||
HAL_Free(signed_handle->url);
|
||||
HAL_Free(signed_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief post message and recv response
|
||||
*
|
||||
* @param params @see HttpSignedParams
|
||||
* @param request_buf request buffer
|
||||
* @param request_buf_len request buffer length
|
||||
* @param response_buf response buffer if need recv
|
||||
* @param response_buf_len response buffer length if need recv
|
||||
* @return int if need recv return recv length else return 0 is success
|
||||
*/
|
||||
int IOT_HTTP_SignedRequest(HttpSignedParams *params, const char *request_buf, size_t request_buf_len,
|
||||
uint8_t *response_buf, int response_buf_len)
|
||||
{
|
||||
int rc = 0;
|
||||
POINTER_SANITY_CHECK(params, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(request_buf, QCLOUD_ERR_INVAL);
|
||||
if (params->need_recv) {
|
||||
POINTER_SANITY_CHECK(response_buf, QCLOUD_ERR_INVAL);
|
||||
}
|
||||
HTTPSignedHandle *handle = _http_signed_init(params);
|
||||
if (!handle) {
|
||||
Log_e("http signed init error.");
|
||||
goto exit;
|
||||
}
|
||||
rc = _http_signed_upload(handle, request_buf, request_buf_len);
|
||||
if (rc) {
|
||||
Log_e("http signed upload error.");
|
||||
goto exit;
|
||||
}
|
||||
if (params->need_recv) {
|
||||
rc = IOT_HTTP_Recv(handle->http_client, response_buf, response_buf_len, params->recv_timeout_ms);
|
||||
}
|
||||
|
||||
exit:
|
||||
_http_signed_deinit(handle);
|
||||
return rc;
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
file(GLOB src_log_upload ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
|
||||
set(inc_log_upload ${CMAKE_CURRENT_SOURCE_DIR}/inc/)
|
||||
|
||||
set(src_services ${src_services} ${src_log_upload} PARENT_SCOPE)
|
||||
set(inc_services ${inc_services} ${inc_log_upload} PARENT_SCOPE)
|
||||
|
||||
if( ${CONFIG_COMPILE_SAMPLE} STREQUAL "ON")
|
||||
file(GLOB src_log_upload_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/log_upload_sample.c)
|
||||
add_executable(log_upload_sample ${src_log_upload_sample})
|
||||
target_link_libraries(log_upload_sample ${libsdk})
|
||||
endif()
|
||||
|
||||
if( ${CONFIG_IOT_TEST} STREQUAL "ON")
|
||||
file(GLOB src_unit_test ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc)
|
||||
set(src_test ${src_test} ${src_unit_test} PARENT_SCOPE)
|
||||
endif()
|
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file mqtt_client.h
|
||||
* @brief mqtt client internel api
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-05-31
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-05-31 <td>1.0 <td>fancyxu <td>first commit
|
||||
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#ifndef IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_LOG_UPLOAD_INC_LOG_UPLOAD_H_
|
||||
#define IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_LOG_UPLOAD_INC_LOG_UPLOAD_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "qcloud_iot_log_upload.h"
|
||||
|
||||
#include "utils_list.h"
|
||||
#include "utils_base64.h"
|
||||
#include "utils_hmac.h"
|
||||
|
||||
/**
|
||||
* @brief To check log http server return msg or not.
|
||||
*
|
||||
*/
|
||||
#define LOG_CHECK_HTTP_RET_CODE
|
||||
|
||||
/**
|
||||
* @brief Enable log upload debug or not.
|
||||
*
|
||||
*/
|
||||
// #define LOG_UPLOAD_DEBUG
|
||||
|
||||
/**
|
||||
* @brief Http recv length.
|
||||
*
|
||||
*/
|
||||
#define HTTP_RET_JSON_LENGTH 256
|
||||
|
||||
/**
|
||||
* @brief Wait http recv time out.
|
||||
*
|
||||
*/
|
||||
#define HTTP_WAIT_RET_TIMEOUT_MS 5000
|
||||
|
||||
/**
|
||||
* @brief Do immediate log update if buffer is lower than this threshold
|
||||
*
|
||||
*/
|
||||
#define LOG_LOW_BUFFER_THRESHOLD (LOG_UPLOAD_BUFFER_SIZE / 4)
|
||||
|
||||
/**
|
||||
* @brief Sign key length.
|
||||
*
|
||||
*/
|
||||
#define SIGN_KEY_SIZE (24)
|
||||
|
||||
#ifdef LOG_UPLOAD_DEBUG
|
||||
#define UPLOAD_DBG(fmt, ...) HAL_Printf(">>LOG-DBG>>%s(%d): " fmt "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
#else
|
||||
#define UPLOAD_DBG(...)
|
||||
#endif
|
||||
#define UPLOAD_ERR(fmt, ...) HAL_Printf(">>LOG-ERR>>%s(%d): " fmt "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* @brief Log upload buffer init.
|
||||
*
|
||||
* @param[in] init_params @see LogUploadInitParams
|
||||
* @return void* if success return @see LogUploadBuffer
|
||||
*/
|
||||
void *log_upload_buffer_init(const LogUploadInitParams *init_params);
|
||||
|
||||
/**
|
||||
* @brief Deinit log buffer handle.
|
||||
*
|
||||
* @param[in] upload_buffer_handle @see LogUploadBuffer
|
||||
*/
|
||||
void log_upload_buffer_deinit(void *upload_buffer_handle);
|
||||
|
||||
/**
|
||||
* @brief Clear log upload buffer.
|
||||
*
|
||||
*/
|
||||
void log_upload_buffer_clear(void);
|
||||
|
||||
/**
|
||||
* @brief Get the log buffer write index object.
|
||||
*
|
||||
* @param[in] log_upload_buffer_handle @see LogUploadBuffer
|
||||
* @return write index
|
||||
*/
|
||||
uint32_t log_upload_buffer_get_write_index(void *log_upload_buffer_handle);
|
||||
|
||||
/**
|
||||
* @brief Get the log buffer head len object.
|
||||
*
|
||||
* @param[in] log_upload_buffer_handle @see LogUploadBuffer
|
||||
* @return head length
|
||||
*/
|
||||
uint32_t log_upload_buffer_get_head_len(void *log_upload_buffer_handle);
|
||||
|
||||
/**
|
||||
* @brief Get the log buffer size object.
|
||||
*
|
||||
* @return log buffer size
|
||||
*/
|
||||
uint32_t log_upload_buffer_get_size(void);
|
||||
|
||||
/**
|
||||
* @brief Check log buffer is empty or not.
|
||||
*
|
||||
* @param[in] log_upload_buffer_handle @see LogUploadBuffer
|
||||
* @return true empty
|
||||
* @return false not empty
|
||||
*/
|
||||
bool log_upload_buffer_is_empty(void *log_upload_buffer_handle);
|
||||
|
||||
/**
|
||||
* @brief When a new log appears during the reporting period, move the log to the specified position in the buffer.
|
||||
*
|
||||
* @param[in] log_upload_buffer_handle @see LogUploadBuffer
|
||||
* @param[in] need_remove_size
|
||||
*/
|
||||
void log_upload_buffer_remove_log(void *log_upload_buffer_handle, uint32_t need_remove_size);
|
||||
|
||||
/**
|
||||
* @brief Copy log buffer head.
|
||||
*
|
||||
* @param[out] dst Destination address
|
||||
* @param[in] log_upload_buffer_handle @see LogUploadBuffer
|
||||
*/
|
||||
void log_upload_buffer_copy_header(char *dst, void *log_upload_buffer_handle);
|
||||
|
||||
/**
|
||||
* @brief Get the log buffer object.
|
||||
*
|
||||
* @param[in] log_upload_buffer_handle @see LogUploadBuffer
|
||||
* @return log buffer
|
||||
*/
|
||||
char *log_upload_buffer_get(void *log_upload_buffer_handle);
|
||||
|
||||
/**
|
||||
* @brief Push data to log buffer.
|
||||
*
|
||||
* @param[in] log_content need push data
|
||||
* @return if 0 success else QCLOUD_ERR_FAILURE
|
||||
*/
|
||||
int log_upload_buffer_append(const char *log_content);
|
||||
|
||||
/**
|
||||
* @brief Post data to tencent cloud over http.
|
||||
*
|
||||
* @param[in] post_buf need post data buffer
|
||||
* @param[in] post_size need post data buffer length
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int post_one_http_to_server(void *log_upload_buffer_handle, char *post_buf, size_t post_size);
|
||||
|
||||
/**
|
||||
* @brief Set log upload in comm err state.
|
||||
*
|
||||
* @param state true or false
|
||||
*/
|
||||
void log_upload_set_upload_log_in_comm_err(bool state);
|
||||
|
||||
/**
|
||||
* @brief Change log upload level.
|
||||
*
|
||||
* @param[in] level @see LogLevel
|
||||
*/
|
||||
void log_upload_set_log_upload_level(LogLevel level);
|
||||
|
||||
/**
|
||||
* @brief Get log upload level.
|
||||
*
|
||||
* @return @see LogLevel
|
||||
*/
|
||||
LogLevel log_upload_get_log_upload_level(void);
|
||||
|
||||
/**
|
||||
* @brief Init log upload module.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int log_mqtt_init(void *client);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_LOG_UPLOAD_INC_LOG_UPLOAD_H_
|
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file broadcast_sample.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-07-18
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "qcloud_iot_common.h"
|
||||
|
||||
#include "utils_log.h"
|
||||
|
||||
/**
|
||||
* @brief MQTT event callback, @see MQTTEventHandleFun
|
||||
*
|
||||
* @param[in] client pointer to mqtt client
|
||||
* @param[in] handle_context context
|
||||
* @param[in] msg msg
|
||||
*/
|
||||
static void _mqtt_event_handler(void *client, void *handle_context, MQTTEventMsg *msg)
|
||||
{
|
||||
MQTTMessage *mqtt_message = (MQTTMessage *)msg->msg;
|
||||
uintptr_t packet_id = (uintptr_t)msg->msg;
|
||||
|
||||
switch (msg->event_type) {
|
||||
case MQTT_EVENT_UNDEF:
|
||||
Log_i("undefined event occur.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_DISCONNECT:
|
||||
Log_i("MQTT disconnect.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_RECONNECT:
|
||||
Log_i("MQTT reconnect.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_RECEIVED:
|
||||
Log_i("topic message arrived but without any related handle: topic=%.*s, topic_msg=%.*s",
|
||||
mqtt_message->topic_len, STRING_PTR_PRINT_SANITY_CHECK(mqtt_message->topic_name),
|
||||
mqtt_message->payload_len, STRING_PTR_PRINT_SANITY_CHECK((char *)mqtt_message->payload));
|
||||
break;
|
||||
case MQTT_EVENT_SUBSCRIBE_SUCCESS:
|
||||
Log_i("subscribe success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBE_TIMEOUT:
|
||||
Log_i("subscribe wait ack timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBE_NACK:
|
||||
Log_i("subscribe nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_SUCCESS:
|
||||
Log_i("unsubscribe success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_TIMEOUT:
|
||||
Log_i("unsubscribe timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_NACK:
|
||||
Log_i("unsubscribe nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_SUCCESS:
|
||||
Log_i("publish success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_TIMEOUT:
|
||||
Log_i("publish timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_NACK:
|
||||
Log_i("publish nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
default:
|
||||
Log_i("Should NOT arrive here.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Setup MQTT construct parameters.
|
||||
*
|
||||
* @param[in,out] initParams @see MQTTInitParams
|
||||
* @param[in] device_info @see DeviceInfo
|
||||
*/
|
||||
static void _setup_connect_init_params(MQTTInitParams *init_params, DeviceInfo *device_info)
|
||||
{
|
||||
init_params->device_info = device_info;
|
||||
init_params->event_handle.h_fp = _mqtt_event_handler;
|
||||
}
|
||||
|
||||
static void _setup_log_upload_init_params(LogUploadInitParams *init_params, DeviceInfo *device_info)
|
||||
{
|
||||
init_params->device_name = device_info->device_name;
|
||||
init_params->product_id = device_info->product_id;
|
||||
init_params->sign_key = device_info->device_secret;
|
||||
|
||||
init_params->log_buffer_size = LOG_UPLOAD_BUFFER_SIZE;
|
||||
init_params->save_log_filename = "./tmp/upload-fail-save.log";
|
||||
init_params->read_func = HAL_File_Read;
|
||||
init_params->save_func = HAL_File_Write;
|
||||
init_params->del_func = HAL_File_Del;
|
||||
init_params->get_size_func = HAL_File_GetSize;
|
||||
}
|
||||
// ----------------------------------------------------------------------------
|
||||
// Main
|
||||
// ----------------------------------------------------------------------------
|
||||
static int sg_main_exit = 0;
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void _main_exit(int sig)
|
||||
{
|
||||
Log_e("demo exit by signal:%d\n", sig);
|
||||
sg_main_exit = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
#ifdef __linux__
|
||||
signal(SIGINT, _main_exit);
|
||||
#endif
|
||||
|
||||
int rc;
|
||||
int loop_cnt = 5;
|
||||
// init log level
|
||||
LogHandleFunc func = DEFAULT_LOG_HANDLE_FUNCS;
|
||||
func.log_upload = IOT_Log_Upload_AppendToUploadBuffer;
|
||||
|
||||
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
|
||||
|
||||
DeviceInfo device_info;
|
||||
|
||||
rc = HAL_GetDevInfo((void *)&device_info);
|
||||
if (rc) {
|
||||
Log_e("get device info failed: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// init connection
|
||||
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
|
||||
_setup_connect_init_params(&init_params, &device_info);
|
||||
// init log upload
|
||||
LogUploadInitParams log_upload_init_params = DEFAULT_LOG_UPLOAD_INIT_PARAMS;
|
||||
|
||||
_setup_log_upload_init_params(&log_upload_init_params, &device_info);
|
||||
|
||||
IOT_Log_Upload_InitPre(&log_upload_init_params);
|
||||
// create MQTT client and connect with server
|
||||
void *client = IOT_MQTT_Construct(&init_params);
|
||||
if (client) {
|
||||
Log_i("Cloud Device Construct Success");
|
||||
rc = IOT_Log_Upload_Init(client);
|
||||
if (rc) {
|
||||
Log_e("Log upload init failed: %d", rc);
|
||||
}
|
||||
IOT_Log_Upload(true);
|
||||
} else {
|
||||
Log_e("MQTT Construct failed!");
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
do {
|
||||
Log_d("log upload test debug %d...", loop_cnt);
|
||||
Log_i("log upload test info %d...", loop_cnt);
|
||||
Log_w("log upload test waring %d...", loop_cnt);
|
||||
Log_e("log upload test error %d...", loop_cnt);
|
||||
rc = IOT_MQTT_Yield(client, 200);
|
||||
IOT_Log_Upload(rc ? true : false);
|
||||
} while (loop_cnt--);
|
||||
// If you have to report some urgent messages, you can call this function
|
||||
IOT_Log_Upload(true);
|
||||
rc = IOT_MQTT_Destroy(&client);
|
||||
IOT_Log_Upload_Deinit();
|
||||
utils_log_deinit();
|
||||
return rc;
|
||||
}
|
@@ -0,0 +1,735 @@
|
||||
/**
|
||||
* @file log_buffer.c
|
||||
* @author {hubert} ({hubertxxu@tencent.com})
|
||||
* @brief
|
||||
* @version 1.0
|
||||
* @date 2022-01-23
|
||||
*
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* Date Version Author Description
|
||||
* 2022-01-23 1.0 hubertxxu first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "log_upload.h"
|
||||
#include "utils_hmac.h"
|
||||
|
||||
typedef struct {
|
||||
void *lock_buf;
|
||||
char *log_buffer;
|
||||
const char *host;
|
||||
uint32_t log_buffer_size;
|
||||
uint32_t log_buffer_head_len;
|
||||
uint32_t write_index;
|
||||
char sign_key[SIGN_KEY_SIZE + 1];
|
||||
} LogUploadBuffer;
|
||||
|
||||
static LogUploadBuffer *sg_log_upload_buffer_handle = NULL;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Common
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Reset log upload buffer.
|
||||
*
|
||||
* @param[in,out] log_upload_buffer_handle handle of log upload buffer
|
||||
*/
|
||||
static void _reset_log_buffer(LogUploadBuffer *log_upload_buffer_handle)
|
||||
{
|
||||
log_upload_buffer_handle->write_index = log_upload_buffer_handle->log_buffer_head_len;
|
||||
memset(log_upload_buffer_handle->log_buffer + log_upload_buffer_handle->log_buffer_head_len, 0,
|
||||
log_upload_buffer_handle->log_buffer_size - log_upload_buffer_handle->log_buffer_head_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init log buffer common logic.
|
||||
*
|
||||
* @param[in] init_params @see LogUploadInitParams
|
||||
* @return pointer to log buffer
|
||||
*/
|
||||
static LogUploadBuffer *_log_upload_buffer_init_pre(const LogUploadInitParams *init_params)
|
||||
{
|
||||
LogUploadBuffer *handle = (LogUploadBuffer *)HAL_Malloc(sizeof(LogUploadBuffer));
|
||||
if (!handle) {
|
||||
UPLOAD_ERR("log upload buffer handle malloc error.");
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
memset(handle, 0, sizeof(LogUploadBuffer));
|
||||
|
||||
handle->lock_buf = HAL_MutexCreate();
|
||||
if (!handle->lock_buf) {
|
||||
UPLOAD_ERR("mutex create failed");
|
||||
goto err_exit;
|
||||
}
|
||||
handle->log_buffer_size = init_params->log_buffer_size;
|
||||
handle->log_buffer = HAL_Malloc(handle->log_buffer_size);
|
||||
if (!handle->log_buffer) {
|
||||
UPLOAD_ERR("malloc log buffer failed");
|
||||
goto err_exit;
|
||||
}
|
||||
memset(handle->log_buffer, 0, handle->log_buffer_size);
|
||||
|
||||
/*init sign key*/
|
||||
int key_len = strlen(init_params->sign_key);
|
||||
if (!key_len) {
|
||||
UPLOAD_ERR("invalid sign key length");
|
||||
goto err_exit;
|
||||
}
|
||||
memcpy(handle->sign_key, init_params->sign_key, key_len > SIGN_KEY_SIZE ? SIGN_KEY_SIZE : key_len);
|
||||
|
||||
sg_log_upload_buffer_handle = handle;
|
||||
return handle;
|
||||
err_exit:
|
||||
if (handle) {
|
||||
HAL_MutexDestroy(handle->lock_buf);
|
||||
HAL_Free(handle);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deinit log buffer handle.
|
||||
*
|
||||
* @param[in] upload_buffer_handle @see LogUploadBuffer
|
||||
*/
|
||||
void log_upload_buffer_deinit(void *upload_buffer_handle)
|
||||
{
|
||||
LogUploadBuffer *handle = (LogUploadBuffer *)upload_buffer_handle;
|
||||
if (!handle) {
|
||||
return;
|
||||
}
|
||||
HAL_MutexLock(handle->lock_buf);
|
||||
_reset_log_buffer(handle);
|
||||
HAL_Free(handle->log_buffer);
|
||||
handle->log_buffer = NULL;
|
||||
HAL_MutexUnlock(handle->lock_buf);
|
||||
HAL_MutexDestroy(handle->lock_buf);
|
||||
handle->lock_buf = NULL;
|
||||
HAL_Free(handle);
|
||||
sg_log_upload_buffer_handle = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the log buffer write index object.
|
||||
*
|
||||
* @param[in] log_upload_buffer_handle @see LogUploadBuffer
|
||||
* @return write index
|
||||
*/
|
||||
uint32_t log_upload_buffer_get_write_index(void *log_upload_buffer_handle)
|
||||
{
|
||||
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
|
||||
HAL_MutexLock(handle->lock_buf);
|
||||
uint32_t write_index = handle->write_index;
|
||||
HAL_MutexUnlock(handle->lock_buf);
|
||||
return write_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the log buffer object.
|
||||
*
|
||||
* @param[in] log_upload_buffer_handle @see LogUploadBuffer
|
||||
* @return log buffer
|
||||
*/
|
||||
char *log_upload_buffer_get(void *log_upload_buffer_handle)
|
||||
{
|
||||
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
|
||||
return handle->log_buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the log buffer head len object.
|
||||
*
|
||||
* @param[in] log_upload_buffer_handle @see LogUploadBuffer
|
||||
* @return head length
|
||||
*/
|
||||
uint32_t log_upload_buffer_get_head_len(void *log_upload_buffer_handle)
|
||||
{
|
||||
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
|
||||
return handle->log_buffer_head_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check log buffer is empty or not.
|
||||
*
|
||||
* @param[in] log_upload_buffer_handle @see LogUploadBuffer
|
||||
* @return true empty
|
||||
* @return false not empty
|
||||
*/
|
||||
bool log_upload_buffer_is_empty(void *log_upload_buffer_handle)
|
||||
{
|
||||
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
|
||||
return handle->write_index == handle->log_buffer_head_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief When a new log appears during the reporting period, move the log to the specified position in the buffer.
|
||||
*
|
||||
* @param[in] log_upload_buffer_handle @see LogUploadBuffer
|
||||
* @param[in] need_remove_size
|
||||
*/
|
||||
void log_upload_buffer_remove_log(void *log_upload_buffer_handle, uint32_t need_remove_size)
|
||||
{
|
||||
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
|
||||
if (need_remove_size > handle->write_index || need_remove_size > handle->log_buffer_size) {
|
||||
return;
|
||||
}
|
||||
HAL_MutexLock(handle->lock_buf);
|
||||
memmove(handle->log_buffer + handle->log_buffer_head_len, handle->log_buffer + need_remove_size,
|
||||
handle->write_index - need_remove_size);
|
||||
handle->write_index = handle->write_index - need_remove_size + handle->log_buffer_head_len;
|
||||
memset(handle->log_buffer + handle->write_index, 0, handle->log_buffer_size - handle->write_index);
|
||||
HAL_MutexUnlock(handle->lock_buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy log buffer head.
|
||||
*
|
||||
* @param[out] dst Destination address
|
||||
* @param[in] log_upload_buffer_handle @see LogUploadBuffer
|
||||
*/
|
||||
void log_upload_buffer_copy_header(char *dst, void *log_upload_buffer_handle)
|
||||
{
|
||||
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
|
||||
memcpy(dst, handle->log_buffer, handle->log_buffer_head_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear log upload buffer.
|
||||
*
|
||||
*/
|
||||
void log_upload_buffer_clear(void)
|
||||
{
|
||||
if (!sg_log_upload_buffer_handle) {
|
||||
UPLOAD_ERR("log upload buffer not init.");
|
||||
return;
|
||||
}
|
||||
HAL_MutexLock(sg_log_upload_buffer_handle->lock_buf);
|
||||
_reset_log_buffer(sg_log_upload_buffer_handle);
|
||||
HAL_MutexUnlock(sg_log_upload_buffer_handle->lock_buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the log buffer size object.
|
||||
*
|
||||
* @return log buffer size
|
||||
*/
|
||||
uint32_t log_upload_buffer_get_size(void)
|
||||
{
|
||||
if (!sg_log_upload_buffer_handle) {
|
||||
UPLOAD_ERR("log upload buffer not init.");
|
||||
return 0;
|
||||
}
|
||||
return sg_log_upload_buffer_handle->log_buffer_size;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// New log fromat by json
|
||||
// ----------------------------------------------------------------------------
|
||||
#ifdef LOG_UPLOAD_TYPE_JSON
|
||||
/**
|
||||
* @brief Log upload buffer init.
|
||||
*
|
||||
* @param[in] init_params @see LogUploadInitParams
|
||||
* @return void* if success return @see LogUploadBuffer
|
||||
*/
|
||||
void *log_upload_buffer_init(const LogUploadInitParams *init_params)
|
||||
{
|
||||
LogUploadBuffer *handle = _log_upload_buffer_init_pre(init_params);
|
||||
if (!handle) {
|
||||
return NULL;
|
||||
}
|
||||
handle->log_buffer_head_len = HAL_Snprintf(handle->log_buffer, handle->log_buffer_size,
|
||||
"{\"DeviceName\":\"%s\",\"ProductId\":\"%s\",\"Message\":[",
|
||||
init_params->device_name, init_params->product_id);
|
||||
handle->write_index = handle->log_buffer_head_len;
|
||||
handle->host = init_params->host;
|
||||
_reset_log_buffer(handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
#ifdef LOG_CHECK_HTTP_RET_CODE
|
||||
/**
|
||||
* @brief Get http respose result.
|
||||
*
|
||||
* @param[in] json http response buffer
|
||||
* @param[in] json_len http response buffer length
|
||||
* @return true log report success
|
||||
* @return false log report error
|
||||
*/
|
||||
static bool _get_json_ret_code(char *json, size_t json_len)
|
||||
{
|
||||
int rc = 0;
|
||||
UtilsJsonValue resposse;
|
||||
// get response
|
||||
rc = utils_json_value_get("Response.Error", strlen("Response.Error"), json, json_len, &resposse);
|
||||
if (rc) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief When upload, append endchar in the upload buffer ending.
|
||||
*
|
||||
* @param[in,out] log_buf upload buffer
|
||||
* @param[in] log_size upload buffer index
|
||||
*/
|
||||
static void _append_endchar_to_upload_buffer(char *log_buf, size_t log_size)
|
||||
{
|
||||
log_buf[log_size - 2] = ']';
|
||||
log_buf[log_size - 1] = '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief When upload, remove endchar in the upload buffer ending.
|
||||
*
|
||||
* @param[in,out] log_buf upload buffer
|
||||
* @param[in] log_size upload buffer index
|
||||
*/
|
||||
static void _remove_endchar_from_upload_buffer(char *log_buf, size_t log_size)
|
||||
{
|
||||
log_buf[log_size - 2] = ',';
|
||||
log_buf[log_size - 1] = ' ';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Post data to tencent cloud over http.
|
||||
*
|
||||
* @param[in] post_buf need post data buffer
|
||||
* @param[in] post_size need post data buffer length
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int post_one_http_to_server(void *log_upload_buffer_handle, char *post_buf, size_t post_size)
|
||||
{
|
||||
int rc = 0;
|
||||
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
|
||||
if (!post_size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
_append_endchar_to_upload_buffer(post_buf, post_size);
|
||||
|
||||
HttpSignedParams params = {
|
||||
.host = handle->host ? handle->host : LOG_UPLOAD_SERVER_URL,
|
||||
.uri = LOG_UPLOAD_URI_PATH,
|
||||
.secretkey = handle->sign_key,
|
||||
#ifdef LOG_CHECK_HTTP_RET_CODE
|
||||
.need_recv = true,
|
||||
.recv_timeout_ms = HTTP_WAIT_RET_TIMEOUT_MS,
|
||||
#else
|
||||
.need_recv = false,
|
||||
.recv_timeout_ms = 0,
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef LOG_CHECK_HTTP_RET_CODE
|
||||
char buf[HTTP_RET_JSON_LENGTH] = {0};
|
||||
rc = IOT_HTTP_SignedRequest(¶ms, post_buf, post_size, (uint8_t *)buf, HTTP_RET_JSON_LENGTH);
|
||||
if (rc < 0) {
|
||||
UPLOAD_ERR("IOT_HTTP_SignedRequest failed, rc = %d", rc);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (rc == 0) {
|
||||
UPLOAD_ERR("HTTP Recv length = %d", rc);
|
||||
rc = QCLOUD_ERR_HTTP;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
buf[rc - 1] = '\0'; // json_parse relies on a string
|
||||
if (strlen(buf) > 0 && _get_json_ret_code(buf, rc)) {
|
||||
UPLOAD_DBG("Log server return SUCCESS: %s", buf);
|
||||
rc = QCLOUD_RET_SUCCESS;
|
||||
} else {
|
||||
UPLOAD_ERR("Log server return FAIL: %s", buf);
|
||||
rc = QCLOUD_ERR_HTTP;
|
||||
}
|
||||
|
||||
#else
|
||||
rc = IOT_HTTP_SignedRequest(¶ms, post_buf, post_size, NULL, 0);
|
||||
#endif
|
||||
|
||||
exit:
|
||||
_remove_endchar_from_upload_buffer(post_buf, post_size);
|
||||
UPLOAD_DBG("%ld data have be post to server. rc : %d", post_size, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Push data to log buffer.
|
||||
*
|
||||
* @param[in] log_content need push data
|
||||
* @return if 0 success else QCLOUD_ERR_FAILURE
|
||||
*/
|
||||
int log_upload_buffer_append(const char *log_content)
|
||||
{
|
||||
uint16_t log_size = strlen(log_content);
|
||||
if (HAL_MutexTryLock(sg_log_upload_buffer_handle->lock_buf) != 0) {
|
||||
UPLOAD_ERR("trylock buffer failed!");
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
if ((sg_log_upload_buffer_handle->write_index + log_size + 5) > sg_log_upload_buffer_handle->log_buffer_size) {
|
||||
HAL_MutexUnlock(sg_log_upload_buffer_handle->lock_buf);
|
||||
UPLOAD_ERR("log upload buffer is not enough!");
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\"';
|
||||
int index = 0;
|
||||
while (index < (log_size - 2)) {
|
||||
// replace may overflow buffer size
|
||||
if ((sg_log_upload_buffer_handle->write_index + 5) > sg_log_upload_buffer_handle->log_buffer_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (log_content[index]) {
|
||||
case '\"':
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\"';
|
||||
break;
|
||||
case '\\':
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
|
||||
break;
|
||||
case '/':
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '/';
|
||||
break;
|
||||
case '\b':
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = 'b';
|
||||
break;
|
||||
case '\f':
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = 'f';
|
||||
break;
|
||||
case '\n':
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = 'n';
|
||||
break;
|
||||
case '\r':
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = 'r';
|
||||
break;
|
||||
case '\t':
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = 't';
|
||||
break;
|
||||
default:
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] =
|
||||
log_content[index];
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
/* replace \r\n to \", add as delimiter */
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\"';
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = ',';
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = ' ';
|
||||
|
||||
HAL_MutexUnlock(sg_log_upload_buffer_handle->lock_buf);
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
#else
|
||||
// ----------------------------------------------------------------------------
|
||||
// Old log format by text
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/* log post header format */
|
||||
#define TIMESTAMP_SIZE (10)
|
||||
#define SIGNATURE_SIZE (40)
|
||||
#define CTRL_BYTES_SIZE (4)
|
||||
// LOG_BUF_FIXED_HEADER_SIZE = 112
|
||||
#define LOG_BUF_FIXED_HEADER_SIZE \
|
||||
(SIGNATURE_SIZE + CTRL_BYTES_SIZE + MAX_SIZE_OF_PRODUCT_ID + MAX_SIZE_OF_DEVICE_NAME + TIMESTAMP_SIZE)
|
||||
|
||||
#ifdef LOG_UPLOAD_AES_ENCRYPT_POST
|
||||
#include "mbedtls/aes.h"
|
||||
|
||||
/**
|
||||
* @brief Header of encrypt.
|
||||
*
|
||||
*/
|
||||
static char sg_head_content[128] = {0};
|
||||
|
||||
/**
|
||||
* @brief Log data encrypt by aes cbc.
|
||||
*
|
||||
* @param[in] handle @see LogUploadBuffer
|
||||
* @param[in] buf need encrypt data
|
||||
* @param[in] datalen need encrypt data length
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _log_aes_cbc_encrypt(LogUploadBuffer *handle, uint8_t *buf, uint32_t datalen)
|
||||
{
|
||||
int ret = QCLOUD_RET_SUCCESS;
|
||||
|
||||
mbedtls_aes_context ctx;
|
||||
|
||||
unsigned char iv[16];
|
||||
memset(iv, '0', 16);
|
||||
|
||||
mbedtls_aes_init(&ctx);
|
||||
|
||||
ret = mbedtls_aes_setkey_enc(&ctx, (uint8_t *)handle->sign_key, 128);
|
||||
if (ret) {
|
||||
UPLOAD_ERR("Set encry key err,ret:%d", ret);
|
||||
ret = QCLOUD_ERR_FAILURE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ret = mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_ENCRYPT, datalen, iv, buf, buf);
|
||||
if (ret) {
|
||||
Log_e("encrypt err,ret:%d", ret);
|
||||
ret = QCLOUD_ERR_FAILURE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
mbedtls_aes_free(&ctx);
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
|
||||
exit:
|
||||
mbedtls_aes_free(&ctx);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Log upload buffer init.
|
||||
*
|
||||
* @param[in] init_params @see LogUploadInitParams
|
||||
* @return void* if success return @see LogUploadBuffer
|
||||
*/
|
||||
void *log_upload_buffer_init(const LogUploadInitParams *init_params)
|
||||
{
|
||||
LogUploadBuffer *handle = _log_upload_buffer_init_pre(init_params);
|
||||
if (!handle) {
|
||||
return NULL;
|
||||
}
|
||||
handle->log_buffer_head_len = LOG_BUF_FIXED_HEADER_SIZE;
|
||||
memset(handle->log_buffer, '#', handle->log_buffer_head_len);
|
||||
handle->log_buffer[SIGNATURE_SIZE] = 'P';
|
||||
memcpy(handle->log_buffer + SIGNATURE_SIZE + CTRL_BYTES_SIZE, init_params->product_id, MAX_SIZE_OF_PRODUCT_ID);
|
||||
memcpy(handle->log_buffer + SIGNATURE_SIZE + CTRL_BYTES_SIZE + MAX_SIZE_OF_PRODUCT_ID, init_params->device_name,
|
||||
strlen(init_params->device_name));
|
||||
handle->write_index = handle->log_buffer_head_len;
|
||||
handle->host = init_params->host;
|
||||
_reset_log_buffer(handle);
|
||||
|
||||
#ifdef LOG_UPLOAD_AES_ENCRYPT_POST
|
||||
HAL_Snprintf(sg_head_content, 128,
|
||||
"Accept:application/json;*/*\r\n"
|
||||
"X-TC-IotEncryType:%c\r\n"
|
||||
"X-TC-IotCid:%s%s\r\n",
|
||||
handle->log_buffer[SIGNATURE_SIZE], init_params->product_id, init_params->device_name);
|
||||
#endif
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
#ifdef LOG_CHECK_HTTP_RET_CODE
|
||||
/**
|
||||
* @brief Get http respose result.
|
||||
*
|
||||
* @param[in] json http response buffer
|
||||
* @param[in] json_len http response buffer length
|
||||
* @return true log report success
|
||||
* @return false log report error
|
||||
*/
|
||||
static bool _get_json_ret_code(char *json, size_t json_len)
|
||||
{
|
||||
int rc = 0;
|
||||
uint32_t ret_code;
|
||||
rc = utils_json_get_uint32("Retcode", strlen("Retcode"), json, json_len, &ret_code);
|
||||
if (!rc) {
|
||||
if (!ret_code) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Update timestamp add signature post buffer.
|
||||
*
|
||||
* @param[in] handle @see LogUploadBuffer
|
||||
* @param[in] post_buf need post data buffer
|
||||
* @param[in] post_size need post data buffer length
|
||||
*/
|
||||
static void _update_time_and_signature(LogUploadBuffer *handle, char *post_buf, size_t post_size)
|
||||
{
|
||||
char timestamp[TIMESTAMP_SIZE + 1] = {0};
|
||||
char signature[SIGNATURE_SIZE + 1] = {0};
|
||||
uint32_t now_time = IOT_Timer_CurrentSec();
|
||||
|
||||
/* record the timestamp for this log uploading */
|
||||
HAL_Snprintf(timestamp, TIMESTAMP_SIZE + 1, "%010d", now_time);
|
||||
memcpy(post_buf + sg_log_upload_buffer_handle->log_buffer_head_len - TIMESTAMP_SIZE, timestamp, strlen(timestamp));
|
||||
|
||||
/* signature of this log uploading */
|
||||
utils_hmac_sha1_hex(post_buf + SIGNATURE_SIZE, post_size - SIGNATURE_SIZE, (uint8_t *)handle->sign_key,
|
||||
strlen(handle->sign_key), signature);
|
||||
memcpy(post_buf, signature, SIGNATURE_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Post data to tencent cloud over http.
|
||||
*
|
||||
* @param[in] post_buf need post data buffer
|
||||
* @param[in] post_size need post data buffer length
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int post_one_http_to_server(void *log_upload_buffer_handle, char *post_buf, size_t post_size)
|
||||
{
|
||||
int rc = 0;
|
||||
#ifdef LOG_UPLOAD_AES_ENCRYPT_POST
|
||||
char *log_encrypt_buffer = NULL;
|
||||
#endif
|
||||
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
|
||||
_update_time_and_signature(handle, post_buf, post_size);
|
||||
void *http_client = IOT_HTTP_Init();
|
||||
if (!http_client) {
|
||||
UPLOAD_ERR("http client init error");
|
||||
goto exit;
|
||||
}
|
||||
IotHTTPConnectParams connect_params = {
|
||||
.url = handle->host ? handle->host : LOG_UPLOAD_OLD_SERVER_URL,
|
||||
.port = LOG_UPLOAD_OLD_SERVER_PORT,
|
||||
.ca_crt = NULL, // TODO: support cert
|
||||
};
|
||||
|
||||
rc = IOT_HTTP_Connect(http_client, &connect_params);
|
||||
if (rc) {
|
||||
UPLOAD_ERR("http connect error.");
|
||||
goto exit;
|
||||
}
|
||||
IotHTTPRequestParams request = {
|
||||
.url = handle->host ? handle->host : LOG_UPLOAD_OLD_SERVER_URL,
|
||||
.method = IOT_HTTP_METHOD_POST,
|
||||
.content_length = post_size,
|
||||
.content_type = "text/plain;charset=utf-8",
|
||||
.content = post_buf,
|
||||
.header = "Accept:application/json;*/*\r\n",
|
||||
};
|
||||
|
||||
#ifdef LOG_UPLOAD_AES_ENCRYPT_POST
|
||||
/*ANSI X.923: zero-padding with last byte as padlen */
|
||||
int padlen = 16 - post_size % 16;
|
||||
int datalen = post_size + padlen;
|
||||
log_encrypt_buffer = HAL_Malloc(datalen + 1);
|
||||
if (!log_encrypt_buffer) {
|
||||
UPLOAD_ERR("malloc log encrypt buffer error. malloc length : %d", datalen);
|
||||
rc = QCLOUD_ERR_MALLOC;
|
||||
goto exit;
|
||||
}
|
||||
memset(log_encrypt_buffer, 0, datalen + 1);
|
||||
memcpy(log_encrypt_buffer, post_buf, post_size);
|
||||
log_encrypt_buffer[datalen - 1] = padlen;
|
||||
|
||||
// aes cbc
|
||||
rc = _log_aes_cbc_encrypt(handle, (uint8_t *)log_encrypt_buffer, datalen);
|
||||
if (QCLOUD_RET_SUCCESS != rc) {
|
||||
UPLOAD_ERR("log encrypt failed, ret = %d", rc);
|
||||
goto exit;
|
||||
}
|
||||
request.header = sg_head_content;
|
||||
request.content_type = "application/octet-stream";
|
||||
request.content_length = datalen;
|
||||
request.content = log_encrypt_buffer;
|
||||
#endif
|
||||
|
||||
rc = IOT_HTTP_Request(http_client, &request);
|
||||
// 2. check rc
|
||||
if (rc) {
|
||||
UPLOAD_ERR("http post error.");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
#ifdef LOG_CHECK_HTTP_RET_CODE
|
||||
char buf[HTTP_RET_JSON_LENGTH] = {0};
|
||||
rc = IOT_HTTP_Recv(http_client, (uint8_t *)buf, HTTP_RET_JSON_LENGTH, HTTP_WAIT_RET_TIMEOUT_MS);
|
||||
if (rc < 0) {
|
||||
UPLOAD_ERR("qcloud_http_recv_data failed, rc = %d", rc);
|
||||
} else if (rc == 0) {
|
||||
UPLOAD_ERR("HTTP Recv length == %d", rc);
|
||||
rc = QCLOUD_ERR_HTTP_TIMEOUT;
|
||||
} else {
|
||||
buf[rc - 1] = '\0'; // json_parse relies on a string
|
||||
if (strlen(buf) > 0 && _get_json_ret_code(buf, rc)) {
|
||||
UPLOAD_DBG("Log server return SUCCESS: %s", buf);
|
||||
rc = QCLOUD_RET_SUCCESS;
|
||||
} else {
|
||||
UPLOAD_ERR("Log server return FAIL: %s", buf);
|
||||
rc = QCLOUD_ERR_HTTP;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
exit:
|
||||
if (http_client) {
|
||||
IOT_HTTP_Disconnect(http_client);
|
||||
IOT_HTTP_Deinit(http_client);
|
||||
}
|
||||
#ifdef LOG_UPLOAD_AES_ENCRYPT_POST
|
||||
if (log_encrypt_buffer) {
|
||||
HAL_Free(log_encrypt_buffer);
|
||||
log_encrypt_buffer = NULL;
|
||||
}
|
||||
#endif
|
||||
UPLOAD_DBG("%ld data have be post to server. rc : %d", post_size, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Push data to log buffer.
|
||||
*
|
||||
* @param[in] log_content need push data
|
||||
* @return if 0 success else QCLOUD_ERR_FAILURE
|
||||
*/
|
||||
int log_upload_buffer_append(const char *log_content)
|
||||
{
|
||||
uint16_t log_size = strlen(log_content);
|
||||
if (HAL_MutexTryLock(sg_log_upload_buffer_handle->lock_buf) != 0) {
|
||||
UPLOAD_ERR("trylock buffer failed!");
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
if ((sg_log_upload_buffer_handle->write_index + log_size + 1) > sg_log_upload_buffer_handle->log_buffer_size) {
|
||||
HAL_MutexUnlock(sg_log_upload_buffer_handle->lock_buf);
|
||||
UPLOAD_DBG("log upload buffer is not enough! remain : %d, need upload length: %d",
|
||||
sg_log_upload_buffer_handle->log_buffer_size - sg_log_upload_buffer_handle->write_index, log_size);
|
||||
return QCLOUD_ERR_BUF_TOO_SHORT;
|
||||
}
|
||||
|
||||
memcpy(sg_log_upload_buffer_handle->log_buffer + sg_log_upload_buffer_handle->write_index, log_content, log_size);
|
||||
sg_log_upload_buffer_handle->write_index += log_size;
|
||||
|
||||
/* replace \r\n to \n\f as delimiter */
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index - 1] = '\f';
|
||||
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index - 2] = '\n';
|
||||
|
||||
HAL_MutexUnlock(sg_log_upload_buffer_handle->lock_buf);
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
#endif
|
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file log_mqtt.c
|
||||
* @brief
|
||||
* @author hubertxxu (hubertxxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2022-01-05
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2022-01-05 <td>1.0 <td>hubertxxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "log_upload.h"
|
||||
|
||||
/**
|
||||
* @brief Parse log level.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] message message from topic
|
||||
* @param[in,out] usr_data pointer to @see LogLevelResultInfo
|
||||
*/
|
||||
static void _log_mqtt_message_callback(void *client, const MQTTMessage *message, void *usr_data)
|
||||
{
|
||||
int rc;
|
||||
uint32_t log_level;
|
||||
|
||||
Log_d("Receive log result message:%.*s", message->payload_len, message->payload_str);
|
||||
|
||||
// get log level
|
||||
rc = utils_json_get_uint32("log_level", sizeof("log_level") - 1, message->payload_str, message->payload_len,
|
||||
&log_level);
|
||||
if (rc) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (log_level) {
|
||||
case LOG_LEVEL_DISABLE:
|
||||
log_upload_buffer_clear();
|
||||
log_upload_set_upload_log_in_comm_err(true);
|
||||
log_upload_set_log_upload_level(LOG_LEVEL_ERROR);
|
||||
UPLOAD_DBG("Upload log level change to: %d", LOG_LEVEL_ERROR);
|
||||
break;
|
||||
case LOG_LEVEL_ERROR:
|
||||
case LOG_LEVEL_WARN:
|
||||
case LOG_LEVEL_INFO:
|
||||
case LOG_LEVEL_DEBUG:
|
||||
if (log_level < log_upload_get_log_upload_level()) {
|
||||
log_upload_buffer_clear();
|
||||
}
|
||||
log_upload_set_upload_log_in_comm_err(false);
|
||||
log_upload_set_log_upload_level((LogLevel)log_level);
|
||||
UPLOAD_DBG("Upload log level change to: %d", log_level);
|
||||
break;
|
||||
default:
|
||||
UPLOAD_ERR("Invalid log level: %d", log_level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check and subscribe log result topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _log_mqtt_result_topic_check_and_sub(void *client)
|
||||
{
|
||||
char log_upload_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
/* subscribe the log topic: "$log/operation/result/${productId}/${deviceName}" */
|
||||
HAL_Snprintf(log_upload_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$log/operation/result/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
|
||||
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
|
||||
sub_params.on_message_handler = _log_mqtt_message_callback;
|
||||
sub_params.qos = QOS1;
|
||||
return IOT_MQTT_SubscribeSync(client, log_upload_topic, &sub_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish get log level message to log topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _log_mqtt_get_log_level_publish(void *client)
|
||||
{
|
||||
static int sg_get_log_level_token_count = 0;
|
||||
char topic_name[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
char payload[128] = {0};
|
||||
|
||||
HAL_Snprintf(topic_name, sizeof(topic_name), "$log/operation/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
|
||||
HAL_Snprintf(payload, sizeof(payload), "{\"type\": \"get_log_level\",\"clientToken\": \"get_log_level-%u\"}",
|
||||
sg_get_log_level_token_count++);
|
||||
|
||||
PublishParams pub_params = DEFAULT_PUB_PARAMS;
|
||||
pub_params.qos = QOS0;
|
||||
pub_params.payload = payload;
|
||||
pub_params.payload_len = strlen(payload);
|
||||
return IOT_MQTT_Publish(client, topic_name, &pub_params) >= 0 ? QCLOUD_RET_SUCCESS : QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init log upload module.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int log_mqtt_init(void *client)
|
||||
{
|
||||
int rc = _log_mqtt_result_topic_check_and_sub(client);
|
||||
if (rc) {
|
||||
return rc;
|
||||
}
|
||||
return _log_mqtt_get_log_level_publish(client);
|
||||
}
|
@@ -0,0 +1,551 @@
|
||||
/**
|
||||
* @file log_upload.c
|
||||
* @author {hubert} ({hubertxxu@tencent.com})
|
||||
* @brief
|
||||
* @version 1.0
|
||||
* @date 2022-01-24
|
||||
*
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* Date Version Author Description
|
||||
* 2022-01-24 1.0 hubertxxu first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "log_upload.h"
|
||||
|
||||
#ifdef LOG_UPLOAD_TYPE_JSON
|
||||
#define LOG_DELIMITER "\", "
|
||||
#else
|
||||
#define LOG_DELIMITER "\n\f"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Data structure for log upload.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
bool upload_only_in_comm_err;
|
||||
LogLevel log_level;
|
||||
QcloudIotTimer upload_timer;
|
||||
void *upload_buffer_handle;
|
||||
|
||||
const char *save_log_filename;
|
||||
LogSaveFunc save_func;
|
||||
LogReadFunc read_func;
|
||||
LogDelFunc del_func;
|
||||
LogGetSizeFunc get_size_func;
|
||||
|
||||
bool log_save_enabled;
|
||||
bool log_client_init_done;
|
||||
} QcloudIoTLogUpload;
|
||||
|
||||
static QcloudIoTLogUpload *sg_log_uploader_client;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Internal API
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Get log upload client.
|
||||
*
|
||||
* @return @see QcloudIoTLogUpload
|
||||
*/
|
||||
static QcloudIoTLogUpload *_get_log_uploader_client(void)
|
||||
{
|
||||
if (!sg_log_uploader_client) {
|
||||
return NULL;
|
||||
}
|
||||
if (!sg_log_uploader_client->log_client_init_done) {
|
||||
return NULL;
|
||||
}
|
||||
return sg_log_uploader_client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set client.
|
||||
*
|
||||
* @param[in] client @see QcloudIoTLogUpload
|
||||
*/
|
||||
static void _set_log_uploader_client(void *client)
|
||||
{
|
||||
sg_log_uploader_client = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set log upload in comm err state.
|
||||
*
|
||||
* @param state true or false
|
||||
*/
|
||||
void log_upload_set_upload_log_in_comm_err(bool state)
|
||||
{
|
||||
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
|
||||
POINTER_SANITY_CHECK_RTN(log_uploader_handle);
|
||||
log_uploader_handle->upload_only_in_comm_err = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Change log upload level.
|
||||
*
|
||||
* @param[in] level @see LogLevel
|
||||
*/
|
||||
void log_upload_set_log_upload_level(LogLevel level)
|
||||
{
|
||||
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
|
||||
POINTER_SANITY_CHECK_RTN(log_uploader_handle);
|
||||
log_uploader_handle->log_level = level;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get log upload level.
|
||||
*
|
||||
* @return @see LogLevel
|
||||
*/
|
||||
LogLevel log_upload_get_log_upload_level(void)
|
||||
{
|
||||
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
|
||||
POINTER_SANITY_CHECK(log_uploader_handle, LOG_LEVEL_DISABLE);
|
||||
return log_uploader_handle->log_level;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if need force upload.
|
||||
*
|
||||
* @param[in] force_upload
|
||||
* @return true need force upload
|
||||
* @return false not needed force upload
|
||||
*/
|
||||
static bool _check_force_upload(bool force_upload)
|
||||
{
|
||||
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
|
||||
if (!log_uploader_handle) {
|
||||
return false;
|
||||
}
|
||||
/* 1: check if have data to upload */
|
||||
if (log_upload_buffer_is_empty(log_uploader_handle->upload_buffer_handle)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t write_index = log_upload_buffer_get_write_index(log_uploader_handle->upload_buffer_handle);
|
||||
bool is_low_buffer = (log_upload_buffer_get_size() - write_index) < LOG_LOW_BUFFER_THRESHOLD ? true : false;
|
||||
|
||||
/* 2. check if upload only in comm err */
|
||||
if (!force_upload && log_uploader_handle->upload_only_in_comm_err) {
|
||||
/* buffer is low but we couldn't upload now, reset buffer */
|
||||
if (is_low_buffer) {
|
||||
log_upload_buffer_clear();
|
||||
}
|
||||
IOT_Timer_CountdownMs(&log_uploader_handle->upload_timer, LOG_UPLOAD_INTERVAL_MS);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 3: force upload is true, handle it right now */
|
||||
if (force_upload) {
|
||||
return true;
|
||||
}
|
||||
/* 4 : check if buffer is low */
|
||||
if (is_low_buffer) {
|
||||
/* buffer is low, handle it right now */
|
||||
return true;
|
||||
}
|
||||
/*5: check if timeout, handle it right now*/
|
||||
return IOT_Timer_Expired(&log_uploader_handle->upload_timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Save log to file or flash.
|
||||
*
|
||||
* @param[in] log_buf need saved buffer
|
||||
* @param[in] log_size need saved buffer length
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _save_log(char *log_buf, size_t log_size)
|
||||
{
|
||||
int rc = 0;
|
||||
size_t write_size, current_size;
|
||||
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
|
||||
POINTER_SANITY_CHECK(log_uploader_handle, QCLOUD_ERR_INVAL);
|
||||
if (!log_size) {
|
||||
UPLOAD_ERR("nothing need to save. log_size : %ld", log_size);
|
||||
return -1;
|
||||
}
|
||||
|
||||
current_size = log_uploader_handle->get_size_func(log_uploader_handle->save_log_filename);
|
||||
|
||||
/* overwrite the previous saved log to avoid too many saved logs */
|
||||
if ((current_size + log_size) > MAX_LOG_SAVE_SIZE) {
|
||||
UPLOAD_ERR("overwrite the previous saved log. %ld", current_size);
|
||||
rc = log_uploader_handle->del_func(log_uploader_handle->save_log_filename);
|
||||
if (rc) {
|
||||
Log_e("fail to delete previous log");
|
||||
}
|
||||
current_size = 0;
|
||||
}
|
||||
UPLOAD_DBG("saved data [%ld]", log_size);
|
||||
write_size =
|
||||
log_uploader_handle->save_func(log_uploader_handle->save_log_filename, log_buf, log_size, current_size);
|
||||
if (write_size != log_size) {
|
||||
Log_e("fail to save log. RC %ld - log size %ld", write_size, log_size);
|
||||
}
|
||||
|
||||
return write_size != log_size ? -1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get at most message to upload.
|
||||
*
|
||||
* @param[in] post_buf post buffer
|
||||
* @return size of message to upload
|
||||
*/
|
||||
static uint32_t _buffer_get_at_most_message(const char *post_buf)
|
||||
{
|
||||
uint32_t possible_size = 0;
|
||||
char *next_log_buf = NULL;
|
||||
size_t delimiter_len = strlen(LOG_DELIMITER);
|
||||
while (possible_size < MAX_HTTP_LOG_POST_SIZE) {
|
||||
/* locate the delimiter */
|
||||
next_log_buf = strstr(post_buf + possible_size, LOG_DELIMITER);
|
||||
if (!next_log_buf) {
|
||||
return 0;
|
||||
}
|
||||
possible_size = (size_t)(next_log_buf - post_buf + delimiter_len);
|
||||
/* end of log */
|
||||
if (next_log_buf[delimiter_len] == 0 && possible_size < MAX_HTTP_LOG_POST_SIZE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return possible_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Report log buffer to the server.
|
||||
*
|
||||
* @param[in,out] log_uploader_handle @see QcloudIoTLogUpload
|
||||
* @param[in] post_buf post buffer
|
||||
* @param[in] post_size post size
|
||||
* @param[in] is_log_buffer log buffer or saved log
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _post_log_buffer_to_cloud(QcloudIoTLogUpload *log_uploader_handle, char *post_buf, size_t post_size,
|
||||
bool is_log_buffer)
|
||||
{
|
||||
int rc = 0;
|
||||
uint32_t log_buffer_head_len = log_upload_buffer_get_head_len(log_uploader_handle->upload_buffer_handle);
|
||||
|
||||
size_t orig_post_size = post_size;
|
||||
size_t post_payload, possible_size, actual_post_payload = 0;
|
||||
do {
|
||||
// 1. get message in buffer
|
||||
possible_size = _buffer_get_at_most_message(post_buf);
|
||||
if (!possible_size) {
|
||||
UPLOAD_ERR("Upload size should not be 0! Total sent: %ld. Left: %ld",
|
||||
actual_post_payload + log_buffer_head_len, post_size);
|
||||
if (is_log_buffer) {
|
||||
log_upload_buffer_clear();
|
||||
}
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
if (is_log_buffer && (possible_size > log_upload_buffer_get_size() || possible_size > orig_post_size ||
|
||||
possible_size <= log_buffer_head_len)) {
|
||||
log_upload_buffer_clear();
|
||||
UPLOAD_ERR("possible size (%ld) is too large.", possible_size);
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
// 2. post to server
|
||||
rc = post_one_http_to_server(log_uploader_handle->upload_buffer_handle, post_buf, possible_size);
|
||||
if (rc) {
|
||||
UPLOAD_ERR("Send log failed. Total sent: %ld. Left: %ld", actual_post_payload + log_buffer_head_len,
|
||||
post_size);
|
||||
// when send failed save log in file.
|
||||
if (log_uploader_handle->log_save_enabled && is_log_buffer) {
|
||||
_save_log(post_buf + log_buffer_head_len, possible_size - log_buffer_head_len);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. remove have be post message
|
||||
memmove(post_buf + log_buffer_head_len, post_buf + possible_size, post_size - possible_size);
|
||||
post_payload = possible_size - log_buffer_head_len;
|
||||
post_size -= post_payload;
|
||||
actual_post_payload += post_payload;
|
||||
memset(post_buf + post_size, 0, orig_post_size - post_size);
|
||||
UPLOAD_DBG("post log %d OK. Total sent: %d. Left: %d ", possible_size,
|
||||
actual_post_payload + log_buffer_head_len, post_size);
|
||||
} while (post_size > log_buffer_head_len);
|
||||
|
||||
if (is_log_buffer) {
|
||||
log_upload_buffer_remove_log(log_uploader_handle->upload_buffer_handle, orig_post_size);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle log to save flash or file.
|
||||
*
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _handle_saved_log(void)
|
||||
{
|
||||
int rc = QCLOUD_RET_SUCCESS;
|
||||
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
|
||||
if (!log_uploader_handle) {
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
size_t whole_log_size = log_uploader_handle->get_size_func(log_uploader_handle->save_log_filename);
|
||||
if (!whole_log_size) {
|
||||
return rc;
|
||||
}
|
||||
uint32_t log_buffer_head_len = log_upload_buffer_get_head_len(log_uploader_handle->upload_buffer_handle);
|
||||
size_t buf_size = whole_log_size + log_buffer_head_len + 1;
|
||||
char *log_buf = HAL_Malloc(buf_size);
|
||||
if (!log_buf) {
|
||||
UPLOAD_ERR("Malloc failed, size: %ld", buf_size);
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
/* read the whole log to buffer */
|
||||
size_t read_len = log_uploader_handle->read_func(log_uploader_handle->save_log_filename,
|
||||
log_buf + log_buffer_head_len, whole_log_size, 0);
|
||||
if (read_len != whole_log_size) {
|
||||
Log_e("fail to read whole saved log. Size: %ld - read: %ld", whole_log_size, read_len);
|
||||
rc = QCLOUD_ERR_FAILURE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* copy header from global log buffer */
|
||||
size_t upload_size = whole_log_size + log_buffer_head_len;
|
||||
log_upload_buffer_copy_header(log_buf, log_uploader_handle->upload_buffer_handle);
|
||||
log_buf[buf_size - 1] = 0;
|
||||
|
||||
/* post server */
|
||||
rc = _post_log_buffer_to_cloud(log_uploader_handle, log_buf, upload_size, false);
|
||||
if (rc == QCLOUD_RET_SUCCESS || rc == QCLOUD_ERR_INVAL || rc == QCLOUD_ERR_HTTP) {
|
||||
UPLOAD_DBG("handle saved log done! Size: %ld. ", whole_log_size);
|
||||
log_uploader_handle->del_func(log_uploader_handle->save_log_filename);
|
||||
}
|
||||
exit:
|
||||
HAL_Free(log_buf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Append need report log to log upload buffer.
|
||||
*
|
||||
* @param[in] log_level @see LogLevel
|
||||
* @param[in] log_content data of need to report
|
||||
*/
|
||||
static void _log_upload_append_to_log_buffer(LogLevel log_level, const char *log_content)
|
||||
{
|
||||
int rc = 0;
|
||||
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
|
||||
// POINTER_SANITY_CHECK_RTN(log_uploader_handle);
|
||||
if (!log_uploader_handle) {
|
||||
return;
|
||||
}
|
||||
// if not necessary. just give up upload.
|
||||
if (log_level > log_uploader_handle->log_level) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t log_size = strlen(log_content);
|
||||
if (!log_size) {
|
||||
UPLOAD_ERR("log_size is not 0!");
|
||||
return;
|
||||
}
|
||||
rc = log_upload_buffer_append(log_content);
|
||||
if (rc) {
|
||||
/* log buffer is full. upload data right now */
|
||||
IOT_Timer_CountdownMs(&log_uploader_handle->upload_timer, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Upload data to tencent cloud.
|
||||
*
|
||||
* @param[in] force_upload upload if necessary
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _do_log_upload(bool force_upload)
|
||||
{
|
||||
int rc;
|
||||
static bool sg_need_handle_saved_log = true;
|
||||
|
||||
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
|
||||
POINTER_SANITY_CHECK(log_uploader_handle, QCLOUD_ERR_INVAL);
|
||||
|
||||
/* step1: handle previously saved log */
|
||||
if (log_uploader_handle->log_save_enabled && sg_need_handle_saved_log) {
|
||||
rc = _handle_saved_log();
|
||||
if (!rc) {
|
||||
sg_need_handle_saved_log = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* step2: check if have data to upload */
|
||||
if (!_check_force_upload(force_upload)) {
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
// log_buffer_lock();
|
||||
|
||||
/* step3: post log buffer data to cloud */
|
||||
uint32_t upload_log_size = log_upload_buffer_get_write_index(log_uploader_handle->upload_buffer_handle);
|
||||
|
||||
rc = _post_log_buffer_to_cloud(
|
||||
log_uploader_handle, log_upload_buffer_get(log_uploader_handle->upload_buffer_handle), upload_log_size, true);
|
||||
if (rc) {
|
||||
sg_need_handle_saved_log = true;
|
||||
}
|
||||
|
||||
IOT_Timer_CountdownMs(&log_uploader_handle->upload_timer, LOG_UPLOAD_INTERVAL_MS);
|
||||
|
||||
// log_buffer_unlock();
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init log upload previously.
|
||||
*
|
||||
* @param[in] init_params @see LogUploadInitParams
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int log_upload_init_pre(const LogUploadInitParams *init_params)
|
||||
{
|
||||
if (_get_log_uploader_client()) {
|
||||
UPLOAD_DBG("log upload initialized.");
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
QcloudIoTLogUpload *log_uploader_handle = HAL_Malloc(sizeof(QcloudIoTLogUpload));
|
||||
if (!log_uploader_handle) {
|
||||
UPLOAD_ERR("allocate for log client failed");
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
log_uploader_handle->upload_only_in_comm_err = false;
|
||||
|
||||
/* all the call back functions are necessary to handle log save and re-upload*/
|
||||
if (init_params->save_func && init_params->read_func && init_params->del_func && init_params->get_size_func &&
|
||||
init_params->save_log_filename) {
|
||||
log_uploader_handle->save_log_filename = init_params->save_log_filename;
|
||||
log_uploader_handle->save_func = init_params->save_func;
|
||||
log_uploader_handle->read_func = init_params->read_func;
|
||||
log_uploader_handle->del_func = init_params->del_func;
|
||||
log_uploader_handle->get_size_func = init_params->get_size_func;
|
||||
log_uploader_handle->log_save_enabled = true;
|
||||
} else {
|
||||
log_uploader_handle->log_save_enabled = false;
|
||||
}
|
||||
|
||||
log_uploader_handle->upload_buffer_handle = log_upload_buffer_init(init_params);
|
||||
if (!log_uploader_handle->upload_buffer_handle) {
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
_set_log_uploader_client(log_uploader_handle);
|
||||
log_uploader_handle->log_level = LOG_LEVEL_ERROR;
|
||||
log_uploader_handle->log_client_init_done = true;
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
|
||||
err_exit:
|
||||
if (log_uploader_handle) {
|
||||
log_upload_buffer_deinit(log_uploader_handle->upload_buffer_handle);
|
||||
HAL_Free(log_uploader_handle);
|
||||
log_uploader_handle = NULL;
|
||||
}
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stop log upload add release resources.
|
||||
*
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int log_upload_deinit(void)
|
||||
{
|
||||
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
|
||||
POINTER_SANITY_CHECK(log_uploader_handle, QCLOUD_ERR_INVAL);
|
||||
log_upload_buffer_deinit(log_uploader_handle->upload_buffer_handle);
|
||||
HAL_Free(log_uploader_handle);
|
||||
log_uploader_handle = NULL;
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// External API
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Init log upload previously.
|
||||
*
|
||||
* @param[in] init_params @see LogUploadInitParams
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int IOT_Log_Upload_InitPre(const LogUploadInitParams *init_params)
|
||||
{
|
||||
POINTER_SANITY_CHECK(init_params, QCLOUD_ERR_INVAL);
|
||||
STRING_PTR_SANITY_CHECK(init_params->product_id, QCLOUD_ERR_INVAL);
|
||||
STRING_PTR_SANITY_CHECK(init_params->device_name, QCLOUD_ERR_INVAL);
|
||||
STRING_PTR_SANITY_CHECK(init_params->sign_key, QCLOUD_ERR_INVAL);
|
||||
return log_upload_init_pre(init_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init log upload module.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int IOT_Log_Upload_Init(void *client)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
return log_mqtt_init(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stop log upload add release resources.
|
||||
*
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int IOT_Log_Upload_Deinit(void)
|
||||
{
|
||||
return log_upload_deinit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Do log upload.
|
||||
*
|
||||
* @param[in] force_upload force upload when error
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int IOT_Log_Upload(bool force_upload)
|
||||
{
|
||||
return _do_log_upload(force_upload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Append need report log to log upload buffer.
|
||||
*
|
||||
* @param[in] log_level @see LogLevel
|
||||
* @param[in] log_content data of need to report
|
||||
*/
|
||||
void IOT_Log_Upload_AppendToUploadBuffer(LogLevel log_level, const char *log_content)
|
||||
{
|
||||
POINTER_SANITY_CHECK_RTN(log_content);
|
||||
_log_upload_append_to_log_buffer(log_level, log_content);
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
ColumnLimit: 120
|
||||
DerivePointerAlignment: true
|
||||
PointerAlignment: Left
|
||||
SortIncludes: true
|
||||
IncludeBlocks: Preserve
|
||||
IndentPPDirectives: AfterHash
|
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file test_broadcast.cc
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-07-18
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "mqtt_client_test.h"
|
||||
#include "qcloud_iot_common.h"
|
||||
|
||||
namespace mqtt_client_unittest {
|
||||
|
||||
/**
|
||||
* @brief Test log upload
|
||||
*
|
||||
*/
|
||||
TEST_F(MqttClientTest, log_upload_test) {
|
||||
LogHandleFunc func = DEFAULT_LOG_HANDLE_FUNCS;
|
||||
func.log_upload = IOT_Log_Upload_AppendToUploadBuffer;
|
||||
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
|
||||
|
||||
LogUploadInitParams log_upload_init_params = DEFAULT_LOG_UPLOAD_INIT_PARAMS;
|
||||
log_upload_init_params.device_name = device_info.device_name;
|
||||
log_upload_init_params.product_id = device_info.product_id;
|
||||
log_upload_init_params.sign_key = device_info.device_secret;
|
||||
|
||||
log_upload_init_params.log_buffer_size = LOG_UPLOAD_BUFFER_SIZE;
|
||||
log_upload_init_params.save_log_filename = "./tmp/upload-fail-save.log";
|
||||
log_upload_init_params.read_func = HAL_File_Read;
|
||||
log_upload_init_params.save_func = HAL_File_Write;
|
||||
log_upload_init_params.del_func = HAL_File_Del;
|
||||
log_upload_init_params.get_size_func = HAL_File_GetSize;
|
||||
|
||||
ASSERT_EQ(IOT_Log_Upload_InitPre(&log_upload_init_params), 0);
|
||||
ASSERT_EQ(IOT_Log_Upload_Init(client), 0);
|
||||
|
||||
uint32_t loop_cnt = 10;
|
||||
int rc = 0;
|
||||
do {
|
||||
Log_d("log upload test debug %d...", loop_cnt);
|
||||
Log_i("log upload test info %d...", loop_cnt);
|
||||
Log_w("log upload test waring %d...", loop_cnt);
|
||||
Log_e("log upload test error %d...", loop_cnt);
|
||||
rc = IOT_MQTT_Yield(client, 200);
|
||||
ASSERT_EQ(IOT_Log_Upload(rc ? true : false), 0);
|
||||
} while (loop_cnt--);
|
||||
|
||||
ASSERT_EQ(IOT_Log_Upload_Deinit(), 0);
|
||||
|
||||
func.log_upload = NULL;
|
||||
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
|
||||
}
|
||||
|
||||
} // namespace mqtt_client_unittest
|
@@ -0,0 +1,18 @@
|
||||
file(GLOB src_mqtt_client ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
|
||||
set(inc_mqtt_client ${CMAKE_CURRENT_SOURCE_DIR}/inc/)
|
||||
|
||||
set(src_services ${src_services} ${src_mqtt_client} PARENT_SCOPE)
|
||||
set(inc_services ${inc_services} ${inc_mqtt_client} PARENT_SCOPE)
|
||||
|
||||
if( ${CONFIG_COMPILE_SAMPLE} STREQUAL "ON")
|
||||
file(GLOB src_mqtt_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/mqtt_sample.c)
|
||||
add_executable(mqtt_sample ${src_mqtt_sample})
|
||||
target_link_libraries(mqtt_sample ${libsdk})
|
||||
endif()
|
||||
|
||||
if( ${CONFIG_IOT_TEST} STREQUAL "ON")
|
||||
file(GLOB src_unit_test ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc)
|
||||
set(inc_mqtt_client_test ${CMAKE_CURRENT_SOURCE_DIR}/test)
|
||||
set(src_test ${src_test} ${src_unit_test} PARENT_SCOPE)
|
||||
set(inc_test ${inc_test} ${inc_mqtt_client_test} PARENT_SCOPE)
|
||||
endif()
|
@@ -0,0 +1,446 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file mqtt_client.h
|
||||
* @brief mqtt client internel api
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-05-31
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-05-31 <td>1.0 <td>fancyxu <td>first commit
|
||||
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#ifndef IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_MQTT_CLIENT_INC_MQTT_CLIENT_H_
|
||||
#define IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_MQTT_CLIENT_INC_MQTT_CLIENT_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "qcloud_iot_mqtt_client.h"
|
||||
|
||||
#include "mqtt_packet.h"
|
||||
|
||||
#include "network_interface.h"
|
||||
|
||||
#include "utils_list.h"
|
||||
#include "utils_base64.h"
|
||||
#include "utils_hmac.h"
|
||||
|
||||
/**
|
||||
* @brief Packet id, random from [1 - 65536]
|
||||
*
|
||||
*/
|
||||
#define MAX_PACKET_ID (65535)
|
||||
|
||||
/**
|
||||
* @brief Max size of conn Id
|
||||
*
|
||||
*/
|
||||
#define MAX_CONN_ID_LEN (6)
|
||||
|
||||
/**
|
||||
* @brief Max number of topic subscribed
|
||||
*
|
||||
*/
|
||||
#define MAX_MESSAGE_HANDLERS (10)
|
||||
|
||||
/**
|
||||
* @brief Max number in repub list
|
||||
*
|
||||
*/
|
||||
#define MAX_REPUB_NUM (20)
|
||||
|
||||
/**
|
||||
* @brief Minimal wait interval when reconnect
|
||||
*
|
||||
*/
|
||||
#define MIN_RECONNECT_WAIT_INTERVAL (1000)
|
||||
|
||||
/**
|
||||
* @brief Minimal MQTT timeout value
|
||||
*
|
||||
*/
|
||||
#define MIN_COMMAND_TIMEOUT (500)
|
||||
|
||||
/**
|
||||
* @brief Maxmal MQTT timeout value
|
||||
*
|
||||
*/
|
||||
#define MAX_COMMAND_TIMEOUT (20000)
|
||||
|
||||
/**
|
||||
* @brief Minimal TLS handshaking timeout value (unit: ms)
|
||||
*
|
||||
*/
|
||||
#define QCLOUD_IOT_TLS_HANDSHAKE_TIMEOUT (5 * 1000)
|
||||
|
||||
/**
|
||||
* @brief Enable repeat packet id filter
|
||||
*
|
||||
*/
|
||||
#define MQTT_RMDUP_MSG_ENABLED
|
||||
|
||||
/**
|
||||
* @brief Connect status of mqtt server
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
NOTCONNECTED = 0,
|
||||
CONNECTED = 1,
|
||||
} ConnStatus;
|
||||
|
||||
/**
|
||||
* @brief mqtt connection mode
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
FIRST_CONNECT = 0,
|
||||
RECONNECT = 1,
|
||||
} ConnMode;
|
||||
|
||||
/*
|
||||
* @brief Subscribe status of topic.
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
SUB_ACK_NOT_RECEIVED = 0,
|
||||
SUB_ACK_RECEIVED = 1,
|
||||
} SubStatus;
|
||||
|
||||
/**
|
||||
* @brief data structure for topic subscription handle
|
||||
*/
|
||||
typedef struct {
|
||||
char *topic_filter; /**< topic name, wildcard filter is supported */
|
||||
SubStatus status; /**< status of sub handle */
|
||||
SubscribeParams params; /**< params needed to subscribe */
|
||||
} SubTopicHandle;
|
||||
|
||||
/**
|
||||
* @brief MQTT QCloud IoT Client structure
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
DeviceInfo *device_info; /**< device info needed to connect server */
|
||||
|
||||
uint16_t next_packet_id; /**< MQTT random packet id */
|
||||
uint32_t command_timeout_ms; /**< MQTT command timeout, unit:ms */
|
||||
|
||||
uint8_t write_buf[QCLOUD_IOT_MQTT_TX_BUF_LEN]; /**< MQTT write buffer */
|
||||
uint8_t read_buf[QCLOUD_IOT_MQTT_RX_BUF_LEN]; /**< MQTT read buffer */
|
||||
size_t write_buf_size; /**< size of MQTT write buffer */
|
||||
size_t read_buf_size; /**< size of MQTT read buffer */
|
||||
|
||||
MQTTEventHandler event_handle; /**< callback for MQTT event */
|
||||
uint8_t auto_connect_enable; /**< enable auto connection or not */
|
||||
uint8_t default_subscribe; /**< no subscribe packet send, only add subhandle */
|
||||
|
||||
void *lock_generic; /**< mutex/lock for this client struture */
|
||||
void *lock_write_buf; /**< mutex/lock for write buffer */
|
||||
void *list_pub_wait_ack; /**< puback waiting list */
|
||||
void *list_sub_wait_ack; /**< suback waiting list */
|
||||
|
||||
char host_addr[HOST_STR_LENGTH]; /**< MQTT server host */
|
||||
MQTTGetNextHostIp get_next_host_ip; // get host ip
|
||||
const char *main_host;
|
||||
const char *backup_host; // if host not connect will try this.
|
||||
IotNetwork network_stack; /**< MQTT network stack */
|
||||
|
||||
MQTTPacketConnectOption options; /**< handle to connection parameters */
|
||||
char conn_id[MAX_CONN_ID_LEN]; /**< connect id */
|
||||
|
||||
SubTopicHandle sub_handles[MAX_MESSAGE_HANDLERS]; /**< subscription handle array */
|
||||
QcloudIotTimer ping_timer; /**< MQTT ping timer */
|
||||
QcloudIotTimer reconnect_delay_timer; /**< MQTT reconnect delay timer */
|
||||
uint8_t was_manually_disconnected; /**< was disconnect by server or device */
|
||||
uint8_t is_ping_outstanding; /**< 1 = ping request is sent while ping response not arrived yet */
|
||||
uint32_t current_reconnect_wait_interval; /**< unit:ms */
|
||||
|
||||
uint8_t is_connected; /**< is connected or not */
|
||||
uint32_t counter_network_disconnected; /**< number of disconnection*/
|
||||
|
||||
#ifdef MQTT_RMDUP_MSG_ENABLED
|
||||
#define MQTT_MAX_REPEAT_BUF_LEN 10
|
||||
uint16_t repeat_packet_id_buf[MQTT_MAX_REPEAT_BUF_LEN]; /**< repeat packet id buffer */
|
||||
unsigned int current_packet_id_cnt; /**< index of packet id buffer */
|
||||
#endif
|
||||
} QcloudIotClient;
|
||||
|
||||
/**
|
||||
* @brief topic publish info
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t *buf; /**< msg buffer */
|
||||
uint32_t len; /**< msg length */
|
||||
uint16_t packet_id; /**< packet id */
|
||||
QcloudIotTimer pub_start_time; /**< timer for puback waiting */
|
||||
} QcloudIotPubInfo;
|
||||
|
||||
/**
|
||||
* @brief topic subscribe/unsubscribe info
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t *buf; /**< msg buffer */
|
||||
uint16_t len; /**< msg length */
|
||||
MQTTPacketType type; /**< type: sub or unsub */
|
||||
uint16_t packet_id; /**< packet id */
|
||||
QcloudIotTimer sub_start_time; /**< timer for suback waiting */
|
||||
SubTopicHandle handler; /**< handle of topic subscribed(unsubscribed) */
|
||||
} QcloudIotSubInfo;
|
||||
|
||||
/**************************************************************************************
|
||||
* common
|
||||
**************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Get the next packet id object.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return packet id
|
||||
*/
|
||||
uint16_t get_next_packet_id(QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Get the next conn id object.
|
||||
*
|
||||
* @param[out] conn_id buffer to save conn id
|
||||
*/
|
||||
void get_next_conn_id(char *conn_id);
|
||||
|
||||
/**
|
||||
* @brief Set the client conn state object.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] connected connect status, @see ConnStatus
|
||||
*/
|
||||
void set_client_conn_state(QcloudIotClient *client, uint8_t connected);
|
||||
|
||||
/**
|
||||
* @brief Get the client conn state object.
|
||||
*
|
||||
* @param[in,out] client
|
||||
* @return @see ConnStatus
|
||||
*/
|
||||
uint8_t get_client_conn_state(QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Send mqtt packet, timeout = command_timeout_ms.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] length length of data to be sent, data is saved in client write_buf
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int send_mqtt_packet(QcloudIotClient *client, size_t length);
|
||||
|
||||
/**************************************************************************************
|
||||
* connect
|
||||
**************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Connect MQTT server.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] mode when connect error. Choose different connection strategies according to the mode
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_connect(QcloudIotClient *client, ConnMode mode);
|
||||
|
||||
/**
|
||||
* @brief Reconnect MQTT server.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_attempt_reconnect(QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Disconnect MQTT server.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_disconnect(QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Serialize and send pingreq packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] try_times if failed, retry times
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_pingreq(QcloudIotClient *client, int try_times);
|
||||
|
||||
/**************************************************************************************
|
||||
* publish
|
||||
**************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Serialize and send publish packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] topic_name topic to publish
|
||||
* @param[in] params publish params
|
||||
* @return >=0 for packet id, < 0 for failed @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_publish(QcloudIotClient *client, const char *topic_name, const PublishParams *params);
|
||||
|
||||
/**
|
||||
* @brief Deserialize publish packet and deliver_message.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] params publish params
|
||||
* @return >=0 for packet id, < 0 for failed @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_handle_publish(QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Deserialize puback packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @return 0 for success.
|
||||
*/
|
||||
int qcloud_iot_mqtt_handle_puback(QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Process puback waiting timout.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
*/
|
||||
void qcloud_iot_mqtt_check_pub_timeout(QcloudIotClient *client);
|
||||
|
||||
/**************************************************************************************
|
||||
* subscribe
|
||||
**************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Serialize and send subscribe packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic to subscribe
|
||||
* @param[in] params subscribe params
|
||||
* @return >=0 for packet id, < 0 for failed @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_subscribe(QcloudIotClient *client, const char *topic_filter, const SubscribeParams *params);
|
||||
|
||||
/**
|
||||
* @brief Deserialize suback packet and return sub result.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_handle_suback(QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Serialize and send unsubscribe packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic to unsubscribe
|
||||
* @return >=0 packet id, < 0 for failed @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_unsubscribe(QcloudIotClient *client, const char *topic_filter);
|
||||
|
||||
/**
|
||||
* @brief Deserialize unsuback packet and remove from list.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_handle_unsuback(QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Process suback waiting timeout.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
void qcloud_iot_mqtt_check_sub_timeout(QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Resubscribe topic when reconnect.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_resubscribe(QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Return if topic is sub ready.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter
|
||||
* @return true for ready
|
||||
* @return false for not ready
|
||||
*/
|
||||
bool qcloud_iot_mqtt_is_sub_ready(QcloudIotClient *client, const char *topic_filter);
|
||||
|
||||
/**
|
||||
* @brief Get usr data, usr should handle lock/unlock usrdata itself in callback and caller.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter
|
||||
* @return NULL or user data
|
||||
*/
|
||||
void *qcloud_iot_mqtt_get_subscribe_usr_data(QcloudIotClient *client, const char *topic_filter);
|
||||
|
||||
/**
|
||||
* @brief Clear sub handle array.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
void qcloud_iot_mqtt_sub_handle_array_clear(QcloudIotClient *client);
|
||||
|
||||
/**
|
||||
* @brief Clear suback wait list and clear sub handle.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
void qcloud_iot_mqtt_suback_wait_list_clear(QcloudIotClient *client);
|
||||
|
||||
/**************************************************************************************
|
||||
* yield
|
||||
**************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Check connection and keep alive state, read/handle MQTT message in synchronized way.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] timeout_ms timeout value (unit: ms) for this operation
|
||||
*
|
||||
* @return QCLOUD_RET_SUCCESS when success, QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT when try reconnecting, others @see
|
||||
* IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_yield(QcloudIotClient *client, uint32_t timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Wait read specific mqtt packet, such as connack.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] packet_type packet type except to read
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_wait_for_read(QcloudIotClient *client, uint8_t packet_type);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_MQTT_CLIENT_INC_MQTT_CLIENT_H_
|
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file mqtt_sample.c
|
||||
* @brief a simple sample for mqtt client
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-05-31
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-05-31 <td>1.0 <td>fancyxu <td>first commit
|
||||
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "qcloud_iot_common.h"
|
||||
|
||||
#include "utils_log.h"
|
||||
|
||||
/**
|
||||
* @brief MQTT event callback, @see MQTTEventHandleFun
|
||||
*
|
||||
* @param[in] client pointer to mqtt client
|
||||
* @param[in] handle_context context
|
||||
* @param[in] msg msg
|
||||
*/
|
||||
static void _mqtt_event_handler(void *client, void *handle_context, MQTTEventMsg *msg)
|
||||
{
|
||||
MQTTMessage *mqtt_message = (MQTTMessage *)msg->msg;
|
||||
uintptr_t packet_id = (uintptr_t)msg->msg;
|
||||
|
||||
switch (msg->event_type) {
|
||||
case MQTT_EVENT_UNDEF:
|
||||
Log_i("undefined event occur.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_DISCONNECT:
|
||||
Log_i("MQTT disconnect.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_RECONNECT:
|
||||
Log_i("MQTT reconnect.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_RECEIVED:
|
||||
Log_i("topic message arrived but without any related handle: topic=%.*s, topic_msg=%.*s",
|
||||
mqtt_message->topic_len, STRING_PTR_PRINT_SANITY_CHECK(mqtt_message->topic_name),
|
||||
mqtt_message->payload_len, STRING_PTR_PRINT_SANITY_CHECK((char *)mqtt_message->payload));
|
||||
break;
|
||||
case MQTT_EVENT_SUBSCRIBE_SUCCESS:
|
||||
Log_i("subscribe success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBE_TIMEOUT:
|
||||
Log_i("subscribe wait ack timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBE_NACK:
|
||||
Log_i("subscribe nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_SUCCESS:
|
||||
Log_i("unsubscribe success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_TIMEOUT:
|
||||
Log_i("unsubscribe timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_NACK:
|
||||
Log_i("unsubscribe nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_SUCCESS:
|
||||
Log_i("publish success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_TIMEOUT:
|
||||
Log_i("publish timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_NACK:
|
||||
Log_i("publish nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
default:
|
||||
Log_i("Should NOT arrive here.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Setup MQTT construct parameters.
|
||||
*
|
||||
* @param[in,out] initParams @see MQTTInitParams
|
||||
* @param[in] device_info @see DeviceInfo
|
||||
*/
|
||||
static void _setup_connect_init_params(MQTTInitParams *init_params, DeviceInfo *device_info)
|
||||
{
|
||||
init_params->device_info = device_info;
|
||||
init_params->event_handle.h_fp = _mqtt_event_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish a test mqtt message.
|
||||
*
|
||||
* @param[in, out] client pointer to mqtt client
|
||||
* @param[in] topic_keyword topic keyword
|
||||
* @param[in] qos qos to publish
|
||||
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
static int _publish_test_msg(void *client, const char *topic_keyword, QoS qos)
|
||||
{
|
||||
char topic_name[MAX_SIZE_OF_CLOUD_TOPIC] = {0};
|
||||
|
||||
DeviceInfo *dev_info = IOT_MQTT_GetDeviceInfo(client);
|
||||
|
||||
HAL_Snprintf(topic_name, sizeof(topic_name), "%s/%s/%s", STRING_PTR_PRINT_SANITY_CHECK(dev_info->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(dev_info->device_name), STRING_PTR_PRINT_SANITY_CHECK(topic_keyword));
|
||||
|
||||
static int test_count = 0;
|
||||
PublishParams pub_params = DEFAULT_PUB_PARAMS;
|
||||
pub_params.qos = qos;
|
||||
|
||||
char publish_content[128] = {0};
|
||||
HAL_Snprintf(publish_content, sizeof(publish_content), "{\"action\": \"publish_test\", \"count\": \"%d\"}",
|
||||
test_count++);
|
||||
pub_params.payload = publish_content;
|
||||
pub_params.payload_len = strlen(publish_content);
|
||||
return IOT_MQTT_Publish(client, topic_name, &pub_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Callback when MQTT msg arrives @see OnMessageHandler
|
||||
*
|
||||
* @param[in, out] client pointer to mqtt client
|
||||
* @param[in] message publish message from server
|
||||
* @param[in] usr_data user data of SubscribeParams, @see SubscribeParams
|
||||
*/
|
||||
static void _on_message_callback(void *client, const MQTTMessage *message, void *user_data)
|
||||
{
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log_i("Receive Message With topicName:%.*s, payload:%.*s", message->topic_len,
|
||||
STRING_PTR_PRINT_SANITY_CHECK(message->topic_name), message->payload_len,
|
||||
STRING_PTR_PRINT_SANITY_CHECK(message->payload_str));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Subscribe MQTT topic and wait for sub result.
|
||||
*
|
||||
* @param[in, out] client pointer to mqtt client
|
||||
* @param[in] topic_keyword topic keyword
|
||||
* @param[in] qos qos to subscribe
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _subscribe_topic_wait_result(void *client, char *topic_keyword, QoS qos)
|
||||
{
|
||||
char topic_name[MAX_SIZE_OF_CLOUD_TOPIC] = {0};
|
||||
|
||||
DeviceInfo *dev_info = IOT_MQTT_GetDeviceInfo(client);
|
||||
|
||||
HAL_Snprintf(topic_name, sizeof(topic_name), "%s/%s/%s", STRING_PTR_PRINT_SANITY_CHECK(dev_info->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(dev_info->device_name), STRING_PTR_PRINT_SANITY_CHECK(topic_keyword));
|
||||
|
||||
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
|
||||
sub_params.qos = qos;
|
||||
sub_params.on_message_handler = _on_message_callback;
|
||||
|
||||
return IOT_MQTT_SubscribeSync(client, topic_name, &sub_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unsubscribe MQTT topic.
|
||||
*
|
||||
* @param[in, out] client pointer to mqtt client
|
||||
* @param[in] topic_keyword topic keyword
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _unsubscribe_topic(void *client, char *topic_keyword)
|
||||
{
|
||||
char topic_name[MAX_SIZE_OF_CLOUD_TOPIC] = {0};
|
||||
|
||||
DeviceInfo *dev_info = IOT_MQTT_GetDeviceInfo(client);
|
||||
|
||||
HAL_Snprintf(topic_name, sizeof(topic_name), "%s/%s/%s", STRING_PTR_PRINT_SANITY_CHECK(dev_info->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(dev_info->device_name), STRING_PTR_PRINT_SANITY_CHECK(topic_keyword));
|
||||
|
||||
int rc = IOT_MQTT_Unsubscribe(client, topic_name);
|
||||
if (rc < 0) {
|
||||
Log_e("MQTT unsubscribe FAILED: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
return IOT_MQTT_Yield(client, 500); // wait for unsuback
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Main
|
||||
// ----------------------------------------------------------------------------
|
||||
static int sg_main_exit = 0;
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void _main_exit(int sig)
|
||||
{
|
||||
Log_e("demo exit by signal:%d\n", sig);
|
||||
sg_main_exit = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
#ifdef __linux__
|
||||
signal(SIGINT, _main_exit);
|
||||
#endif
|
||||
|
||||
int rc;
|
||||
|
||||
// init log level
|
||||
LogHandleFunc func = DEFAULT_LOG_HANDLE_FUNCS;
|
||||
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
|
||||
|
||||
DeviceInfo device_info;
|
||||
|
||||
rc = HAL_GetDevInfo((void *)&device_info);
|
||||
if (rc) {
|
||||
Log_e("get device info failed: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// init connection
|
||||
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
|
||||
_setup_connect_init_params(&init_params, &device_info);
|
||||
|
||||
// create MQTT client and connect with server
|
||||
void *client = IOT_MQTT_Construct(&init_params);
|
||||
if (client) {
|
||||
Log_i("Cloud Device Construct Success");
|
||||
} else {
|
||||
Log_e("MQTT Construct failed!");
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
// subscribe normal topics and wait result
|
||||
rc = _subscribe_topic_wait_result(client, "data", QOS0);
|
||||
if (rc) {
|
||||
Log_e("Client Subscribe Topic Failed: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
do {
|
||||
rc = _publish_test_msg(client, "data", QOS1);
|
||||
if (rc < 0) {
|
||||
Log_e("client publish topic failed :%d.", rc);
|
||||
}
|
||||
|
||||
rc = IOT_MQTT_Yield(client, 2000);
|
||||
if (rc == QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT) {
|
||||
HAL_SleepMs(1000);
|
||||
continue;
|
||||
} else if (rc != QCLOUD_RET_SUCCESS && rc != QCLOUD_RET_MQTT_RECONNECTED) {
|
||||
Log_e("exit with error: %d", rc);
|
||||
break;
|
||||
}
|
||||
} while (!sg_main_exit);
|
||||
|
||||
rc = _unsubscribe_topic(client, "data");
|
||||
rc |= IOT_MQTT_Destroy(&client);
|
||||
|
||||
utils_log_deinit();
|
||||
return rc;
|
||||
}
|
@@ -0,0 +1,584 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file mqtt_client.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-05-28
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-05-28 <td>1.0 <td>fancyxu <td>first commit
|
||||
* <tr><td>2021-07-07 <td>1.1 <td>fancyxu <td>support user host for unittest
|
||||
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "mqtt_client.h"
|
||||
|
||||
#define MQTT_VERSION_3_1_1 4
|
||||
// 20 for timestamp length & delimiter
|
||||
#define MAX_MQTT_CONNECT_USR_NAME_LEN (MAX_SIZE_OF_CLIENT_ID + QCLOUD_IOT_DEVICE_SDK_APPID_LEN + MAX_CONN_ID_LEN + 20)
|
||||
#define MAX_MQTT_CONNECT_PASSWORD_LEN (51)
|
||||
|
||||
/**************************************************************************************
|
||||
* static method
|
||||
**************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Get random packet id, when start.
|
||||
*
|
||||
* @return packet id
|
||||
*/
|
||||
static uint16_t _get_random_start_packet_id(void)
|
||||
{
|
||||
srand(IOT_Timer_CurrentSec());
|
||||
return rand() % 65536 + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init list_pub_wait_ack and list_sub_wait_ack
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _mqtt_client_list_init(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
|
||||
UtilsListFunc func = DEFAULT_LIST_FUNCS;
|
||||
|
||||
client->list_pub_wait_ack = utils_list_create(func, MAX_REPUB_NUM);
|
||||
if (!client->list_pub_wait_ack) {
|
||||
Log_e("create pub wait list failed.");
|
||||
goto error;
|
||||
}
|
||||
|
||||
client->list_sub_wait_ack = utils_list_create(func, MAX_MESSAGE_HANDLERS);
|
||||
if (!client->list_sub_wait_ack) {
|
||||
Log_e("create sub wait list failed.");
|
||||
goto error;
|
||||
}
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
error:
|
||||
utils_list_destroy(client->list_pub_wait_ack);
|
||||
client->list_pub_wait_ack = NULL;
|
||||
utils_list_destroy(client->list_sub_wait_ack);
|
||||
client->list_sub_wait_ack = NULL;
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init network, tcp(with AUTH_WITH_NO_TLS) or tls.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
static void _mqtt_client_network_init(QcloudIotClient *client, const MQTTInitParams *params)
|
||||
{
|
||||
HAL_Snprintf(client->host_addr, HOST_STR_LENGTH, "%s.%s", client->device_info->product_id,
|
||||
params->host ? params->host : QCLOUD_IOT_MQTT_DIRECT_DOMAIN);
|
||||
client->main_host = params->host;
|
||||
client->backup_host = params->backup_host;
|
||||
client->get_next_host_ip = params->get_next_host_ip;
|
||||
#ifndef AUTH_WITH_NO_TLS
|
||||
// device param for TLS connection
|
||||
#ifdef AUTH_MODE_CERT
|
||||
Log_d("cert file: %s", STRING_PTR_PRINT_SANITY_CHECK(client->device_info->cert_file));
|
||||
Log_d("key file: %s", STRING_PTR_PRINT_SANITY_CHECK(client->device_info->key_file));
|
||||
|
||||
client->network_stack.ssl_connect_params.cert_file = client->device_info->cert_file;
|
||||
client->network_stack.ssl_connect_params.key_file = client->device_info->key_file;
|
||||
client->network_stack.ssl_connect_params.ca_crt = iot_ca_get();
|
||||
client->network_stack.ssl_connect_params.ca_crt_len = strlen(client->network_stack.ssl_connect_params.ca_crt);
|
||||
#else
|
||||
client->network_stack.ssl_connect_params.psk = (char *)client->device_info->device_secret_decode;
|
||||
client->network_stack.ssl_connect_params.psk_length = client->device_info->device_secret_decode_len;
|
||||
client->network_stack.ssl_connect_params.psk_id = client->device_info->client_id;
|
||||
client->network_stack.ssl_connect_params.ca_crt = NULL;
|
||||
client->network_stack.ssl_connect_params.ca_crt_len = 0;
|
||||
#endif
|
||||
client->network_stack.host = client->host_addr;
|
||||
client->network_stack.port = MQTT_SERVER_PORT_TLS;
|
||||
client->network_stack.ssl_connect_params.timeout_ms = client->command_timeout_ms > QCLOUD_IOT_TLS_HANDSHAKE_TIMEOUT
|
||||
? client->command_timeout_ms
|
||||
: QCLOUD_IOT_TLS_HANDSHAKE_TIMEOUT;
|
||||
client->network_stack.type = IOT_NETWORK_TYPE_TLS;
|
||||
#else
|
||||
client->network_stack.host = client->host_addr;
|
||||
client->network_stack.port = MQTT_SERVER_PORT_NO_TLS;
|
||||
client->network_stack.type = IOT_NETWORK_TYPE_TCP;
|
||||
#endif
|
||||
qcloud_iot_network_init(&(client->network_stack));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init mqtt connection options.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] params mqtt init params, @see MQTTInitParams
|
||||
* @return int
|
||||
*/
|
||||
static int _mqtt_client_connect_option_init(QcloudIotClient *client, const MQTTInitParams *params)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
|
||||
int rc = 0;
|
||||
long cur_timesec = 0;
|
||||
|
||||
client->options.mqtt_version = MQTT_VERSION_3_1_1;
|
||||
// Upper limit of keep alive interval is (11.5 * 60) seconds
|
||||
client->options.client_id = client->device_info->client_id;
|
||||
client->options.keep_alive_interval = params->keep_alive_interval > 690 ? 690 : params->keep_alive_interval;
|
||||
client->options.clean_session = params->clean_session;
|
||||
|
||||
// calculate user name & password
|
||||
client->options.username = (char *)HAL_Malloc(MAX_MQTT_CONNECT_USR_NAME_LEN);
|
||||
if (!client->options.username) {
|
||||
Log_e("malloc username failed!");
|
||||
rc = QCLOUD_ERR_MALLOC;
|
||||
goto error;
|
||||
}
|
||||
|
||||
cur_timesec =
|
||||
(MAX_ACCESS_EXPIRE_TIMEOUT <= 0) ? 0x7fffffffL : (IOT_Timer_CurrentSec() + MAX_ACCESS_EXPIRE_TIMEOUT / 1000);
|
||||
get_next_conn_id(client->conn_id);
|
||||
HAL_Snprintf(client->options.username, MAX_MQTT_CONNECT_USR_NAME_LEN, "%s;%s;%s;%ld", client->options.client_id,
|
||||
QCLOUD_IOT_DEVICE_SDK_APPID, client->conn_id, cur_timesec);
|
||||
|
||||
#if defined(AUTH_WITH_NO_TLS) && defined(AUTH_MODE_KEY)
|
||||
char sign[41] = {0};
|
||||
client->options.password = (char *)HAL_Malloc(MAX_MQTT_CONNECT_PASSWORD_LEN);
|
||||
if (!client->options.password) {
|
||||
Log_e("malloc password failed!");
|
||||
rc = QCLOUD_ERR_MALLOC;
|
||||
goto error;
|
||||
}
|
||||
utils_hmac_sha1_hex(client->options.username, strlen(client->options.username),
|
||||
client->device_info->device_secret_decode, client->device_info->device_secret_decode_len, sign);
|
||||
HAL_Snprintf(client->options.password, MAX_MQTT_CONNECT_PASSWORD_LEN, "%s;hmacsha1", sign);
|
||||
#endif
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
error:
|
||||
HAL_Free(client->options.username);
|
||||
client->options.username = NULL;
|
||||
HAL_Free(client->options.password);
|
||||
client->options.password = NULL;
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init mqtt client.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] params mqtt init params, @see MQTTInitParams
|
||||
* @return @see IotReturnCode
|
||||
*
|
||||
* @note
|
||||
* 1. init device info.
|
||||
* 2. init mutex and var
|
||||
* 3. init list @see _mqtt_client_list_init
|
||||
* 4. init connect option @see _mqtt_client_connect_option_init
|
||||
* 5. init network @see _mqtt_client_network_init
|
||||
*/
|
||||
static int _qcloud_iot_mqtt_client_init(QcloudIotClient *client, const MQTTInitParams *params)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc = 0;
|
||||
memset(client, 0x0, sizeof(QcloudIotClient));
|
||||
|
||||
// set device info
|
||||
client->device_info = params->device_info;
|
||||
rc = HAL_Snprintf(client->device_info->client_id, MAX_SIZE_OF_CLIENT_ID, "%s%s", client->device_info->product_id,
|
||||
client->device_info->device_name);
|
||||
#ifdef AUTH_MODE_KEY
|
||||
utils_base64decode(client->device_info->device_secret_decode, MAX_SIZE_OF_DECODE_PSK_LENGTH,
|
||||
&client->device_info->device_secret_decode_len, (uint8_t *)client->device_info->device_secret,
|
||||
strlen(client->device_info->device_secret));
|
||||
#endif
|
||||
Log_i("SDK_Ver: %s, Product_ID: %s, Device_Name: %s", QCLOUD_IOT_DEVICE_SDK_VERSION,
|
||||
client->device_info->product_id, client->device_info->device_name);
|
||||
|
||||
// command timeout should be in range [MIN_COMMAND_TIMEOUT, MAX_COMMAND_TIMEOUT]
|
||||
client->command_timeout_ms =
|
||||
(params->command_timeout < MIN_COMMAND_TIMEOUT)
|
||||
? MIN_COMMAND_TIMEOUT
|
||||
: (params->command_timeout > MAX_COMMAND_TIMEOUT ? MAX_COMMAND_TIMEOUT : params->command_timeout);
|
||||
|
||||
// packet id, random from [1 - 65536]
|
||||
client->next_packet_id = _get_random_start_packet_id();
|
||||
client->write_buf_size = QCLOUD_IOT_MQTT_TX_BUF_LEN;
|
||||
client->read_buf_size = QCLOUD_IOT_MQTT_RX_BUF_LEN;
|
||||
client->event_handle = params->event_handle;
|
||||
client->auto_connect_enable = params->auto_connect_enable;
|
||||
client->default_subscribe = params->default_subscribe;
|
||||
|
||||
client->lock_generic = HAL_MutexCreate();
|
||||
if (!client->lock_generic) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
client->lock_write_buf = HAL_MutexCreate();
|
||||
if (!client->lock_write_buf) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = _mqtt_client_list_init(client);
|
||||
if (rc) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = _mqtt_client_connect_option_init(client, params);
|
||||
if (rc) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
_mqtt_client_network_init(client, params);
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
|
||||
error:
|
||||
HAL_MutexDestroy(client->lock_generic);
|
||||
client->lock_generic = NULL;
|
||||
HAL_MutexDestroy(client->lock_write_buf);
|
||||
client->lock_write_buf = NULL;
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deinit mqtt client.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
static void _qcloud_iot_mqtt_client_deinit(QcloudIotClient *client)
|
||||
{
|
||||
HAL_Free(client->options.username);
|
||||
HAL_Free(client->options.password);
|
||||
HAL_MutexDestroy(client->lock_generic);
|
||||
HAL_MutexDestroy(client->lock_write_buf);
|
||||
qcloud_iot_mqtt_sub_handle_array_clear(client);
|
||||
qcloud_iot_mqtt_suback_wait_list_clear(client);
|
||||
utils_list_destroy(client->list_pub_wait_ack);
|
||||
utils_list_destroy(client->list_sub_wait_ack);
|
||||
Log_i("release mqtt client resources");
|
||||
}
|
||||
|
||||
/**************************************************************************************
|
||||
* API
|
||||
**************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Create MQTT client and connect to MQTT server.
|
||||
*
|
||||
* @param[in] params MQTT init parameters
|
||||
* @return a valid MQTT client handle when success, or NULL otherwise
|
||||
*/
|
||||
void *IOT_MQTT_Construct(const MQTTInitParams *params)
|
||||
{
|
||||
POINTER_SANITY_CHECK(params, NULL);
|
||||
POINTER_SANITY_CHECK(params->device_info, NULL);
|
||||
STRING_PTR_SANITY_CHECK(params->device_info->product_id, NULL);
|
||||
STRING_PTR_SANITY_CHECK(params->device_info->device_name, NULL);
|
||||
#ifdef AUTH_MODE_CERT
|
||||
STRING_PTR_SANITY_CHECK(params->device_info->dev_cert_file_name, NULL);
|
||||
STRING_PTR_SANITY_CHECK(params->device_info->dev_key_file_name, NULL);
|
||||
#else
|
||||
STRING_PTR_SANITY_CHECK(params->device_info->device_secret, NULL);
|
||||
#endif
|
||||
|
||||
int rc = 0;
|
||||
|
||||
QcloudIotClient *client = NULL;
|
||||
|
||||
// create and init MQTTClient
|
||||
client = (QcloudIotClient *)HAL_Malloc(sizeof(QcloudIotClient));
|
||||
if (!client) {
|
||||
Log_e("malloc MQTTClient failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rc = _qcloud_iot_mqtt_client_init(client, params);
|
||||
if (rc) {
|
||||
Log_e("mqtt init failed: %d", rc);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!params->connect_when_construct) {
|
||||
return client;
|
||||
}
|
||||
|
||||
rc = qcloud_iot_mqtt_connect(client, FIRST_CONNECT);
|
||||
if (rc) {
|
||||
Log_e("mqtt connect with id: %s failed: %d", STRING_PTR_PRINT_SANITY_CHECK(client->conn_id), rc);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
Log_i("mqtt connect with id: %s success", client->conn_id);
|
||||
return client;
|
||||
exit:
|
||||
_qcloud_iot_mqtt_client_deinit(client);
|
||||
HAL_Free(client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connect Mqtt server if not connect.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client pointer, should using the pointer of IOT_MQTT_Construct return.
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int IOT_MQTT_Connect(void *client)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
if (!get_client_conn_state(client)) {
|
||||
IOT_FUNC_EXIT_RC(qcloud_iot_mqtt_connect(client, FIRST_CONNECT));
|
||||
}
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Close connection and destroy MQTT client.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client pointer, should using the pointer of IOT_MQTT_Construct return.
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int IOT_MQTT_Destroy(void **client)
|
||||
{
|
||||
POINTER_SANITY_CHECK(*client, QCLOUD_ERR_INVAL);
|
||||
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)(*client);
|
||||
|
||||
int rc = qcloud_iot_mqtt_disconnect(mqtt_client);
|
||||
if (rc) {
|
||||
// disconnect network stack by force
|
||||
mqtt_client->network_stack.disconnect(&(mqtt_client->network_stack));
|
||||
set_client_conn_state(mqtt_client, NOTCONNECTED);
|
||||
}
|
||||
|
||||
_qcloud_iot_mqtt_client_deinit(mqtt_client);
|
||||
|
||||
HAL_Free(*client);
|
||||
*client = NULL;
|
||||
Log_i("mqtt release!");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check connection and keep alive state, read/handle MQTT packet in synchronized way.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] timeout_ms timeout value (unit: ms) for this operation
|
||||
* @return QCLOUD_RET_SUCCESS when success, QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT when try reconnecting, others @see
|
||||
* IotReturnCode
|
||||
*/
|
||||
int IOT_MQTT_Yield(void *client, uint32_t timeout_ms)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
return qcloud_iot_mqtt_yield(mqtt_client, timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish MQTT message.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_name topic to publish
|
||||
* @param[in] params @see PublishParams
|
||||
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
int IOT_MQTT_Publish(void *client, const char *topic_name, const PublishParams *params)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(params, QCLOUD_ERR_INVAL);
|
||||
STRING_PTR_SANITY_CHECK(topic_name, QCLOUD_ERR_INVAL);
|
||||
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
|
||||
if (!get_client_conn_state(client)) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_NO_CONN);
|
||||
}
|
||||
|
||||
if (strlen(topic_name) > MAX_SIZE_OF_CLOUD_TOPIC) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MAX_TOPIC_LENGTH);
|
||||
}
|
||||
|
||||
if (QOS2 == params->qos) {
|
||||
Log_e("QoS2 is not supported currently");
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_QOS_NOT_SUPPORT);
|
||||
}
|
||||
|
||||
return qcloud_iot_mqtt_publish(mqtt_client, topic_name, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Subscribe MQTT topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter to subscribe
|
||||
* @param[in] params @see SubscribeParams
|
||||
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
int IOT_MQTT_Subscribe(void *client, const char *topic_filter, const SubscribeParams *params)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(params, QCLOUD_ERR_INVAL);
|
||||
STRING_PTR_SANITY_CHECK(topic_filter, QCLOUD_ERR_INVAL);
|
||||
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
|
||||
if (!get_client_conn_state(client) && !mqtt_client->default_subscribe) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_NO_CONN);
|
||||
}
|
||||
|
||||
if (strlen(topic_filter) > MAX_SIZE_OF_CLOUD_TOPIC) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MAX_TOPIC_LENGTH);
|
||||
}
|
||||
|
||||
if (QOS2 == params->qos) {
|
||||
Log_e("QoS2 is not supported currently");
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_QOS_NOT_SUPPORT);
|
||||
}
|
||||
|
||||
return qcloud_iot_mqtt_subscribe(mqtt_client, topic_filter, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unsubscribe MQTT topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter to unsubscribe
|
||||
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
int IOT_MQTT_Unsubscribe(void *client, const char *topic_filter)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
STRING_PTR_SANITY_CHECK(topic_filter, QCLOUD_ERR_INVAL);
|
||||
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
|
||||
if (!get_client_conn_state(client)) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_NO_CONN);
|
||||
}
|
||||
|
||||
if (strlen(topic_filter) > MAX_SIZE_OF_CLOUD_TOPIC) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MAX_TOPIC_LENGTH);
|
||||
}
|
||||
|
||||
return qcloud_iot_mqtt_unsubscribe(mqtt_client, topic_filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if MQTT topic has been subscribed or not
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter to subscribe
|
||||
* @return true already subscribed
|
||||
* @return false not ready
|
||||
*/
|
||||
bool IOT_MQTT_IsSubReady(void *client, const char *topic_filter)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, false);
|
||||
STRING_PTR_SANITY_CHECK(topic_filter, false);
|
||||
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
return qcloud_iot_mqtt_is_sub_ready(mqtt_client, topic_filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get user data in subscribe.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter to subscribe
|
||||
* @return NULL or user data
|
||||
*/
|
||||
void *IOT_MQTT_GetSubUsrData(void *client, const char *topic_filter)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, NULL);
|
||||
STRING_PTR_SANITY_CHECK(topic_filter, NULL);
|
||||
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
return qcloud_iot_mqtt_get_subscribe_usr_data(mqtt_client, topic_filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Subscribe and wait sub ready.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter to subscribe
|
||||
* @param[in] params @see SubscribeParams
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int IOT_MQTT_SubscribeSync(void *client, const char *topic_filter, const SubscribeParams *params)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(params, QCLOUD_ERR_INVAL);
|
||||
STRING_PTR_SANITY_CHECK(topic_filter, QCLOUD_ERR_INVAL);
|
||||
|
||||
int rc;
|
||||
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
|
||||
int cnt_sub = mqtt_client->command_timeout_ms / QCLOUD_IOT_MQTT_YIELD_TIMEOUT;
|
||||
|
||||
if (IOT_MQTT_IsSubReady(client, topic_filter)) {
|
||||
// if already sub, free the user data
|
||||
if (params->user_data_free) {
|
||||
params->user_data_free(params->user_data);
|
||||
}
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
rc = IOT_MQTT_Subscribe(client, topic_filter, params);
|
||||
if (rc < 0) {
|
||||
Log_e("topic subscribe failed: %d, cnt: %d", rc, cnt_sub);
|
||||
return rc;
|
||||
}
|
||||
|
||||
while (cnt_sub-- >= 0 && rc >= 0 && !IOT_MQTT_IsSubReady(client, topic_filter)) {
|
||||
/**
|
||||
* @brief wait for subscription result
|
||||
*
|
||||
*/
|
||||
rc = IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT);
|
||||
}
|
||||
return IOT_MQTT_IsSubReady(client, topic_filter) ? QCLOUD_RET_SUCCESS : QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if MQTT is connected.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return true connected
|
||||
* @return false no connected
|
||||
*/
|
||||
bool IOT_MQTT_IsConnected(void *client)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
return get_client_conn_state(mqtt_client);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get device info using to connect mqtt server.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see DeviceInfo
|
||||
*/
|
||||
DeviceInfo *IOT_MQTT_GetDeviceInfo(void *client)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, NULL);
|
||||
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
|
||||
return mqtt_client->device_info;
|
||||
}
|
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file mqtt_client_common.c
|
||||
* @brief common api
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-05-28
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-05-28 <td>1.0 <td>fancyxu <td>first commit
|
||||
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "mqtt_client.h"
|
||||
|
||||
/**
|
||||
* @brief Get the next packet id object.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return packet id
|
||||
*/
|
||||
uint16_t get_next_packet_id(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
client->next_packet_id = (uint16_t)((MAX_PACKET_ID == client->next_packet_id) ? 1 : (client->next_packet_id + 1));
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
|
||||
IOT_FUNC_EXIT_RC(client->next_packet_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the next conn id object.
|
||||
*
|
||||
* @param[out] conn_id buffer to save conn id
|
||||
*/
|
||||
void get_next_conn_id(char *conn_id)
|
||||
{
|
||||
int i;
|
||||
srand(IOT_Timer_CurrentSec());
|
||||
for (i = 0; i < MAX_CONN_ID_LEN - 1; i++) {
|
||||
int flag = rand() % 3;
|
||||
switch (flag) {
|
||||
case 0:
|
||||
conn_id[i] = (rand() % 26) + 'a';
|
||||
break;
|
||||
case 1:
|
||||
conn_id[i] = (rand() % 26) + 'A';
|
||||
break;
|
||||
case 2:
|
||||
conn_id[i] = (rand() % 10) + '0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
conn_id[MAX_CONN_ID_LEN - 1] = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the client conn state object.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] connected connect status, @see ConnStatus
|
||||
*/
|
||||
void set_client_conn_state(QcloudIotClient *client, uint8_t connected)
|
||||
{
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
client->is_connected = connected;
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the client conn state object.
|
||||
*
|
||||
* @param[in,out] client
|
||||
* @return @see ConnStatus
|
||||
*/
|
||||
uint8_t get_client_conn_state(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
uint8_t is_connected = 0;
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
is_connected = client->is_connected;
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
IOT_FUNC_EXIT_RC(is_connected);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send mqtt packet, timeout = command_timeout_ms.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] length length of data to be sent, data is saved in client write_buf
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int send_mqtt_packet(QcloudIotClient *client, size_t length)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
|
||||
int rc = QCLOUD_RET_SUCCESS;
|
||||
|
||||
size_t sent_len = 0;
|
||||
|
||||
if (length >= client->write_buf_size) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_BUF_TOO_SHORT);
|
||||
}
|
||||
|
||||
rc = client->network_stack.write(&(client->network_stack), client->write_buf, length, client->command_timeout_ms,
|
||||
&sent_len);
|
||||
rc = QCLOUD_ERR_TCP_WRITE_TIMEOUT == rc ? QCLOUD_ERR_MQTT_REQUEST_TIMEOUT : rc;
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file mqtt_client_connect.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-05-28
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-05-28 <td>1.0 <td>fancyxu <td>first commit
|
||||
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "mqtt_client.h"
|
||||
/**
|
||||
* @brief switch to domain mode
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
HOST_DOMAIN = 1,
|
||||
BACKUP_DOMAIN = 0,
|
||||
} DomainMode;
|
||||
|
||||
/**
|
||||
* @brief Serialize and send connect packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _mqtt_connect(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
uint8_t session_present, connack_rc;
|
||||
int rc, packet_len;
|
||||
|
||||
// TCP or TLS network connect
|
||||
rc = client->network_stack.connect(&(client->network_stack));
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
// send MQTT CONNECT packet
|
||||
HAL_MutexLock(client->lock_write_buf);
|
||||
packet_len = mqtt_connect_packet_serialize(client->write_buf, client->write_buf_size, &client->options);
|
||||
if (packet_len > 0) {
|
||||
rc = send_mqtt_packet(client, packet_len);
|
||||
} else {
|
||||
rc = packet_len == MQTT_ERR_SHORT_BUFFER ? QCLOUD_ERR_BUF_TOO_SHORT : QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
// recv MQTT CONNACK packet
|
||||
rc = qcloud_iot_mqtt_wait_for_read(client, CONNACK);
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
rc = mqtt_connack_packet_deserialize(client->read_buf, client->read_buf_size, &session_present, &connack_rc);
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
if (CONNACK_CONNECTION_ACCEPTED != connack_rc) {
|
||||
IOT_FUNC_EXIT_RC(connack_rc);
|
||||
}
|
||||
|
||||
// set connect state
|
||||
set_client_conn_state(client, CONNECTED);
|
||||
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
client->was_manually_disconnected = client->is_ping_outstanding = 0;
|
||||
IOT_Timer_Countdown(&client->ping_timer, client->options.keep_alive_interval);
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief switch domain
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] mode @see DomainMode
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static void _mqtt_switch_domain(QcloudIotClient *client, DomainMode mode)
|
||||
{
|
||||
if (mode == BACKUP_DOMAIN && client->backup_host) {
|
||||
HAL_Snprintf(client->host_addr, HOST_STR_LENGTH, "%s.%s", client->device_info->product_id, client->backup_host);
|
||||
client->network_stack.host = client->host_addr;
|
||||
} else if (mode == HOST_DOMAIN) {
|
||||
HAL_Snprintf(client->host_addr, HOST_STR_LENGTH, "%s.%s", client->device_info->product_id, client->main_host);
|
||||
client->network_stack.host = client->host_addr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief set connect host
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] srv_ip server ip string
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _mqtt_set_srv_ip(QcloudIotClient *client, const char *srv_ip)
|
||||
{
|
||||
int size = HAL_Snprintf(client->host_addr, HOST_STR_LENGTH, "%s", srv_ip);
|
||||
if (size < 0 || size > HOST_STR_LENGTH - 1) {
|
||||
Log_e("gen host name failed: %d", size);
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
|
||||
} else {
|
||||
client->network_stack.host = client->host_addr;
|
||||
Log_i("using HOST IP : %s", srv_ip);
|
||||
}
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connect MQTT server.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] mode when connect error. Choose different connection strategies according to the mode
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_connect(QcloudIotClient *client, ConnMode mode)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc = 0;
|
||||
|
||||
// check connection state first
|
||||
if (get_client_conn_state(client)) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_MQTT_ALREADY_CONNECTED);
|
||||
}
|
||||
|
||||
int connect_fail_cnt = 0;
|
||||
int max_connect_fail_switch_cnt = (mode == FIRST_CONNECT) ? 3 : 1;
|
||||
|
||||
while ((rc = _mqtt_connect(client))) {
|
||||
client->network_stack.disconnect(&(client->network_stack));
|
||||
connect_fail_cnt++;
|
||||
|
||||
if (connect_fail_cnt == max_connect_fail_switch_cnt && client->backup_host) {
|
||||
_mqtt_switch_domain(client, BACKUP_DOMAIN);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (connect_fail_cnt > max_connect_fail_switch_cnt) {
|
||||
if (!client->get_next_host_ip) {
|
||||
break;
|
||||
}
|
||||
|
||||
const char *srv_ip = client->get_next_host_ip();
|
||||
if (!srv_ip) {
|
||||
break;
|
||||
}
|
||||
_mqtt_set_srv_ip(client, srv_ip);
|
||||
}
|
||||
}
|
||||
|
||||
// In reconnection mode, the connection is successfully switched back to the primary domain name
|
||||
if (!rc && connect_fail_cnt && mode == RECONNECT) {
|
||||
_mqtt_switch_domain(client, HOST_DOMAIN);
|
||||
}
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reconnect MQTT server.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_attempt_reconnect(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc = 0;
|
||||
|
||||
Log_i("attempt to reconnect...");
|
||||
|
||||
if (get_client_conn_state(client)) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_MQTT_ALREADY_CONNECTED);
|
||||
}
|
||||
|
||||
rc = qcloud_iot_mqtt_connect(client, RECONNECT);
|
||||
|
||||
if (!get_client_conn_state(client)) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
#if 0 // clean session is 0, user don't need resubscribe only if mqtt server error.
|
||||
if (!client->options.clean_session) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_MQTT_RECONNECTED);
|
||||
}
|
||||
#endif
|
||||
|
||||
rc = qcloud_iot_mqtt_resubscribe(client);
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_MQTT_RECONNECTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnect MQTT server.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_disconnect(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc, packet_len = 0;
|
||||
|
||||
if (!get_client_conn_state(client)) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_NO_CONN);
|
||||
}
|
||||
|
||||
HAL_MutexLock(client->lock_write_buf);
|
||||
packet_len = mqtt_disconnect_packet_serialize(client->write_buf, client->write_buf_size);
|
||||
if (packet_len > 0) {
|
||||
rc = send_mqtt_packet(client, packet_len);
|
||||
} else {
|
||||
rc = packet_len == MQTT_ERR_SHORT_BUFFER ? QCLOUD_ERR_BUF_TOO_SHORT : QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
client->network_stack.disconnect(&(client->network_stack));
|
||||
set_client_conn_state(client, NOTCONNECTED);
|
||||
client->was_manually_disconnected = 1;
|
||||
|
||||
Log_i("mqtt disconnect!");
|
||||
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Serialize and send pingreq packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] try_times if failed, retry times
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_pingreq(QcloudIotClient *client, int try_times)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc, packet_len, i = 0;
|
||||
|
||||
HAL_MutexLock(client->lock_write_buf);
|
||||
packet_len = mqtt_pingreq_packet_serialize(client->write_buf, client->write_buf_size);
|
||||
if (packet_len > 0) {
|
||||
do {
|
||||
rc = send_mqtt_packet(client, packet_len);
|
||||
} while (rc && (i++ < try_times));
|
||||
} else {
|
||||
rc = packet_len == MQTT_ERR_SHORT_BUFFER ? QCLOUD_ERR_BUF_TOO_SHORT : QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
@@ -0,0 +1,397 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file mqtt_client_publish.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-05-28
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-05-28 <td>1.0 <td>fancyxu <td>first commit
|
||||
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "mqtt_client.h"
|
||||
|
||||
/**
|
||||
* @brief Push pub info to list for republish.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] packet_len packet len of publish packet
|
||||
* @param[in] packet_id packet id
|
||||
* @param[out] node list node
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _push_pub_info_to_list(QcloudIotClient *client, int packet_len, uint16_t packet_id, void **node)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
void *list = client->list_pub_wait_ack;
|
||||
QcloudIotPubInfo *repub_info = NULL;
|
||||
|
||||
// construct republish info
|
||||
repub_info = (QcloudIotPubInfo *)HAL_Malloc(sizeof(QcloudIotPubInfo) + packet_len);
|
||||
if (!repub_info) {
|
||||
Log_e("memory malloc failed!");
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
|
||||
}
|
||||
|
||||
repub_info->buf = (uint8_t *)repub_info + sizeof(QcloudIotPubInfo);
|
||||
repub_info->len = packet_len;
|
||||
repub_info->packet_id = packet_id;
|
||||
memcpy(repub_info->buf, client->write_buf, packet_len); // save the whole packet
|
||||
IOT_Timer_CountdownMs(&repub_info->pub_start_time, client->command_timeout_ms);
|
||||
|
||||
// push republish info to list
|
||||
*node = utils_list_push(list, repub_info);
|
||||
if (!*node) {
|
||||
HAL_Free(repub_info);
|
||||
Log_e("list push failed! Check the list len!");
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
|
||||
}
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check pub wait list timeout.
|
||||
*
|
||||
* @param[in,out] list pointer to pub wait ack list.
|
||||
* @param[in] node pointer to list node
|
||||
* @param[in] val pointer to value, @see QcloudIotPubInfo
|
||||
* @param[in] usr_data @see QcloudIotClient
|
||||
* @return @see UtilsListResult
|
||||
*/
|
||||
static UtilsListResult _pub_wait_list_process_check_timeout(void *list, void *node, void *val, void *usr_data)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
|
||||
MQTTEventMsg msg;
|
||||
QcloudIotPubInfo *repub_info = (QcloudIotPubInfo *)val;
|
||||
QcloudIotClient *client = (QcloudIotClient *)usr_data;
|
||||
|
||||
// check the request if timeout or not
|
||||
if (IOT_Timer_Remain(&repub_info->pub_start_time) > 0) {
|
||||
IOT_FUNC_EXIT_RC(LIST_TRAVERSE_CONTINUE);
|
||||
}
|
||||
|
||||
// notify timeout event
|
||||
if (client->event_handle.h_fp) {
|
||||
msg.event_type = MQTT_EVENT_PUBLISH_TIMEOUT;
|
||||
msg.msg = (void *)(uintptr_t)repub_info->packet_id;
|
||||
client->event_handle.h_fp(client, client->event_handle.context, &msg);
|
||||
}
|
||||
utils_list_remove(list, node);
|
||||
IOT_FUNC_EXIT_RC(LIST_TRAVERSE_CONTINUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove info from pub wait list.
|
||||
*
|
||||
* @param[in,out] list pointer to pub wait ack list.
|
||||
* @param[in] node pointer to list node
|
||||
* @param[in] val pointer to value, @see QcloudIotPubInfo
|
||||
* @param[in] usr_data pointer to packet id
|
||||
* @return @see UtilsListResult
|
||||
*/
|
||||
static UtilsListResult _pub_wait_list_process_remove_info(void *list, void *node, void *val, void *usr_data)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
|
||||
QcloudIotPubInfo *repub_info = (QcloudIotPubInfo *)val;
|
||||
if (repub_info->packet_id == *((uint16_t *)usr_data)) {
|
||||
utils_list_remove(list, node);
|
||||
IOT_FUNC_EXIT_RC(LIST_TRAVERSE_BREAK);
|
||||
}
|
||||
IOT_FUNC_EXIT_RC(LIST_TRAVERSE_CONTINUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove node signed with packet id from publish ACK wait list.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] packet_id packet id
|
||||
*/
|
||||
static void _remove_pub_info_from_list(QcloudIotClient *client, uint16_t packet_id)
|
||||
{
|
||||
utils_list_process(client->list_pub_wait_ack, LIST_HEAD, _pub_wait_list_process_remove_info, &packet_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if topic match
|
||||
*
|
||||
* @param[in] topic_filter topic name filter, wildcard is supported
|
||||
* @param[in] topic_name topic name, no wildcard
|
||||
* @param[in] topic_name_len length of topic name
|
||||
* @return 1 for matched, 0 for no matched
|
||||
*
|
||||
* @note assume topic filter and name is in correct format;
|
||||
* # can only be at end;
|
||||
* + and # can only be next to separator.
|
||||
*/
|
||||
static uint8_t _is_topic_matched(char *topic_filter, const char *topic_name, uint16_t topic_name_len)
|
||||
{
|
||||
char *curf = topic_filter;
|
||||
char *curn = (char *)topic_name;
|
||||
char *curn_end = curn + topic_name_len;
|
||||
|
||||
while (*curf && curn < curn_end) {
|
||||
if (*curn == '/' && *curf != '/')
|
||||
break;
|
||||
if (*curf != '+' && *curf != '#' && *curf != *curn)
|
||||
break;
|
||||
if (*curf == '+') { // skip until we meet the next separator, or end of string
|
||||
char *nextpos = curn + 1;
|
||||
while (nextpos < curn_end && *nextpos != '/') nextpos = ++curn + 1;
|
||||
} else if (*curf == '#')
|
||||
curn = curn_end - 1; // skip until end of string
|
||||
curf++;
|
||||
curn++;
|
||||
};
|
||||
|
||||
return (curn == curn_end) && (*curf == '\0');
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief deliver the message to user callback
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] message message to deliver, @see MQTTMessage
|
||||
*/
|
||||
static void _deliver_message(QcloudIotClient *client, MQTTMessage *message)
|
||||
{
|
||||
int i;
|
||||
|
||||
MQTTEventMsg msg;
|
||||
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
|
||||
if (client->sub_handles[i].topic_filter &&
|
||||
_is_topic_matched((char *)client->sub_handles[i].topic_filter, message->topic_name, message->topic_len)) {
|
||||
if (client->sub_handles[i].params.on_message_handler) {
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
// if found, then handle it, then return
|
||||
client->sub_handles[i].params.on_message_handler(client, message,
|
||||
client->sub_handles[i].params.user_data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
|
||||
/* Message handler not found for topic */
|
||||
/* May be we do not care change FAILURE use SUCCESS*/
|
||||
Log_d("no matching any topic, call default handle function");
|
||||
|
||||
if (client->event_handle.h_fp) {
|
||||
msg.event_type = MQTT_EVENT_PUBLISH_RECEIVED;
|
||||
msg.msg = message;
|
||||
client->event_handle.h_fp(client, client->event_handle.context, &msg);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MQTT_RMDUP_MSG_ENABLED
|
||||
|
||||
/**
|
||||
* @brief Check if packet id repeat.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] packet_id packet_id
|
||||
* @return < 0 for failed.
|
||||
*/
|
||||
static int _get_packet_id_repeat_buf(QcloudIotClient *client, uint16_t packet_id)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < MQTT_MAX_REPEAT_BUF_LEN; ++i) {
|
||||
if (packet_id == client->repeat_packet_id_buf[i]) {
|
||||
return packet_id;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add packet id, if buf full, then overwrite.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] packet_id packet_id
|
||||
*/
|
||||
static void _add_packet_id_to_repeat_buf(QcloudIotClient *client, uint16_t packet_id)
|
||||
{
|
||||
if (_get_packet_id_repeat_buf(client, packet_id) >= 0) {
|
||||
return;
|
||||
}
|
||||
client->repeat_packet_id_buf[client->current_packet_id_cnt++] = packet_id;
|
||||
client->current_packet_id_cnt %= MQTT_MAX_REPEAT_BUF_LEN;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Serialize and send publish packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] topic_name topic to publish
|
||||
* @param[in] params publish params
|
||||
* @return >=0 for packet id, < 0 for failed @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_publish(QcloudIotClient *client, const char *topic_name, const PublishParams *params)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc, packet_len;
|
||||
MQTTPublishFlags flags;
|
||||
void *node = NULL;
|
||||
uint16_t packet_id = 0;
|
||||
|
||||
if (params->qos > QOS0) {
|
||||
packet_id = get_next_packet_id(client);
|
||||
}
|
||||
|
||||
Log_d("publish qos=%d|packet_id=%d|topic_name=%s|payload=%s", params->qos, packet_id, topic_name,
|
||||
STRING_PTR_PRINT_SANITY_CHECK((char *)params->payload));
|
||||
|
||||
flags.dup = params->dup;
|
||||
flags.qos = params->qos;
|
||||
flags.retain = params->retain;
|
||||
|
||||
// serialize packet
|
||||
HAL_MutexLock(client->lock_write_buf);
|
||||
packet_len = mqtt_publish_packet_serialize(client->write_buf, client->write_buf_size, &flags, packet_id, topic_name,
|
||||
params->payload, params->payload_len);
|
||||
if (packet_len < 0) {
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
rc = packet_len == MQTT_ERR_SHORT_BUFFER ? QCLOUD_ERR_BUF_TOO_SHORT : QCLOUD_ERR_FAILURE;
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
if (params->qos > QOS0) {
|
||||
rc = _push_pub_info_to_list(client, packet_len, packet_id, &node);
|
||||
if (rc) {
|
||||
Log_e("push publish info failed!");
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
}
|
||||
|
||||
// send the publish packet
|
||||
rc = send_mqtt_packet(client, packet_len);
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
if (rc) {
|
||||
if (params->qos > QOS0) {
|
||||
utils_list_remove(client->list_pub_wait_ack, node);
|
||||
}
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
IOT_FUNC_EXIT_RC(packet_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deserialize publish packet and deliver_message.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] params publish params
|
||||
* @return >=0 for packet id, < 0 for failed @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_handle_publish(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc, packet_len = 0;
|
||||
MQTTPublishFlags flags;
|
||||
MQTTMessage msg;
|
||||
|
||||
rc = mqtt_publish_packet_deserialize(client->read_buf, client->read_buf_size, &flags, &msg.packet_id,
|
||||
&msg.topic_name, &msg.topic_len, &msg.payload, &msg.payload_len);
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
msg.qos = flags.qos;
|
||||
|
||||
if (QOS0 == msg.qos) {
|
||||
// No further processing required for QOS0
|
||||
_deliver_message(client, &msg);
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
// only Qos 1 is support
|
||||
#ifdef MQTT_RMDUP_MSG_ENABLED
|
||||
// check if packet_id has been received before
|
||||
if (_get_packet_id_repeat_buf(client, msg.packet_id) < 0)
|
||||
#endif
|
||||
{
|
||||
// deliver to msg callback
|
||||
_deliver_message(client, &msg);
|
||||
}
|
||||
#ifdef MQTT_RMDUP_MSG_ENABLED
|
||||
// just add packet id
|
||||
_add_packet_id_to_repeat_buf(client, msg.packet_id);
|
||||
#endif
|
||||
|
||||
// reply with puback
|
||||
HAL_MutexLock(client->lock_write_buf);
|
||||
packet_len = mqtt_puback_packet_serialize(client->write_buf, client->write_buf_size, msg.packet_id);
|
||||
if (packet_len > 0) {
|
||||
rc = send_mqtt_packet(client, packet_len);
|
||||
} else {
|
||||
rc = packet_len == MQTT_ERR_SHORT_BUFFER ? QCLOUD_ERR_BUF_TOO_SHORT : QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deserialize puback packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @return 0 for success.
|
||||
*/
|
||||
int qcloud_iot_mqtt_handle_puback(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
|
||||
int rc;
|
||||
uint16_t packet_id;
|
||||
MQTTEventMsg msg;
|
||||
|
||||
rc = mqtt_puback_packet_deserialize(client->read_buf, client->read_buf_size, &packet_id);
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
_remove_pub_info_from_list(client, packet_id);
|
||||
|
||||
/* notify this event to user callback */
|
||||
if (client->event_handle.h_fp) {
|
||||
msg.event_type = MQTT_EVENT_PUBLISH_SUCCESS;
|
||||
msg.msg = (void *)(uintptr_t)packet_id;
|
||||
client->event_handle.h_fp(client, client->event_handle.context, &msg);
|
||||
}
|
||||
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Process puback waiting timout.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
*/
|
||||
void qcloud_iot_mqtt_check_pub_timeout(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
utils_list_process(client->list_pub_wait_ack, LIST_HEAD, _pub_wait_list_process_check_timeout, client);
|
||||
IOT_FUNC_EXIT;
|
||||
}
|
@@ -0,0 +1,643 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file mqtt_client_subscribe.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-05-28
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-05-28 <td>1.0 <td>fancyxu <td>first commit
|
||||
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "mqtt_client.h"
|
||||
|
||||
/**
|
||||
* @brief Context of subscribe
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
uint16_t packet_id;
|
||||
SubTopicHandle handler;
|
||||
} QcloudIotSubConext;
|
||||
|
||||
/**
|
||||
* @brief Free topic_filter and user_data
|
||||
*
|
||||
* @param[in] handler subtopic handle
|
||||
*/
|
||||
static void _clear_sub_handle(SubTopicHandle *handler)
|
||||
{
|
||||
if (handler->topic_filter) {
|
||||
HAL_Free(handler->topic_filter);
|
||||
handler->topic_filter = NULL;
|
||||
}
|
||||
|
||||
if (handler->params.user_data && handler->params.user_data_free) {
|
||||
handler->params.user_data_free(handler->params.user_data);
|
||||
handler->params.user_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Push node to subscribe(unsubscribe) ACK wait list.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] packet_len packet len of publish packet
|
||||
* @param[in] packet_id packet id
|
||||
* @param[in] type mqtt packet type SUBSCRIBE or UNSUBSCRIBE
|
||||
* @param[in] handler subtopic handle
|
||||
* @param[out] node node to push to list
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _push_sub_info_to_list(QcloudIotClient *client, int packet_len, uint16_t packet_id, MQTTPacketType type,
|
||||
const SubTopicHandle *handler, void **node)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
void *list = client->list_sub_wait_ack;
|
||||
QcloudIotSubInfo *sub_info = NULL;
|
||||
|
||||
sub_info = (QcloudIotSubInfo *)HAL_Malloc(sizeof(QcloudIotSubInfo) + packet_len);
|
||||
if (!sub_info) {
|
||||
Log_e("memory malloc failed!");
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
|
||||
}
|
||||
|
||||
sub_info->buf = (uint8_t *)sub_info + sizeof(QcloudIotSubInfo);
|
||||
sub_info->len = packet_len;
|
||||
sub_info->type = type;
|
||||
sub_info->packet_id = packet_id;
|
||||
sub_info->handler = *handler;
|
||||
memcpy(sub_info->buf, client->write_buf, packet_len);
|
||||
IOT_Timer_CountdownMs(&sub_info->sub_start_time, client->command_timeout_ms);
|
||||
|
||||
*node = utils_list_push(list, sub_info);
|
||||
if (!*node) {
|
||||
HAL_Free(sub_info);
|
||||
Log_e("list push failed! Check the list len!");
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
|
||||
}
|
||||
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check pub wait list timeout.
|
||||
*
|
||||
* @param[in,out] list pointer to pub wait ack list.
|
||||
* @param[in] node pointer to list node
|
||||
* @param[in] val pointer to value, @see QcloudIotPubInfo
|
||||
* @param[in] usr_data @see QcloudIotClient
|
||||
* @return @see UtilsListResult
|
||||
*/
|
||||
static UtilsListResult _sub_wait_list_process_pop_info(void *list, void *node, void *val, void *usr_data)
|
||||
{
|
||||
QcloudIotSubConext *sub_context = (QcloudIotSubConext *)usr_data;
|
||||
QcloudIotSubInfo *sub_info = (QcloudIotSubInfo *)val;
|
||||
|
||||
if (sub_info->packet_id == sub_context->packet_id) {
|
||||
memcpy(&sub_context->handler, &sub_info->handler, sizeof(SubTopicHandle));
|
||||
utils_list_remove(list, node);
|
||||
return LIST_TRAVERSE_BREAK;
|
||||
}
|
||||
return LIST_TRAVERSE_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check sub wait list timeout.
|
||||
*
|
||||
* @param[in,out] list pointer to sub wait ack list.
|
||||
* @param[in] node pointer to list node
|
||||
* @param[in] val pointer to value, @see QcloudIotSubInfo
|
||||
* @param[in] usr_data @see QcloudIotClient
|
||||
* @return @see UtilsListResult
|
||||
*/
|
||||
static UtilsListResult _sub_wait_list_process_check_timeout(void *list, void *node, void *val, void *usr_data)
|
||||
{
|
||||
MQTTEventMsg msg;
|
||||
QcloudIotSubInfo *sub_info = (QcloudIotSubInfo *)val;
|
||||
QcloudIotClient *client = (QcloudIotClient *)usr_data;
|
||||
|
||||
// check the request if timeout or not
|
||||
if (IOT_Timer_Remain(&sub_info->sub_start_time) > 0) {
|
||||
return LIST_TRAVERSE_CONTINUE;
|
||||
}
|
||||
|
||||
// notify timeout event
|
||||
if (client->event_handle.h_fp) {
|
||||
msg.event_type = SUBSCRIBE == sub_info->type ? MQTT_EVENT_SUBSCRIBE_TIMEOUT : MQTT_EVENT_UNSUBSCRIBE_TIMEOUT;
|
||||
msg.msg = (void *)(uintptr_t)sub_info->packet_id;
|
||||
if (sub_info->handler.params.on_sub_event_handler) {
|
||||
sub_info->handler.params.on_sub_event_handler(client, MQTT_EVENT_SUBSCRIBE_TIMEOUT,
|
||||
sub_info->handler.params.user_data);
|
||||
}
|
||||
client->event_handle.h_fp(client, client->event_handle.context, &msg);
|
||||
}
|
||||
|
||||
_clear_sub_handle(&sub_info->handler);
|
||||
utils_list_remove(list, node);
|
||||
return LIST_TRAVERSE_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear sub wait list.
|
||||
*
|
||||
* @param[in,out] list pointer to sub wait list.
|
||||
* @param[in] node pointer to list node
|
||||
* @param[in] val pointer to value, @see QcloudIotSubInfo
|
||||
* @param[in] usr_data null
|
||||
* @return @see UtilsListResult
|
||||
*/
|
||||
static UtilsListResult _sub_wait_list_process_clear(void *list, void *node, void *val, void *usr_data)
|
||||
{
|
||||
QcloudIotSubInfo *sub_info = (QcloudIotSubInfo *)val;
|
||||
if (sub_info->type == UNSUBSCRIBE) {
|
||||
_clear_sub_handle(&sub_info->handler);
|
||||
}
|
||||
utils_list_remove(list, node);
|
||||
return LIST_TRAVERSE_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pop node signed with packet id from subscribe ACK wait list, and return the sub handler
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] packet_id packet id
|
||||
* @param[out] sub_handle @see SubTopicHandle
|
||||
*/
|
||||
static void _pop_sub_info_from_list(QcloudIotClient *client, uint16_t packet_id, SubTopicHandle *sub_handle)
|
||||
{
|
||||
QcloudIotSubConext sub_context = {.packet_id = packet_id};
|
||||
utils_list_process(client->list_sub_wait_ack, LIST_HEAD, _sub_wait_list_process_pop_info, &sub_context);
|
||||
memcpy(sub_handle, &sub_context.handler, sizeof(SubTopicHandle));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove sub handle when unsubscribe.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] topic_filter topic to remove
|
||||
* @return true topic exist
|
||||
* @return false topic no exist
|
||||
*/
|
||||
static bool _remove_sub_handle_from_array(QcloudIotClient *client, const char *topic_filter)
|
||||
{
|
||||
int i;
|
||||
bool topic_exists = false;
|
||||
|
||||
// remove from message handler array
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
|
||||
if ((client->sub_handles[i].topic_filter && !strcmp(client->sub_handles[i].topic_filter, topic_filter)) ||
|
||||
strstr(topic_filter, "/#") || strstr(topic_filter, "/+")) {
|
||||
// notify this event to topic subscriber
|
||||
if (client->sub_handles[i].params.on_sub_event_handler) {
|
||||
client->sub_handles[i].params.on_sub_event_handler(client, MQTT_EVENT_UNSUBSCRIBE,
|
||||
client->sub_handles[i].params.user_data);
|
||||
}
|
||||
_clear_sub_handle(&client->sub_handles[i]);
|
||||
// we don't want to break here, if the same topic is registered*with 2 callbacks.Unlikely scenario
|
||||
topic_exists = true;
|
||||
}
|
||||
}
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
return topic_exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add sub handle when subscribe.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] sub_handle sub_handle to be add to array
|
||||
* @return true topic exist
|
||||
* @return false topic no exist
|
||||
*/
|
||||
static int _add_sub_handle_to_array(QcloudIotClient *client, const SubTopicHandle *sub_handle)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int i, i_free = -1;
|
||||
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
|
||||
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
|
||||
if (client->sub_handles[i].topic_filter) {
|
||||
if (!strcmp(client->sub_handles[i].topic_filter, sub_handle->topic_filter)) {
|
||||
i_free = i;
|
||||
// free the memory before
|
||||
_clear_sub_handle(&client->sub_handles[i]);
|
||||
Log_w("Identical topic found: %s", sub_handle->topic_filter);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
i_free = i_free == -1 ? i : i_free;
|
||||
}
|
||||
}
|
||||
|
||||
if (-1 == i_free) {
|
||||
Log_e("NO more @sub_handles space!");
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
|
||||
}
|
||||
|
||||
client->sub_handles[i_free] = *sub_handle;
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sub handle status.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] topic_filter topic to set status
|
||||
* @param[in] status @see SubStatus
|
||||
*/
|
||||
static void _set_sub_handle_status_to_array(QcloudIotClient *client, const char *topic_filter, SubStatus status)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int i;
|
||||
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
|
||||
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
|
||||
if (client->sub_handles[i].topic_filter) {
|
||||
if (!strcmp(client->sub_handles[i].topic_filter, topic_filter)) {
|
||||
client->sub_handles[i].status = status;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
IOT_FUNC_EXIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Serialize and send subscribe packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic to subscribe
|
||||
* @param[in] params subscribe params
|
||||
* @return >=0 for packet id, < 0 for failed @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_subscribe(QcloudIotClient *client, const char *topic_filter, const SubscribeParams *params)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc, packet_len, qos = params->qos;
|
||||
uint16_t packet_id;
|
||||
char *topic_filter_stored;
|
||||
void *node = NULL;
|
||||
SubTopicHandle sub_handle;
|
||||
|
||||
// topic filter should be valid in the whole sub life
|
||||
topic_filter_stored = HAL_Malloc(strlen(topic_filter) + 1);
|
||||
if (!topic_filter_stored) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MALLOC);
|
||||
}
|
||||
strncpy(topic_filter_stored, topic_filter, strlen(topic_filter) + 1);
|
||||
|
||||
sub_handle.topic_filter = topic_filter_stored;
|
||||
sub_handle.params = *params;
|
||||
sub_handle.status = client->default_subscribe ? SUB_ACK_RECEIVED : SUB_ACK_NOT_RECEIVED;
|
||||
|
||||
// add sub handle first to process
|
||||
rc = _add_sub_handle_to_array(client, &sub_handle);
|
||||
if (rc) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (client->default_subscribe) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
packet_id = get_next_packet_id(client);
|
||||
Log_d("subscribe topic_name=%s|packet_id=%d", topic_filter_stored, packet_id);
|
||||
// serialize packet
|
||||
HAL_MutexLock(client->lock_write_buf);
|
||||
packet_len = mqtt_subscribe_packet_serialize(client->write_buf, client->write_buf_size, packet_id, 1,
|
||||
&topic_filter_stored, &qos);
|
||||
if (packet_len < 0) {
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
rc = packet_len == MQTT_ERR_SHORT_BUFFER ? QCLOUD_ERR_BUF_TOO_SHORT : QCLOUD_ERR_FAILURE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// add node into sub ack wait list
|
||||
rc = _push_sub_info_to_list(client, packet_len, packet_id, SUBSCRIBE, &sub_handle, &node);
|
||||
if (rc) {
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// send packet
|
||||
rc = send_mqtt_packet(client, packet_len);
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
if (rc) {
|
||||
utils_list_remove(client->list_sub_wait_ack, node);
|
||||
goto exit;
|
||||
}
|
||||
IOT_FUNC_EXIT_RC(packet_id);
|
||||
exit:
|
||||
_remove_sub_handle_from_array(client, topic_filter_stored);
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deserialize suback packet and return sub result.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_handle_suback(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc, count = 0;
|
||||
uint16_t packet_id = 0;
|
||||
int granted_qos;
|
||||
MQTTEventMsg msg = {MQTT_EVENT_SUBSCRIBE_SUCCESS, NULL};
|
||||
SubTopicHandle sub_handle = {0};
|
||||
MQTTEventType event_type = MQTT_EVENT_SUBSCRIBE_SUCCESS;
|
||||
|
||||
rc = mqtt_suback_packet_deserialize(client->read_buf, client->read_buf_size, 1, &count, &packet_id, &granted_qos);
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
msg.msg = (void *)(uintptr_t)packet_id;
|
||||
|
||||
// pop sub info and get sub handle
|
||||
_pop_sub_info_from_list(client, packet_id, &sub_handle);
|
||||
if (!sub_handle.topic_filter) {
|
||||
Log_w("can't get sub handle from list!");
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS); // in case of resubscribe when reconnect
|
||||
}
|
||||
|
||||
// check return code in SUBACK packet: 0x00(QOS0, SUCCESS),0x01(QOS1, SUCCESS),0x02(QOS2,SUCCESS),0x80(Failure)
|
||||
if (0x80 == granted_qos) {
|
||||
msg.event_type = MQTT_EVENT_SUBSCRIBE_NACK;
|
||||
event_type = MQTT_EVENT_SUBSCRIBE_NACK;
|
||||
Log_e("MQTT SUBSCRIBE failed, packet_id: %u topic: %s", packet_id, sub_handle.topic_filter);
|
||||
_remove_sub_handle_from_array(client, sub_handle.topic_filter);
|
||||
rc = QCLOUD_ERR_MQTT_SUB;
|
||||
}
|
||||
|
||||
if (MQTT_EVENT_SUBSCRIBE_SUCCESS == msg.event_type) {
|
||||
_set_sub_handle_status_to_array(client, sub_handle.topic_filter, SUB_ACK_RECEIVED);
|
||||
}
|
||||
|
||||
// notify this event to user callback
|
||||
if (client->event_handle.h_fp) {
|
||||
client->event_handle.h_fp(client, client->event_handle.context, &msg);
|
||||
}
|
||||
|
||||
// notify this event to topic subscriber
|
||||
if (sub_handle.params.on_sub_event_handler) {
|
||||
sub_handle.params.on_sub_event_handler(client, event_type, sub_handle.params.user_data);
|
||||
}
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Serialize and send unsubscribe packet.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic to unsubscribe
|
||||
* @return >=0 packet id, < 0 for failed @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_unsubscribe(QcloudIotClient *client, const char *topic_filter)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc, packet_len;
|
||||
uint16_t packet_id;
|
||||
|
||||
void *node = NULL;
|
||||
SubTopicHandle sub_handle;
|
||||
memset(&sub_handle, 0, sizeof(SubTopicHandle));
|
||||
|
||||
// remove from sub handle
|
||||
if (!_remove_sub_handle_from_array(client, topic_filter)) {
|
||||
Log_w("subscription does not exists: %s", topic_filter);
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_UNSUB_FAIL);
|
||||
}
|
||||
|
||||
// topic filter should be valid in the whole sub life
|
||||
char *topic_filter_stored = HAL_Malloc(strlen(topic_filter) + 1);
|
||||
if (!topic_filter_stored) {
|
||||
Log_e("malloc failed");
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
|
||||
}
|
||||
strncpy(topic_filter_stored, topic_filter, strlen(topic_filter) + 1);
|
||||
sub_handle.topic_filter = topic_filter_stored;
|
||||
|
||||
packet_id = get_next_packet_id(client);
|
||||
Log_d("unsubscribe topic_name=%s|packet_id=%d", topic_filter_stored, packet_id);
|
||||
|
||||
HAL_MutexLock(client->lock_write_buf);
|
||||
packet_len = mqtt_unsubscribe_packet_serialize(client->write_buf, client->write_buf_size, packet_id, 1,
|
||||
&topic_filter_stored);
|
||||
if (packet_len < 0) {
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
rc = packet_len == MQTT_ERR_SHORT_BUFFER ? QCLOUD_ERR_BUF_TOO_SHORT : QCLOUD_ERR_FAILURE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// add node into sub ack wait list
|
||||
rc = _push_sub_info_to_list(client, packet_len, packet_id, UNSUBSCRIBE, &sub_handle, &node);
|
||||
if (rc) {
|
||||
Log_e("push unsubscribe info failed!");
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* send the unsubscribe packet */
|
||||
rc = send_mqtt_packet(client, packet_len);
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
if (rc) {
|
||||
utils_list_remove(client->list_sub_wait_ack, node);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
IOT_FUNC_EXIT_RC(packet_id);
|
||||
exit:
|
||||
_clear_sub_handle(&sub_handle);
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deserialize unsuback packet and remove from list.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_handle_unsuback(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc;
|
||||
uint16_t packet_id = 0;
|
||||
SubTopicHandle sub_handle = {0};
|
||||
MQTTEventMsg msg;
|
||||
|
||||
rc = mqtt_unsuback_packet_deserialize(client->read_buf, client->read_buf_size, &packet_id);
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
_pop_sub_info_from_list(client, packet_id, &sub_handle);
|
||||
_clear_sub_handle(&sub_handle);
|
||||
|
||||
if (client->event_handle.h_fp) {
|
||||
msg.event_type = MQTT_EVENT_UNSUBSCRIBE_SUCCESS;
|
||||
msg.msg = (void *)(uintptr_t)packet_id;
|
||||
client->event_handle.h_fp(client, client->event_handle.context, &msg);
|
||||
}
|
||||
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Process suback waiting timeout.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
void qcloud_iot_mqtt_check_sub_timeout(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
utils_list_process(client->list_sub_wait_ack, LIST_HEAD, _sub_wait_list_process_check_timeout, client);
|
||||
IOT_FUNC_EXIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resubscribe topic when reconnect.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_resubscribe(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc, packet_len, qos, itr = 0;
|
||||
uint16_t packet_id;
|
||||
SubTopicHandle *sub_handle;
|
||||
|
||||
for (itr = 0; itr < MAX_MESSAGE_HANDLERS; itr++) {
|
||||
sub_handle = &client->sub_handles[itr];
|
||||
if (!sub_handle->topic_filter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
packet_id = get_next_packet_id(client);
|
||||
Log_d("subscribe topic_name=%s|packet_id=%d", sub_handle->topic_filter, packet_id);
|
||||
|
||||
HAL_MutexLock(client->lock_write_buf);
|
||||
qos = sub_handle->params.qos;
|
||||
packet_len = mqtt_subscribe_packet_serialize(client->write_buf, client->write_buf_size, packet_id, 1,
|
||||
&sub_handle->topic_filter, &qos);
|
||||
if (packet_len < 0) {
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
continue;
|
||||
}
|
||||
|
||||
// send packet
|
||||
rc = send_mqtt_packet(client, packet_len);
|
||||
HAL_MutexUnlock(client->lock_write_buf);
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
}
|
||||
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return if topic is sub ready.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter
|
||||
* @return true for ready
|
||||
* @return false for not ready
|
||||
*/
|
||||
bool qcloud_iot_mqtt_is_sub_ready(QcloudIotClient *client, const char *topic_filter)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int i = 0;
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
|
||||
if ((client->sub_handles[i].topic_filter && !strcmp(client->sub_handles[i].topic_filter, topic_filter)) ||
|
||||
strstr(topic_filter, "/#") || strstr(topic_filter, "/+")) {
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
return client->sub_handles[i].status == SUB_ACK_RECEIVED;
|
||||
}
|
||||
}
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
IOT_FUNC_EXIT_RC(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get usr data, usr should handle lock/unlock usrdata itself in callback and caller.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic_filter topic filter
|
||||
* @return NULL or user data
|
||||
*/
|
||||
void *qcloud_iot_mqtt_get_subscribe_usr_data(QcloudIotClient *client, const char *topic_filter)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int i = 0;
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
|
||||
if ((client->sub_handles[i].topic_filter && !strcmp(client->sub_handles[i].topic_filter, topic_filter)) ||
|
||||
strstr(topic_filter, "/#") || strstr(topic_filter, "/+")) {
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
return client->sub_handles[i].params.user_data;
|
||||
}
|
||||
}
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
IOT_FUNC_EXIT_RC(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear sub handle array.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
void qcloud_iot_mqtt_sub_handle_array_clear(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int i;
|
||||
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
|
||||
/* notify this event to topic subscriber */
|
||||
if (client->sub_handles[i].topic_filter && client->sub_handles[i].params.on_sub_event_handler) {
|
||||
client->sub_handles[i].params.on_sub_event_handler(client, MQTT_EVENT_CLIENT_DESTROY,
|
||||
client->sub_handles[i].params.user_data);
|
||||
}
|
||||
_clear_sub_handle(&client->sub_handles[i]);
|
||||
}
|
||||
|
||||
IOT_FUNC_EXIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear suback wait list and clear sub handle.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
void qcloud_iot_mqtt_suback_wait_list_clear(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
utils_list_process(client->list_sub_wait_ack, LIST_HEAD, _sub_wait_list_process_clear, client);
|
||||
IOT_FUNC_EXIT;
|
||||
}
|
@@ -0,0 +1,553 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file mqtt_client_yield.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-05-28
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-05-28 <td>1.0 <td>fancyxu <td>first commit
|
||||
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "mqtt_client.h"
|
||||
|
||||
/**
|
||||
* @brief Remain waiting time after MQTT data is received (unit: ms)
|
||||
*
|
||||
*/
|
||||
#define QCLOUD_IOT_MQTT_MAX_REMAIN_WAIT_MS (100)
|
||||
|
||||
/**
|
||||
* @brief Read one byte from network for mqtt packet header except remaining length.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] timeout_ms timeout to read
|
||||
* @param[out] packet_type packet type to get
|
||||
* @param[in,out] pptr pointer to buf pointer
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _read_packet_header(QcloudIotClient *client, uint32_t timeout_ms, uint8_t *packet_type, uint8_t **pptr)
|
||||
{
|
||||
int rc = 0;
|
||||
size_t read_len = 0;
|
||||
|
||||
// refresh timeout
|
||||
timeout_ms = timeout_ms <= 0 ? 1 : timeout_ms;
|
||||
|
||||
rc = client->network_stack.read(&(client->network_stack), *pptr, 1, timeout_ms, &read_len);
|
||||
if (rc == QCLOUD_ERR_SSL_NOTHING_TO_READ || rc == QCLOUD_ERR_TCP_NOTHING_TO_READ) {
|
||||
return QCLOUD_ERR_MQTT_NOTHING_TO_READ;
|
||||
}
|
||||
|
||||
// get packet type
|
||||
*packet_type = (**pptr & 0xf0) >> 4;
|
||||
(*pptr)++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read 1~4 bytes from network for mqtt packet header remaining length.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] timeout_ms timeout to read
|
||||
* @param[out] plen read len
|
||||
* @param[in,out] pptr pointer to buf pointer
|
||||
* @return int
|
||||
*/
|
||||
static int _read_packet_remaining_len(QcloudIotClient *client, uint32_t timeout_ms, uint32_t *plen, uint8_t **pptr)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
|
||||
uint8_t *buf = *pptr;
|
||||
uint8_t c = 0;
|
||||
uint32_t multiplier = 1;
|
||||
uint32_t count = 0;
|
||||
uint32_t len = 0;
|
||||
size_t rlen = 0;
|
||||
|
||||
// refresh timeout
|
||||
timeout_ms = timeout_ms <= 0 ? 1 : timeout_ms;
|
||||
timeout_ms += QCLOUD_IOT_MQTT_MAX_REMAIN_WAIT_MS;
|
||||
|
||||
do {
|
||||
if (++count > MAX_NO_OF_REMAINING_LENGTH_BYTES) {
|
||||
/* bad data */
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_PACKET_READ)
|
||||
}
|
||||
|
||||
if (client->network_stack.read(&(client->network_stack), buf, 1, timeout_ms, &rlen)) {
|
||||
/* The value argument is the important value. len is just used temporarily
|
||||
* and never used by the calling function for anything else */
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
|
||||
}
|
||||
|
||||
c = *buf++;
|
||||
len += (c & 127) * multiplier;
|
||||
multiplier *= 128;
|
||||
} while (c & 128);
|
||||
|
||||
if (plen) {
|
||||
*plen = len;
|
||||
}
|
||||
|
||||
*pptr += count;
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Discard the packet for mqtt read buf not enough.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] timeout_ms timeout to read
|
||||
* @param[in] rem_len remaining length to read
|
||||
*/
|
||||
static void _discard_packet_for_short_buf(QcloudIotClient *client, uint32_t timeout_ms, uint32_t rem_len)
|
||||
{
|
||||
int rc;
|
||||
size_t bytes_to_be_read, total_bytes_read = 0;
|
||||
size_t read_len;
|
||||
|
||||
timeout_ms = timeout_ms <= 0 ? 1 : timeout_ms;
|
||||
timeout_ms += QCLOUD_IOT_MQTT_MAX_REMAIN_WAIT_MS;
|
||||
|
||||
bytes_to_be_read = client->read_buf_size;
|
||||
do {
|
||||
rc = client->network_stack.read(&(client->network_stack), client->read_buf, bytes_to_be_read, timeout_ms,
|
||||
&read_len);
|
||||
if (rc) {
|
||||
break;
|
||||
}
|
||||
|
||||
total_bytes_read += read_len;
|
||||
bytes_to_be_read =
|
||||
(rem_len - total_bytes_read) >= client->read_buf_size ? client->read_buf_size : rem_len - total_bytes_read;
|
||||
} while (total_bytes_read < rem_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read rem_len bytes from network for mqtt packet payload.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] timeout_ms timeout to read
|
||||
* @param[in] rem_len length to read
|
||||
* @param[out] buf buffer to save payload
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _read_packet_payload(QcloudIotClient *client, uint32_t timeout_ms, uint32_t rem_len, uint8_t *buf)
|
||||
{
|
||||
int rc;
|
||||
size_t read_len = 0;
|
||||
|
||||
timeout_ms = timeout_ms <= 0 ? 1 : timeout_ms;
|
||||
timeout_ms += QCLOUD_IOT_MQTT_MAX_REMAIN_WAIT_MS;
|
||||
|
||||
rc = client->network_stack.read(&(client->network_stack), buf, rem_len, timeout_ms, &read_len);
|
||||
if (rc) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (read_len != rem_len) {
|
||||
#ifdef AUTH_WITH_NO_TLS
|
||||
return QCLOUD_ERR_TCP_READ_TIMEOUT;
|
||||
#else
|
||||
return QCLOUD_ERR_SSL_READ_TIMEOUT;
|
||||
#endif
|
||||
}
|
||||
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read MQTT packet from network stack
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in,out] timer timeout timer
|
||||
* @param[out] packet_type MQTT packet type
|
||||
* @return @see IotReturnCode
|
||||
*
|
||||
* @note
|
||||
* 1. read 1st byte in fixed header and check if valid
|
||||
* 2. read the remaining length
|
||||
* 3. read payload according to remaining length
|
||||
*/
|
||||
static int _read_mqtt_packet(QcloudIotClient *client, QcloudIotTimer *timer, uint8_t *packet_type)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc = 0;
|
||||
uint32_t rem_len = 0, packet_len = 0;
|
||||
uint8_t *packet_read_buf = client->read_buf;
|
||||
|
||||
// 1. read 1st byte in fixed header and check if valid
|
||||
rc = _read_packet_header(client, IOT_Timer_Remain(timer), packet_type, &packet_read_buf);
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
// 2. read the remaining length
|
||||
rc = _read_packet_remaining_len(client, IOT_Timer_Remain(timer), &rem_len, &packet_read_buf);
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
// if read buffer is not enough to read the remaining length, discard the packet
|
||||
packet_len = packet_read_buf - client->read_buf + rem_len;
|
||||
if (packet_len >= client->read_buf_size) {
|
||||
_discard_packet_for_short_buf(client, IOT_Timer_Remain(timer), rem_len);
|
||||
Log_e("MQTT Recv buffer not enough: %lu < %d", client->read_buf_size, rem_len);
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_BUF_TOO_SHORT);
|
||||
}
|
||||
|
||||
// 3. read payload according to remaining length
|
||||
if (rem_len > 0) {
|
||||
rc = _read_packet_payload(client, IOT_Timer_Remain(timer), rem_len, packet_read_buf);
|
||||
}
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset ping counter & ping timer.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
*/
|
||||
static void _handle_pingresp_packet(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
client->is_ping_outstanding = 0;
|
||||
IOT_Timer_Countdown(&client->ping_timer, client->options.keep_alive_interval);
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
|
||||
IOT_FUNC_EXIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read a mqtt packet from network and handle it.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt_client
|
||||
* @param[in] timer timer for operation
|
||||
* @param[out] packet_type packet type of packet read
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _cycle_for_read(QcloudIotClient *client, QcloudIotTimer *timer, uint8_t *packet_type)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
|
||||
int rc;
|
||||
|
||||
/* read the socket, see what work is due */
|
||||
rc = _read_mqtt_packet(client, timer, packet_type);
|
||||
if (QCLOUD_ERR_MQTT_NOTHING_TO_READ == rc) {
|
||||
/* Nothing to read, not a cycle failure */
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
switch (*packet_type) {
|
||||
case CONNACK:
|
||||
case PUBREC:
|
||||
case PUBREL:
|
||||
case PUBCOMP:
|
||||
case PINGRESP:
|
||||
break;
|
||||
case PUBACK:
|
||||
rc = qcloud_iot_mqtt_handle_puback(client);
|
||||
break;
|
||||
case SUBACK:
|
||||
rc = qcloud_iot_mqtt_handle_suback(client);
|
||||
break;
|
||||
case UNSUBACK:
|
||||
rc = qcloud_iot_mqtt_handle_unsuback(client);
|
||||
break;
|
||||
case PUBLISH:
|
||||
rc = qcloud_iot_mqtt_handle_publish(client);
|
||||
break;
|
||||
default:
|
||||
// Either unknown packet type or failure occurred should not happen
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_RX_MESSAGE_INVAL);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (*packet_type) {
|
||||
// Recv below msgs are all considered as PING OK
|
||||
case PUBACK:
|
||||
case SUBACK:
|
||||
case UNSUBACK:
|
||||
case PINGRESP:
|
||||
_handle_pingresp_packet(client);
|
||||
break;
|
||||
|
||||
// Recv downlink pub means link is OK but we still need to send PING request
|
||||
case PUBLISH:
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
client->is_ping_outstanding = 0;
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
break;
|
||||
}
|
||||
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set reconnect wait interval if auto conect is enable.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
static void _set_reconnect_wait_interval(QcloudIotClient *client)
|
||||
{
|
||||
client->counter_network_disconnected++;
|
||||
if (client->auto_connect_enable) {
|
||||
srand(IOT_Timer_CurrentSec());
|
||||
// range: 1000 - 2000 ms, in 10ms unit
|
||||
client->current_reconnect_wait_interval = (rand() % 100 + 100) * 10;
|
||||
IOT_Timer_CountdownMs(&(client->reconnect_delay_timer), client->current_reconnect_wait_interval);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle disconnect if connection is error.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
static void _handle_disconnect(QcloudIotClient *client)
|
||||
{
|
||||
int rc;
|
||||
MQTTEventMsg msg;
|
||||
|
||||
if (!get_client_conn_state(client)) {
|
||||
_set_reconnect_wait_interval(client);
|
||||
return;
|
||||
}
|
||||
|
||||
rc = qcloud_iot_mqtt_disconnect(client);
|
||||
// disconnect network stack by force
|
||||
if (rc) {
|
||||
client->network_stack.disconnect(&(client->network_stack));
|
||||
set_client_conn_state(client, NOTCONNECTED);
|
||||
}
|
||||
|
||||
Log_e("disconnect MQTT for some reasons..");
|
||||
|
||||
// notify event
|
||||
if (client->event_handle.h_fp) {
|
||||
msg.event_type = MQTT_EVENT_DISCONNECT;
|
||||
msg.msg = NULL;
|
||||
client->event_handle.h_fp(client, client->event_handle.context, &msg);
|
||||
}
|
||||
|
||||
// exceptional disconnection
|
||||
client->was_manually_disconnected = 0;
|
||||
_set_reconnect_wait_interval(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle reconnect.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _handle_reconnect(QcloudIotClient *client)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
|
||||
int rc = QCLOUD_RET_MQTT_RECONNECTED;
|
||||
|
||||
MQTTEventMsg msg;
|
||||
|
||||
// reconnect control by delay timer (increase interval exponentially )
|
||||
if (!IOT_Timer_Expired(&(client->reconnect_delay_timer))) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT);
|
||||
}
|
||||
|
||||
rc = qcloud_iot_mqtt_attempt_reconnect(client);
|
||||
if (QCLOUD_RET_MQTT_RECONNECTED == rc) {
|
||||
Log_e("attempt to reconnect success.");
|
||||
// notify event
|
||||
if (client->event_handle.h_fp) {
|
||||
msg.event_type = MQTT_EVENT_RECONNECT;
|
||||
msg.msg = NULL;
|
||||
client->event_handle.h_fp(client, client->event_handle.context, &msg);
|
||||
}
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
Log_e("attempt to reconnect failed, errCode: %d", rc);
|
||||
|
||||
client->current_reconnect_wait_interval *= 2;
|
||||
|
||||
if (MAX_RECONNECT_WAIT_INTERVAL < client->current_reconnect_wait_interval) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_RECONNECT_TIMEOUT);
|
||||
}
|
||||
|
||||
IOT_Timer_CountdownMs(&(client->reconnect_delay_timer), client->current_reconnect_wait_interval);
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle MQTT keep alive (hearbeat with server).
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _mqtt_keep_alive(QcloudIotClient *client)
|
||||
{
|
||||
#define MQTT_PING_RETRY_TIMES 2
|
||||
#define MQTT_PING_SEND_RETRY_TIMES 3
|
||||
|
||||
IOT_FUNC_ENTRY;
|
||||
int rc = 0;
|
||||
|
||||
if (0 == client->options.keep_alive_interval) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
if (!IOT_Timer_Expired(&client->ping_timer)) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
if (client->is_ping_outstanding >= MQTT_PING_RETRY_TIMES) {
|
||||
// reaching here means we haven't received any MQTT packet for a long time (keep_alive_interval)
|
||||
Log_e("Fail to recv MQTT msg. Something wrong with the connection.");
|
||||
_handle_disconnect(client);
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_NO_CONN);
|
||||
}
|
||||
|
||||
// there is no ping outstanding - send one
|
||||
rc = qcloud_iot_mqtt_pingreq(client, MQTT_PING_SEND_RETRY_TIMES);
|
||||
if (rc) {
|
||||
// if sending a PING fails, propably the connection is not OK and we decide to disconnect and begin reconnection
|
||||
// attempts
|
||||
Log_e("Fail to send PING request. Something wrong with the connection.");
|
||||
_handle_disconnect(client);
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_NO_CONN);
|
||||
}
|
||||
|
||||
// start a timer to wait for PINGRESP from server
|
||||
HAL_MutexLock(client->lock_generic);
|
||||
client->is_ping_outstanding++;
|
||||
IOT_Timer_CountdownMs(&client->ping_timer, client->command_timeout_ms);
|
||||
HAL_MutexUnlock(client->lock_generic);
|
||||
Log_d("PING request %u has been sent...", client->is_ping_outstanding);
|
||||
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check connection and keep alive state, read/handle MQTT message in synchronized way.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] timeout_ms timeout value (unit: ms) for this operation
|
||||
*
|
||||
* @return QCLOUD_RET_SUCCESS when success, QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT when try reconnecting, others @see
|
||||
* IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_yield(QcloudIotClient *client, uint32_t timeout_ms)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
|
||||
int rc = QCLOUD_RET_SUCCESS;
|
||||
uint8_t packet_type;
|
||||
QcloudIotTimer timer;
|
||||
|
||||
// 1. check if manually disconnect
|
||||
if (!get_client_conn_state(client) && client->was_manually_disconnected == 1) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_RET_MQTT_MANUALLY_DISCONNECTED);
|
||||
}
|
||||
|
||||
// 2. check connection state and if auto reconnect is enabled
|
||||
if (!get_client_conn_state(client) && client->auto_connect_enable == 0) {
|
||||
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_NO_CONN);
|
||||
}
|
||||
|
||||
// 3. main loop for packet reading/handling and keep alive maintainance
|
||||
IOT_Timer_CountdownMs(&timer, timeout_ms);
|
||||
|
||||
while (!IOT_Timer_Expired(&timer)) {
|
||||
// handle reconnect
|
||||
if (!get_client_conn_state(client)) {
|
||||
if (client->current_reconnect_wait_interval > MAX_RECONNECT_WAIT_INTERVAL) {
|
||||
rc = QCLOUD_ERR_MQTT_RECONNECT_TIMEOUT;
|
||||
break;
|
||||
}
|
||||
rc = _handle_reconnect(client);
|
||||
continue;
|
||||
}
|
||||
|
||||
// read and handle packet
|
||||
rc = _cycle_for_read(client, &timer, &packet_type);
|
||||
switch (rc) {
|
||||
case QCLOUD_RET_SUCCESS:
|
||||
// check list of wait publish ACK to remove node that is ACKED or timeout
|
||||
qcloud_iot_mqtt_check_pub_timeout(client);
|
||||
// check list of wait subscribe(or unsubscribe) ACK to remove node that is ACKED or timeout
|
||||
qcloud_iot_mqtt_check_sub_timeout(client);
|
||||
|
||||
rc = _mqtt_keep_alive(client);
|
||||
if (rc) {
|
||||
IOT_FUNC_EXIT_RC(client->auto_connect_enable ? QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT : rc);
|
||||
}
|
||||
break;
|
||||
case QCLOUD_ERR_SSL_READ_TIMEOUT:
|
||||
case QCLOUD_ERR_SSL_READ:
|
||||
case QCLOUD_ERR_TCP_PEER_SHUTDOWN:
|
||||
case QCLOUD_ERR_TCP_READ_TIMEOUT:
|
||||
case QCLOUD_ERR_TCP_READ_FAIL:
|
||||
Log_e("network read failed, rc: %d. MQTT Disconnect.", rc);
|
||||
_handle_disconnect(client);
|
||||
IOT_FUNC_EXIT_RC(client->auto_connect_enable ? QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT
|
||||
: QCLOUD_ERR_MQTT_NO_CONN);
|
||||
break;
|
||||
default: // others, just return
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
}
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wait read specific mqtt packet, such as connack.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] packet_type packet type except to read
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int qcloud_iot_mqtt_wait_for_read(QcloudIotClient *client, uint8_t packet_type)
|
||||
{
|
||||
IOT_FUNC_ENTRY;
|
||||
|
||||
int rc;
|
||||
uint8_t read_packet_type = 0;
|
||||
QcloudIotTimer timer;
|
||||
IOT_Timer_CountdownMs(&timer, client->command_timeout_ms);
|
||||
|
||||
do {
|
||||
if (IOT_Timer_Expired(&timer)) {
|
||||
rc = QCLOUD_ERR_MQTT_REQUEST_TIMEOUT;
|
||||
break;
|
||||
}
|
||||
rc = _cycle_for_read(client, &timer, &read_packet_type);
|
||||
} while (QCLOUD_RET_SUCCESS == rc && read_packet_type != packet_type);
|
||||
|
||||
IOT_FUNC_EXIT_RC(rc);
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
ColumnLimit: 120
|
||||
DerivePointerAlignment: true
|
||||
PointerAlignment: Left
|
||||
SortIncludes: true
|
||||
IncludeBlocks: Preserve
|
||||
IndentPPDirectives: AfterHash
|
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file test_mqtt_client.cc
|
||||
* @brief unittest for mqtt client
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-07-07
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-07-07 <td>1.0 <td>fancyxu <td>first commit
|
||||
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>support tls test
|
||||
* <tr><td>2021-07-12 <td>1.1 <td>fancyxu <td>fix connect twice in 5s error
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "mqtt_client_test.h"
|
||||
|
||||
namespace mqtt_client_unittest {
|
||||
|
||||
void MqttClientTest::SetUp() {
|
||||
LogHandleFunc func = DEFAULT_LOG_HANDLE_FUNCS;
|
||||
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
|
||||
|
||||
ASSERT_EQ(HAL_GetDevInfo(&device_info), 0);
|
||||
|
||||
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
|
||||
init_params.device_info = &device_info;
|
||||
|
||||
HAL_SleepMs(5000); // for iot hub can not connect twice in 5 s
|
||||
|
||||
client = IOT_MQTT_Construct(&init_params);
|
||||
ASSERT_NE(client, nullptr);
|
||||
}
|
||||
|
||||
void MqttClientTest::TearDown() {
|
||||
IOT_MQTT_Destroy(&client);
|
||||
utils_log_deinit();
|
||||
}
|
||||
|
||||
} // namespace mqtt_client_unittest
|
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file mqtt_client_test.h
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-07-18
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#ifndef IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_MQTT_CLIENT_TEST_MQTT_CLIENT_TEST_H_
|
||||
#define IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_MQTT_CLIENT_TEST_MQTT_CLIENT_TEST_H_
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "qcloud_iot_common.h"
|
||||
|
||||
namespace mqtt_client_unittest {
|
||||
|
||||
/**
|
||||
* @brief test fixture of mqtt client
|
||||
*
|
||||
*/
|
||||
class MqttClientTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override;
|
||||
|
||||
void TearDown() override;
|
||||
|
||||
void *client = NULL;
|
||||
DeviceInfo device_info;
|
||||
};
|
||||
|
||||
} // namespace mqtt_client_unittest
|
||||
|
||||
#endif // IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_MQTT_CLIENT_TEST_MQTT_CLIENT_TEST_H_
|
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file test_mqtt_client.cc
|
||||
* @brief unittest for mqtt client
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-07-07
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-07-07 <td>1.0 <td>fancyxu <td>first commit
|
||||
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>support tls test
|
||||
* <tr><td>2021-07-12 <td>1.1 <td>fancyxu <td>fix connect twice in 5s error
|
||||
* <tr><td>2021-07-18 <td>1.2 <td>fancyxu <td>remove MqttClientTest for common use
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "mqtt_client_test.h"
|
||||
|
||||
namespace mqtt_client_unittest {
|
||||
|
||||
/**
|
||||
* @brief Test subscribe.
|
||||
*
|
||||
*/
|
||||
TEST_F(MqttClientTest, subscribe) {
|
||||
int wait_cnt;
|
||||
char topic_name[MAX_SIZE_OF_CLOUD_TOPIC] = {0};
|
||||
HAL_Snprintf(topic_name, sizeof(topic_name), "%s/%s/data", device_info.product_id, device_info.device_name);
|
||||
|
||||
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
|
||||
|
||||
/**
|
||||
* @brief QOS0
|
||||
*
|
||||
*/
|
||||
wait_cnt = 10;
|
||||
sub_params.qos = QOS0;
|
||||
ASSERT_GE(IOT_MQTT_Subscribe(client, topic_name, &sub_params), 0);
|
||||
while (!IOT_MQTT_IsSubReady(client, topic_name) && (wait_cnt > 0)) {
|
||||
ASSERT_EQ(IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT), 0);
|
||||
wait_cnt--;
|
||||
}
|
||||
ASSERT_NE(wait_cnt, 0);
|
||||
|
||||
ASSERT_GE(IOT_MQTT_Unsubscribe(client, topic_name), 0);
|
||||
ASSERT_EQ(IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT), 0);
|
||||
|
||||
/**
|
||||
* @brief QOS1
|
||||
*
|
||||
*/
|
||||
wait_cnt = 10;
|
||||
sub_params.qos = QOS1;
|
||||
ASSERT_GE(IOT_MQTT_Subscribe(client, topic_name, &sub_params), 0);
|
||||
while (!IOT_MQTT_IsSubReady(client, topic_name) && (wait_cnt > 0)) {
|
||||
ASSERT_EQ(IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT), 0);
|
||||
wait_cnt--;
|
||||
}
|
||||
ASSERT_NE(wait_cnt, 0);
|
||||
|
||||
ASSERT_GE(IOT_MQTT_Unsubscribe(client, topic_name), 0);
|
||||
ASSERT_EQ(IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT), 0);
|
||||
|
||||
/**
|
||||
* @brief sub sync
|
||||
*
|
||||
*/
|
||||
ASSERT_GE(IOT_MQTT_SubscribeSync(client, topic_name, &sub_params), 0);
|
||||
ASSERT_GE(IOT_MQTT_Unsubscribe(client, topic_name), 0);
|
||||
ASSERT_EQ(IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test publish.
|
||||
*
|
||||
*/
|
||||
TEST_F(MqttClientTest, publish) {
|
||||
char topic_name[MAX_SIZE_OF_CLOUD_TOPIC] = {0};
|
||||
HAL_Snprintf(topic_name, sizeof(topic_name), "%s/%s/data", device_info.product_id, device_info.device_name);
|
||||
|
||||
char topic_content[] = "{\"action\": \"publish_test\", \"count\": \"0\"}";
|
||||
PublishParams pub_params = DEFAULT_PUB_PARAMS;
|
||||
|
||||
pub_params.payload = topic_content;
|
||||
pub_params.payload_len = strlen(topic_content);
|
||||
/**
|
||||
* @brief QOS0
|
||||
*
|
||||
*/
|
||||
pub_params.qos = QOS0;
|
||||
ASSERT_GE(IOT_MQTT_Publish(client, topic_name, &pub_params), 0);
|
||||
|
||||
/**
|
||||
* @brief QOS1
|
||||
*
|
||||
*/
|
||||
pub_params.qos = QOS1;
|
||||
ASSERT_GE(IOT_MQTT_Publish(client, topic_name, &pub_params), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test clean session.
|
||||
*
|
||||
*/
|
||||
TEST_F(MqttClientTest, clean_session) {
|
||||
IOT_MQTT_Destroy(&client);
|
||||
|
||||
HAL_SleepMs(5000); // for iot hub can not connect twice in 5 s
|
||||
|
||||
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
|
||||
|
||||
char topic_name[MAX_SIZE_OF_CLOUD_TOPIC] = {0};
|
||||
ASSERT_EQ(HAL_GetDevInfo(&device_info), 0);
|
||||
HAL_Snprintf(topic_name, sizeof(topic_name), "%s/%s/data", device_info.product_id, device_info.device_name);
|
||||
|
||||
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
|
||||
init_params.device_info = &device_info;
|
||||
init_params.clean_session = 0;
|
||||
|
||||
client = IOT_MQTT_Construct(&init_params);
|
||||
ASSERT_NE(client, nullptr);
|
||||
ASSERT_GE(IOT_MQTT_SubscribeSync(client, topic_name, &sub_params), 0);
|
||||
IOT_MQTT_Destroy(&client);
|
||||
|
||||
HAL_SleepMs(5000);
|
||||
|
||||
init_params.clean_session = 0;
|
||||
init_params.connect_when_construct = 0;
|
||||
init_params.default_subscribe = 1;
|
||||
|
||||
client = IOT_MQTT_Construct(&init_params);
|
||||
ASSERT_NE(client, nullptr);
|
||||
ASSERT_GE(IOT_MQTT_SubscribeSync(client, topic_name, &sub_params), 0);
|
||||
ASSERT_EQ(IOT_MQTT_Connect(client), 0);
|
||||
}
|
||||
|
||||
} // namespace mqtt_client_unittest
|
@@ -0,0 +1,13 @@
|
||||
file(GLOB src_ota_mqtt ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
|
||||
set(src_services ${src_services} ${src_ota_mqtt} PARENT_SCOPE)
|
||||
|
||||
if( ${CONFIG_COMPILE_SAMPLE} STREQUAL "ON")
|
||||
file(GLOB src_ota_mqtt_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/ota_mqtt_sample.c)
|
||||
add_executable(ota_mqtt_sample ${src_ota_mqtt_sample})
|
||||
target_link_libraries(ota_mqtt_sample ${libsdk})
|
||||
endif()
|
||||
|
||||
if( ${CONFIG_IOT_TEST} STREQUAL "ON")
|
||||
file(GLOB src_unit_test ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc)
|
||||
set(src_test ${src_test} ${src_unit_test} PARENT_SCOPE)
|
||||
endif()
|
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file broadcast_sample.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-07-18
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "qcloud_iot_common.h"
|
||||
|
||||
#include "utils_log.h"
|
||||
|
||||
/**
|
||||
* @brief MQTT event callback, @see MQTTEventHandleFun
|
||||
*
|
||||
* @param[in] client pointer to mqtt client
|
||||
* @param[in] handle_context context
|
||||
* @param[in] msg msg
|
||||
*/
|
||||
static void _mqtt_event_handler(void *client, void *handle_context, MQTTEventMsg *msg)
|
||||
{
|
||||
MQTTMessage *mqtt_message = (MQTTMessage *)msg->msg;
|
||||
uintptr_t packet_id = (uintptr_t)msg->msg;
|
||||
|
||||
switch (msg->event_type) {
|
||||
case MQTT_EVENT_UNDEF:
|
||||
Log_i("undefined event occur.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_DISCONNECT:
|
||||
Log_i("MQTT disconnect.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_RECONNECT:
|
||||
Log_i("MQTT reconnect.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_RECEIVED:
|
||||
Log_i("topic message arrived but without any related handle: topic=%.*s, topic_msg=%.*s",
|
||||
mqtt_message->topic_len, STRING_PTR_PRINT_SANITY_CHECK(mqtt_message->topic_name),
|
||||
mqtt_message->payload_len, STRING_PTR_PRINT_SANITY_CHECK((char *)mqtt_message->payload));
|
||||
break;
|
||||
case MQTT_EVENT_SUBSCRIBE_SUCCESS:
|
||||
Log_i("subscribe success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBE_TIMEOUT:
|
||||
Log_i("subscribe wait ack timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBE_NACK:
|
||||
Log_i("subscribe nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_SUCCESS:
|
||||
Log_i("unsubscribe success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_TIMEOUT:
|
||||
Log_i("unsubscribe timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_NACK:
|
||||
Log_i("unsubscribe nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_SUCCESS:
|
||||
Log_i("publish success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_TIMEOUT:
|
||||
Log_i("publish timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_NACK:
|
||||
Log_i("publish nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
default:
|
||||
Log_i("Should NOT arrive here.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Setup MQTT construct parameters.
|
||||
*
|
||||
* @param[in,out] initParams @see MQTTInitParams
|
||||
* @param[in] device_info @see DeviceInfo
|
||||
*/
|
||||
static void _setup_connect_init_params(MQTTInitParams *init_params, DeviceInfo *device_info)
|
||||
{
|
||||
init_params->device_info = device_info;
|
||||
init_params->event_handle.h_fp = _mqtt_event_handler;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// OTA function
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
int _firmware_download(void *client, char *buf, int buf_len, UtilsJsonValue url, UtilsJsonValue md5sum,
|
||||
uint32_t file_size, const char *version)
|
||||
{
|
||||
Log_i("downloading firmware:url=%.*s|md5sum=%.*s|file_size=%u", url.value_len, url.value, md5sum.value_len,
|
||||
md5sum.value, file_size);
|
||||
IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_DOWNLOADING, 0, version);
|
||||
IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_DOWNLOADING, 50, version);
|
||||
IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_DOWNLOADING, 100, version);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void _firmware_burn(void *client, char *buf, int buf_len, const char *version)
|
||||
{
|
||||
IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_UPGRADE_BEGIN, 0, version);
|
||||
IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_UPGRADE_SUCCESS, 0, version);
|
||||
IOT_OTA_ReportVersion(client, buf, buf_len, version);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// OTA callback
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void _update_firmware_callback(UtilsJsonValue version, UtilsJsonValue url, UtilsJsonValue md5sum, uint32_t file_size,
|
||||
void *usr_data)
|
||||
{
|
||||
char firmware_version[64] = {0};
|
||||
char buf[256];
|
||||
int buf_len = sizeof(buf);
|
||||
|
||||
Log_i("recv firmware: version=%.*s|url=%.*s|md5sum=%.*s|file_size=%u", version.value_len, version.value,
|
||||
url.value_len, url.value, md5sum.value_len, md5sum.value, file_size);
|
||||
strncpy(firmware_version, version.value, version.value_len);
|
||||
|
||||
int rc = _firmware_download(usr_data, buf, buf_len, url, md5sum, file_size, firmware_version);
|
||||
if (rc) {
|
||||
IOT_OTA_ReportProgress(usr_data, buf, buf_len, IOT_OTA_REPORT_TYPE_UPGRADE_FAIL, 0, firmware_version);
|
||||
return;
|
||||
}
|
||||
_firmware_burn(usr_data, buf, buf_len, firmware_version);
|
||||
}
|
||||
|
||||
void _report_version_reply_callback(int result_code, void *usr_data)
|
||||
{
|
||||
Log_i("recv code=%d", result_code);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Main
|
||||
// ----------------------------------------------------------------------------
|
||||
static int sg_main_exit = 0;
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void _main_exit(int sig)
|
||||
{
|
||||
Log_e("demo exit by signal:%d\n", sig);
|
||||
sg_main_exit = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
#ifdef __linux__
|
||||
signal(SIGINT, _main_exit);
|
||||
#endif
|
||||
|
||||
int rc;
|
||||
|
||||
char buf[1024];
|
||||
|
||||
// init log level
|
||||
LogHandleFunc func = DEFAULT_LOG_HANDLE_FUNCS;
|
||||
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
|
||||
|
||||
DeviceInfo device_info;
|
||||
rc = HAL_GetDevInfo((void *)&device_info);
|
||||
if (rc) {
|
||||
Log_e("get device info failed: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// init connection
|
||||
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
|
||||
_setup_connect_init_params(&init_params, &device_info);
|
||||
|
||||
// create MQTT client and connect with server
|
||||
void *client = IOT_MQTT_Construct(&init_params);
|
||||
if (client) {
|
||||
Log_i("Cloud Device Construct Success");
|
||||
} else {
|
||||
Log_e("MQTT Construct failed!");
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
IotOTAUpdateCallback ota_callback = {
|
||||
.update_firmware_callback = _update_firmware_callback,
|
||||
.report_version_reply_callback = _report_version_reply_callback,
|
||||
};
|
||||
rc = IOT_OTA_Init(client, ota_callback, client);
|
||||
if (rc) {
|
||||
Log_e("OTA init failed!, rc=%d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = IOT_OTA_ReportVersion(client, buf, sizeof(buf), QCLOUD_IOT_DEVICE_SDK_VERSION);
|
||||
if (rc) {
|
||||
Log_e("OTA report version failed!, rc=%d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
do {
|
||||
rc = IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT);
|
||||
switch (rc) {
|
||||
case QCLOUD_RET_SUCCESS:
|
||||
break;
|
||||
case QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT:
|
||||
continue;
|
||||
case QCLOUD_RET_MQTT_RECONNECTED:
|
||||
break;
|
||||
default:
|
||||
Log_e("Exit loop caused of errCode:%d", rc);
|
||||
goto exit;
|
||||
}
|
||||
} while (!sg_main_exit);
|
||||
exit:
|
||||
IOT_OTA_Deinit(client);
|
||||
rc = IOT_MQTT_Destroy(&client);
|
||||
utils_log_deinit();
|
||||
return rc;
|
||||
}
|
@@ -0,0 +1,329 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file ota_mqtt.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-10-18
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-10-18 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "qcloud_iot_ota.h"
|
||||
|
||||
/**
|
||||
* @brief Context of OTA update topic, callback and user data.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
IotOTAUpdateCallback callback;
|
||||
void *usr_data;
|
||||
} OTAUpdateContext;
|
||||
|
||||
/**
|
||||
* @brief OTA update type.
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
OTA_UPDATE_TYPE_REPORT_VERSION_RSP = 0,
|
||||
OTA_UPDATE_TYPE_UPDATE_FIRMWARE,
|
||||
} OTAUpdateType;
|
||||
|
||||
/**
|
||||
* @brief Parse payload and callback.
|
||||
*
|
||||
* @param[in] type @see OTAUpdateType
|
||||
* @param[in] message message from cloud
|
||||
* @param[in] callback callback for user
|
||||
* @param[in,out] usr_data user data used in callback
|
||||
*/
|
||||
static void _parse_update_payload_and_callback(OTAUpdateType type, const MQTTMessage *message,
|
||||
const IotOTAUpdateCallback *callback, void *usr_data)
|
||||
{
|
||||
int rc, result_code;
|
||||
uint32_t file_size = 0;
|
||||
UtilsJsonValue version, md5sum, url, value_file_size, value_result_code;
|
||||
|
||||
// callback
|
||||
switch (type) {
|
||||
case OTA_UPDATE_TYPE_REPORT_VERSION_RSP:
|
||||
if (!callback->report_version_reply_callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
rc = utils_json_value_get("result_code", strlen("result_code"), message->payload_str, message->payload_len,
|
||||
&value_result_code);
|
||||
if (rc) {
|
||||
goto error;
|
||||
}
|
||||
rc = utils_json_value_data_get(value_result_code, UTILS_JSON_VALUE_TYPE_INT32, &result_code);
|
||||
if (rc) {
|
||||
goto error;
|
||||
}
|
||||
callback->report_version_reply_callback(result_code, usr_data);
|
||||
break;
|
||||
case OTA_UPDATE_TYPE_UPDATE_FIRMWARE:
|
||||
if (!callback->update_firmware_callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
rc = utils_json_value_get("version", strlen("version"), message->payload_str, message->payload_len,
|
||||
&version);
|
||||
rc |= utils_json_value_get("url", strlen("url"), message->payload_str, message->payload_len, &url);
|
||||
rc |= utils_json_value_get("md5sum", strlen("md5sum"), message->payload_str, message->payload_len, &md5sum);
|
||||
rc |= utils_json_value_get("file_size", strlen("file_size"), message->payload_str, message->payload_len,
|
||||
&value_file_size);
|
||||
rc |= utils_json_value_data_get(value_file_size, UTILS_JSON_VALUE_TYPE_UINT32, &file_size);
|
||||
if (rc) {
|
||||
goto error;
|
||||
}
|
||||
callback->update_firmware_callback(version, url, md5sum, file_size, usr_data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
error:
|
||||
Log_e("invalid format of payload!");
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mqtt message callback for property topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] message message from topic
|
||||
* @param[in,out] usr_data pointer to @see OTAUpdateContext
|
||||
*/
|
||||
static void _ota_mqtt_message_callback(void *client, const MQTTMessage *message, void *usr_data)
|
||||
{
|
||||
const char *ota_update_str[] = {
|
||||
[OTA_UPDATE_TYPE_REPORT_VERSION_RSP] = "report_version_rsp",
|
||||
[OTA_UPDATE_TYPE_UPDATE_FIRMWARE] = "update_firmware",
|
||||
};
|
||||
|
||||
int rc, i = 0;
|
||||
|
||||
OTAUpdateContext *ota_update_context = (OTAUpdateContext *)usr_data;
|
||||
UtilsJsonValue update_type;
|
||||
|
||||
Log_d("receive ota message:%.*s", message->payload_len, message->payload_str);
|
||||
|
||||
rc = utils_json_value_get("type", strlen("type"), message->payload_str, message->payload_len, &update_type);
|
||||
if (rc) {
|
||||
Log_e("invalid ota message!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = OTA_UPDATE_TYPE_REPORT_VERSION_RSP; i <= OTA_UPDATE_TYPE_UPDATE_FIRMWARE; i++) {
|
||||
if (!strncmp(update_type.value, ota_update_str[i], update_type.value_len)) {
|
||||
Log_d("callback ota message!");
|
||||
_parse_update_payload_and_callback(i, message, &ota_update_context->callback, ota_update_context->usr_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if topic already subscribed, if not then subscribe.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param callback @see IotOTAUpdateCallback
|
||||
* @param usr_data usr data in callback
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _ota_mqtt_update_topic_check_and_sub(void *client, IotOTAUpdateCallback callback, void *usr_data)
|
||||
{
|
||||
int rc = 0;
|
||||
char ota_result_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
|
||||
HAL_Snprintf(ota_result_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$ota/update/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
|
||||
|
||||
OTAUpdateContext *ota_update_context = (OTAUpdateContext *)HAL_Malloc(sizeof(OTAUpdateContext));
|
||||
if (!ota_update_context) {
|
||||
return QCLOUD_ERR_MALLOC;
|
||||
}
|
||||
ota_update_context->callback = callback;
|
||||
ota_update_context->usr_data = usr_data;
|
||||
|
||||
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
|
||||
sub_params.on_message_handler = _ota_mqtt_message_callback;
|
||||
sub_params.qos = QOS1;
|
||||
sub_params.user_data = ota_update_context;
|
||||
sub_params.user_data_free = HAL_Free;
|
||||
|
||||
rc = IOT_MQTT_SubscribeSync(client, ota_result_topic, &sub_params);
|
||||
if (rc) {
|
||||
HAL_Free(ota_update_context);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unsubscribe topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
static int _ota_mqtt_update_topic_unsubscribe(void *client)
|
||||
{
|
||||
char ota_result_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
HAL_Snprintf(ota_result_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$ota/update/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
|
||||
return IOT_MQTT_Unsubscribe(client, ota_result_topic);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish to OTA report topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] qos @see QoS
|
||||
* @param[in] payload payload of mqtt packet
|
||||
* @param[in] payload_len payload len
|
||||
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
static int _ota_mqtt_publish(void *client, QoS qos, const char *payload, int payload_len)
|
||||
{
|
||||
char ota_report_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
HAL_Snprintf(ota_report_topic, sizeof(ota_report_topic), "$ota/report/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
|
||||
|
||||
PublishParams pub_params = DEFAULT_PUB_PARAMS;
|
||||
pub_params.qos = qos;
|
||||
pub_params.payload = (void *)payload;
|
||||
pub_params.payload_len = payload_len;
|
||||
return IOT_MQTT_Publish(client, ota_report_topic, &pub_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief OTA init, subscribe update topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] callback @see IotOTAUpdateCallback
|
||||
* @param[in] usr_data usr data used in callback
|
||||
* @return 0 for success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
int IOT_OTA_Init(void *client, IotOTAUpdateCallback callback, void *usr_data)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
return _ota_mqtt_update_topic_check_and_sub(client, callback, usr_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief OTA deinit, unsubscribe update topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
*/
|
||||
void IOT_OTA_Deinit(void *client)
|
||||
{
|
||||
POINTER_SANITY_CHECK_RTN(client);
|
||||
_ota_mqtt_update_topic_unsubscribe(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Report upgrade progress.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[out] buf publish message buffer
|
||||
* @param[in] buf_len buffer len
|
||||
* @param[in] report_type @see IotOTAReportType
|
||||
* @param[in] progress progress using in IOT_OTA_REPORT_TYPE_DOWNLOADING
|
||||
* @param[in] version update firmware version
|
||||
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
int IOT_OTA_ReportProgress(void *client, char *buf, int buf_len, IotOTAReportType report_type, int progress,
|
||||
const char *version)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(buf, QCLOUD_ERR_INVAL);
|
||||
NUMBERIC_SANITY_CHECK(buf_len, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(version, QCLOUD_ERR_INVAL);
|
||||
|
||||
/**
|
||||
* @brief order @see IotOTAReportType
|
||||
*
|
||||
*/
|
||||
const char *ota_state[] = {
|
||||
[IOT_OTA_REPORT_TYPE_DOWNLOADING] = "downloading", [IOT_OTA_REPORT_TYPE_UPGRADE_BEGIN] = "burning",
|
||||
[IOT_OTA_REPORT_TYPE_UPGRADE_SUCCESS] = "done", [IOT_OTA_REPORT_TYPE_DOWNLOAD_TIMEOUT] = "fail",
|
||||
[IOT_OTA_REPORT_TYPE_FILE_NOT_EXIST] = "fail", [IOT_OTA_REPORT_TYPE_AUTH_FAIL] = "fail",
|
||||
[IOT_OTA_REPORT_TYPE_MD5_NOT_MATCH] = "fail", [IOT_OTA_REPORT_TYPE_UPGRADE_FAIL] = "fail",
|
||||
};
|
||||
int result_code[] = {
|
||||
[IOT_OTA_REPORT_TYPE_DOWNLOADING] = 0, [IOT_OTA_REPORT_TYPE_UPGRADE_BEGIN] = 0,
|
||||
[IOT_OTA_REPORT_TYPE_UPGRADE_SUCCESS] = 0, [IOT_OTA_REPORT_TYPE_DOWNLOAD_TIMEOUT] = -1,
|
||||
[IOT_OTA_REPORT_TYPE_FILE_NOT_EXIST] = -2, [IOT_OTA_REPORT_TYPE_AUTH_FAIL] = -3,
|
||||
[IOT_OTA_REPORT_TYPE_MD5_NOT_MATCH] = -4, [IOT_OTA_REPORT_TYPE_UPGRADE_FAIL] = -5,
|
||||
};
|
||||
const char *result_msg[] = {
|
||||
[IOT_OTA_REPORT_TYPE_DOWNLOADING] = "",
|
||||
[IOT_OTA_REPORT_TYPE_UPGRADE_BEGIN] = "",
|
||||
[IOT_OTA_REPORT_TYPE_UPGRADE_SUCCESS] = "",
|
||||
[IOT_OTA_REPORT_TYPE_DOWNLOAD_TIMEOUT] = "timeout",
|
||||
[IOT_OTA_REPORT_TYPE_FILE_NOT_EXIST] = "file not exit",
|
||||
[IOT_OTA_REPORT_TYPE_AUTH_FAIL] = "auth fail",
|
||||
[IOT_OTA_REPORT_TYPE_MD5_NOT_MATCH] = "md5 not match",
|
||||
[IOT_OTA_REPORT_TYPE_UPGRADE_FAIL] = "upgrade fail",
|
||||
};
|
||||
|
||||
int len;
|
||||
switch (report_type) {
|
||||
case IOT_OTA_REPORT_TYPE_DOWNLOADING:
|
||||
len = HAL_Snprintf(buf, buf_len,
|
||||
"{\"type\":\"report_progress\",\"report\":{\"progress\":{\"state\":\"%s\",\"percent\":"
|
||||
"\"%d\",\"result_code\":\"%d\",\"result_msg\":\"\"},\"version\":\"%s\"}}",
|
||||
ota_state[report_type], progress, result_code[report_type], version);
|
||||
break;
|
||||
case IOT_OTA_REPORT_TYPE_UPGRADE_BEGIN:
|
||||
case IOT_OTA_REPORT_TYPE_UPGRADE_SUCCESS:
|
||||
case IOT_OTA_REPORT_TYPE_DOWNLOAD_TIMEOUT:
|
||||
case IOT_OTA_REPORT_TYPE_FILE_NOT_EXIST:
|
||||
case IOT_OTA_REPORT_TYPE_AUTH_FAIL:
|
||||
case IOT_OTA_REPORT_TYPE_MD5_NOT_MATCH:
|
||||
case IOT_OTA_REPORT_TYPE_UPGRADE_FAIL:
|
||||
len = HAL_Snprintf(buf, buf_len,
|
||||
"{\"type\":\"report_progress\",\"report\":{\"progress\":{\"state\":\"%s\",\"result_"
|
||||
"code\":\"%d\",\"result_msg\":\"%s\"},\"version\":\"%s\"}}",
|
||||
ota_state[report_type], result_code[report_type], result_msg[report_type], version);
|
||||
break;
|
||||
default:
|
||||
return QCLOUD_ERR_INVAL;
|
||||
}
|
||||
return _ota_mqtt_publish(client, QOS0, buf, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Report current firmware version.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[out] buf publish message buffer
|
||||
* @param[in] buf_len buffer len
|
||||
* @param[in] version current firmware version
|
||||
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
|
||||
*/
|
||||
int IOT_OTA_ReportVersion(void *client, char *buf, int buf_len, const char *version)
|
||||
{
|
||||
int len = HAL_Snprintf(buf, buf_len, "{\"type\":\"report_version\",\"report\":{\"version\":\"%s\"}}",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(version));
|
||||
return _ota_mqtt_publish(client, QOS0, buf, len);
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
ColumnLimit: 120
|
||||
DerivePointerAlignment: true
|
||||
PointerAlignment: Left
|
||||
SortIncludes: true
|
||||
IncludeBlocks: Preserve
|
||||
IndentPPDirectives: AfterHash
|
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file test_ota_mqtt.cc
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-10-19
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-10-19 <td>1.0 <td>fancyxu <td>first commit
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "mqtt_client_test.h"
|
||||
#include "qcloud_iot_common.h"
|
||||
|
||||
namespace mqtt_client_unittest {
|
||||
|
||||
void _update_firmware_callback(UtilsJsonValue version, UtilsJsonValue url, UtilsJsonValue md5sum, uint32_t file_size,
|
||||
void *usr_data) {
|
||||
Log_i("recv firmware: version=%.*s|url=%.*s|md5sum=%.*s|file_size=%u", version.value_len, version.value,
|
||||
url.value_len, url.value, md5sum.value_len, md5sum.value, file_size);
|
||||
}
|
||||
|
||||
void _report_version_reply_callback(int result_code, void *usr_data) { Log_i("recv code=%d", result_code); }
|
||||
|
||||
/**
|
||||
* @brief Test ota mqtt.
|
||||
*
|
||||
*/
|
||||
TEST_F(MqttClientTest, ota_mqtt) {
|
||||
char buf[256];
|
||||
int buf_len = sizeof(buf);
|
||||
const char *version = "1.0.0";
|
||||
IotOTAUpdateCallback ota_callback = {
|
||||
.update_firmware_callback = _update_firmware_callback,
|
||||
.report_version_reply_callback = _report_version_reply_callback,
|
||||
};
|
||||
ASSERT_EQ(IOT_OTA_Init(client, ota_callback, client), 0);
|
||||
ASSERT_GE(IOT_OTA_ReportVersion(client, buf, buf_len, version), 0);
|
||||
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_DOWNLOADING, 0, version), 0);
|
||||
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_DOWNLOADING, 100, version), 0);
|
||||
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_UPGRADE_BEGIN, 0, version), 0);
|
||||
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_UPGRADE_SUCCESS, 0, version), 0);
|
||||
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_DOWNLOAD_TIMEOUT, 0, version), 0);
|
||||
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_FILE_NOT_EXIST, 0, version), 0);
|
||||
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_AUTH_FAIL, 0, version), 0);
|
||||
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_MD5_NOT_MATCH, 0, version), 0);
|
||||
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_UPGRADE_FAIL, 0, version), 0);
|
||||
IOT_OTA_Deinit(client);
|
||||
}
|
||||
|
||||
} // namespace mqtt_client_unittest
|
@@ -0,0 +1,13 @@
|
||||
file(GLOB src_system_mqtt ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
|
||||
set(src_services ${src_services} ${src_system_mqtt} PARENT_SCOPE)
|
||||
|
||||
if( ${CONFIG_COMPILE_SAMPLE} STREQUAL "ON")
|
||||
file(GLOB src_system_mqtt_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/system_mqtt_sample.c)
|
||||
add_executable(system_mqtt_sample ${src_system_mqtt_sample})
|
||||
target_link_libraries(system_mqtt_sample ${libsdk})
|
||||
endif()
|
||||
|
||||
if( ${CONFIG_IOT_TEST} STREQUAL "ON")
|
||||
file(GLOB src_unit_test ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc)
|
||||
set(src_test ${src_test} ${src_unit_test} PARENT_SCOPE)
|
||||
endif()
|
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file broadcast_sample.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-07-18
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
|
||||
* <tr><td>2022-01-26 <td>1.1 <td>hubert <td>add serverip
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "qcloud_iot_common.h"
|
||||
|
||||
#include "utils_log.h"
|
||||
|
||||
/**
|
||||
* @brief MQTT event callback, @see MQTTEventHandleFun
|
||||
*
|
||||
* @param[in] client pointer to mqtt client
|
||||
* @param[in] handle_context context
|
||||
* @param[in] msg msg
|
||||
*/
|
||||
static void _mqtt_event_handler(void *client, void *handle_context, MQTTEventMsg *msg)
|
||||
{
|
||||
MQTTMessage *mqtt_message = (MQTTMessage *)msg->msg;
|
||||
uintptr_t packet_id = (uintptr_t)msg->msg;
|
||||
|
||||
switch (msg->event_type) {
|
||||
case MQTT_EVENT_UNDEF:
|
||||
Log_i("undefined event occur.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_DISCONNECT:
|
||||
Log_i("MQTT disconnect.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_RECONNECT:
|
||||
Log_i("MQTT reconnect.");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_RECEIVED:
|
||||
Log_i("topic message arrived but without any related handle: topic=%.*s, topic_msg=%.*s",
|
||||
mqtt_message->topic_len, STRING_PTR_PRINT_SANITY_CHECK(mqtt_message->topic_name),
|
||||
mqtt_message->payload_len, STRING_PTR_PRINT_SANITY_CHECK((char *)mqtt_message->payload));
|
||||
break;
|
||||
case MQTT_EVENT_SUBSCRIBE_SUCCESS:
|
||||
Log_i("subscribe success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBE_TIMEOUT:
|
||||
Log_i("subscribe wait ack timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBE_NACK:
|
||||
Log_i("subscribe nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_SUCCESS:
|
||||
Log_i("unsubscribe success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_TIMEOUT:
|
||||
Log_i("unsubscribe timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_UNSUBSCRIBE_NACK:
|
||||
Log_i("unsubscribe nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_SUCCESS:
|
||||
Log_i("publish success, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_TIMEOUT:
|
||||
Log_i("publish timeout, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_PUBLISH_NACK:
|
||||
Log_i("publish nack, packet-id=%u", (unsigned int)packet_id);
|
||||
break;
|
||||
default:
|
||||
Log_i("Should NOT arrive here.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Setup MQTT construct parameters.
|
||||
*
|
||||
* @param[in,out] initParams @see MQTTInitParams
|
||||
* @param[in] device_info @see DeviceInfo
|
||||
*/
|
||||
static void _setup_connect_init_params(MQTTInitParams *init_params, DeviceInfo *device_info)
|
||||
{
|
||||
init_params->device_info = device_info;
|
||||
init_params->event_handle.h_fp = _mqtt_event_handler;
|
||||
init_params->host = QCLOUD_IOT_MQTT_DIRECT_DOMAIN;
|
||||
init_params->backup_host = "your_backup_host_domain.com";
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Main
|
||||
// ----------------------------------------------------------------------------
|
||||
static int sg_main_exit = 0;
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void _main_exit(int sig)
|
||||
{
|
||||
Log_e("demo exit by signal:%d\n", sig);
|
||||
sg_main_exit = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
#ifdef __linux__
|
||||
signal(SIGINT, _main_exit);
|
||||
#endif
|
||||
|
||||
int rc;
|
||||
|
||||
// init log level
|
||||
LogHandleFunc func = DEFAULT_LOG_HANDLE_FUNCS;
|
||||
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
|
||||
|
||||
DeviceInfo device_info;
|
||||
|
||||
rc = HAL_GetDevInfo((void *)&device_info);
|
||||
if (rc) {
|
||||
Log_e("get device info failed: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// init connection
|
||||
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
|
||||
_setup_connect_init_params(&init_params, &device_info);
|
||||
|
||||
// create MQTT client and connect with server
|
||||
void *client = IOT_MQTT_Construct(&init_params);
|
||||
if (client) {
|
||||
Log_i("Cloud Device Construct Success");
|
||||
} else {
|
||||
Log_e("MQTT Construct failed!");
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
|
||||
uint32_t time_stamp = 0;
|
||||
|
||||
rc = IOT_Sys_GetTime(client, &time_stamp);
|
||||
if (rc) {
|
||||
Log_e("Get system time failed!, rc=%d", rc);
|
||||
} else {
|
||||
Log_d("Get system time success, time %d", time_stamp);
|
||||
}
|
||||
|
||||
rc = IOT_Sys_SyncNTPTime(client);
|
||||
if (rc) {
|
||||
Log_e("Get system ntp time failed!, rc=%d", rc);
|
||||
}
|
||||
|
||||
char server_ip[256] = {0};
|
||||
rc = IOT_Sys_GetServerIp(client, server_ip);
|
||||
if (rc) {
|
||||
Log_e("Get system server ip failed!, rc=%d", rc);
|
||||
} else {
|
||||
Log_i("Get system server ip list success. server ip: %s", server_ip);
|
||||
}
|
||||
|
||||
rc = IOT_MQTT_Destroy(&client);
|
||||
|
||||
utils_log_deinit();
|
||||
return rc;
|
||||
}
|
@@ -0,0 +1,359 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file system_mqtt.c
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-07-24
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-07-24 <td>1.0 <td>fancyxu <td>first commit
|
||||
* <tr><td>2022-01-26 <td>1.1 <td>hubert <td>add serverip
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include "qcloud_iot_system.h"
|
||||
|
||||
#define MAX_SYSTEM_INFO_SIZE 256
|
||||
|
||||
#define IP_MAX_NUM 10
|
||||
#define ONE_IP_MAX_LEN 24
|
||||
#define IP_LIST_MAX_LEN (IP_MAX_NUM * ONE_IP_MAX_LEN)
|
||||
|
||||
#define SYS_MQTT_GET_RESOURCE_TIME "{\"type\":\"get\",\"resource\":[\"time\"]}"
|
||||
#define SYS_MQTT_GET_RESOURCE_SERVERIP "{\"type\":\"get\",\"resource\":[\"serverip\"]}"
|
||||
|
||||
typedef enum {
|
||||
RESOURCE_TIME = 0,
|
||||
RESOURCE_IP,
|
||||
} SysResourceType;
|
||||
|
||||
/**
|
||||
* @brief Data structure for system time service.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
bool result_recv_ok;
|
||||
// time
|
||||
struct {
|
||||
uint32_t time;
|
||||
uint64_t ntptime1;
|
||||
uint64_t ntptime2;
|
||||
} time;
|
||||
// server ip
|
||||
struct {
|
||||
char ip_list[IP_LIST_MAX_LEN];
|
||||
int ip_len;
|
||||
} server_ip;
|
||||
SysResourceType wait_type;
|
||||
uint64_t result_recv_time;
|
||||
} SystemResultInfo;
|
||||
|
||||
/**
|
||||
* @brief Parse time or server ip.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] message message from topic
|
||||
* @param[in,out] usr_data pointer to @see SystemResultInfo
|
||||
*/
|
||||
static void _system_mqtt_message_callback(void *client, const MQTTMessage *message, void *usr_data)
|
||||
{
|
||||
int rc = 0;
|
||||
SystemResultInfo *result = (SystemResultInfo *)usr_data;
|
||||
UtilsJsonValue value;
|
||||
|
||||
Log_d("Receive system result message:%.*s", message->payload_len, message->payload_str);
|
||||
message->payload_str[message->payload_len] = '\0';
|
||||
if (strstr(message->payload_str, "\"time\"")) {
|
||||
// get time
|
||||
rc = utils_json_value_get("time", strlen("time"), message->payload_str, message->payload_len, &value);
|
||||
if (rc) {
|
||||
return;
|
||||
}
|
||||
rc = utils_json_value_data_get(value, UTILS_JSON_VALUE_TYPE_UINT32, &result->time.time);
|
||||
if (rc) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get ntptime1
|
||||
rc = utils_json_value_get("ntptime1", strlen("ntptime1"), message->payload_str, message->payload_len, &value);
|
||||
if (rc) {
|
||||
return;
|
||||
}
|
||||
rc = utils_json_value_data_get(value, UTILS_JSON_VALUE_TYPE_UINT64, &result->time.ntptime1);
|
||||
if (rc) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get ntptime2
|
||||
rc = utils_json_value_get("ntptime2", strlen("ntptime2"), message->payload_str, message->payload_len, &value);
|
||||
if (rc) {
|
||||
return;
|
||||
}
|
||||
rc = utils_json_value_data_get(value, UTILS_JSON_VALUE_TYPE_UINT64, &result->time.ntptime2);
|
||||
if (rc) {
|
||||
result->time.ntptime1 = result->time.time * 1000;
|
||||
result->time.ntptime2 = result->time.time * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
if (strstr(message->payload_str, "\"serverip\"")) {
|
||||
// get serverip
|
||||
rc = utils_json_value_get("serverip", strlen("serverip"), message->payload_str, message->payload_len, &value);
|
||||
if (rc) {
|
||||
return;
|
||||
}
|
||||
result->server_ip.ip_len = value.value_len > IP_LIST_MAX_LEN ? IP_LIST_MAX_LEN : value.value_len;
|
||||
memset(result->server_ip.ip_list, 0, IP_LIST_MAX_LEN);
|
||||
strncpy(result->server_ip.ip_list, value.value, result->server_ip.ip_len);
|
||||
}
|
||||
|
||||
result->result_recv_time = HAL_Timer_CurrentMs();
|
||||
result->result_recv_ok = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check and subscribe system result topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _system_mqtt_result_topic_check_and_sub(void *client, const char *topic)
|
||||
{
|
||||
int rc = 0;
|
||||
// 1. check topic is sub ready
|
||||
if (IOT_MQTT_IsSubReady(client, topic)) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
// 2. subscribe
|
||||
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
|
||||
SystemResultInfo *system_info = (SystemResultInfo *)HAL_Malloc(sizeof(SystemResultInfo));
|
||||
if (!system_info) {
|
||||
return QCLOUD_ERR_MALLOC;
|
||||
}
|
||||
|
||||
system_info->result_recv_ok = false;
|
||||
|
||||
sub_params.on_message_handler = _system_mqtt_message_callback;
|
||||
sub_params.qos = QOS1;
|
||||
sub_params.user_data = system_info;
|
||||
sub_params.user_data_free = HAL_Free;
|
||||
|
||||
rc = IOT_MQTT_SubscribeSync(client, topic, &sub_params);
|
||||
if (rc) {
|
||||
HAL_Free(system_info);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish get time message to system topic.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic system result topic
|
||||
* @return >= 0 for success
|
||||
*/
|
||||
static int _system_mqtt_get_resource_publish(void *client, const char *topic, SysResourceType type)
|
||||
{
|
||||
char pub_topic_name[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
HAL_Snprintf(pub_topic_name, sizeof(pub_topic_name), "$sys/operation/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
|
||||
|
||||
SystemResultInfo *result = IOT_MQTT_GetSubUsrData(client, topic);
|
||||
if (!result) {
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
result->wait_type = type;
|
||||
result->result_recv_ok = false;
|
||||
|
||||
PublishParams pub_params = DEFAULT_PUB_PARAMS;
|
||||
pub_params.qos = QOS0;
|
||||
switch (type) {
|
||||
case RESOURCE_TIME:
|
||||
pub_params.payload = SYS_MQTT_GET_RESOURCE_TIME;
|
||||
pub_params.payload_len = strlen(SYS_MQTT_GET_RESOURCE_TIME);
|
||||
break;
|
||||
case RESOURCE_IP:
|
||||
pub_params.payload = SYS_MQTT_GET_RESOURCE_SERVERIP;
|
||||
pub_params.payload_len = strlen(SYS_MQTT_GET_RESOURCE_SERVERIP);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return IOT_MQTT_Publish(client, pub_topic_name, &pub_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wait system result, timeout @see QCLOUD_IOT_MQTT_WAIT_ACK_TIMEOUT
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] topic system result topic
|
||||
* @param[out] time time from system result topic
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _system_mqtt_result_wait(void *client, const char *topic, void *user_data)
|
||||
{
|
||||
int rc = 0;
|
||||
QcloudIotTimer wait_result_timer;
|
||||
SystemResultInfo *result;
|
||||
|
||||
IOT_Timer_CountdownMs(&wait_result_timer, QCLOUD_IOT_MQTT_WAIT_ACK_TIMEOUT);
|
||||
|
||||
result = IOT_MQTT_GetSubUsrData(client, topic);
|
||||
if (!result) {
|
||||
return QCLOUD_ERR_FAILURE;
|
||||
}
|
||||
result->result_recv_ok = false;
|
||||
while (!rc && !IOT_Timer_Expired(&wait_result_timer)) {
|
||||
rc = IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT);
|
||||
if (result->result_recv_ok) {
|
||||
switch (result->wait_type) {
|
||||
case RESOURCE_TIME:
|
||||
*((uint32_t *)user_data) = result->time.time;
|
||||
break;
|
||||
case RESOURCE_IP:
|
||||
strncpy(user_data, result->server_ip.ip_list, result->server_ip.ip_len);
|
||||
result->server_ip.ip_len = 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QCLOUD_RET_SUCCESS;
|
||||
}
|
||||
}
|
||||
return QCLOUD_ERR_MQTT_REQUEST_TIMEOUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish get resource message and wait for result.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[in] type @see SysResourceType
|
||||
* @param[in] user_data pointer to time or sever ip
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
static int _system_mqtt_get_resource(void *client, SysResourceType type, void *user_data)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(user_data, QCLOUD_ERR_INVAL);
|
||||
|
||||
int rc = 0;
|
||||
char system_result_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
|
||||
HAL_Snprintf(system_result_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$sys/operation/result/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
|
||||
|
||||
rc = _system_mqtt_result_topic_check_and_sub(client, system_result_topic);
|
||||
if (rc) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = _system_mqtt_get_resource_publish(client, system_result_topic, type);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return _system_mqtt_result_wait(client, system_result_topic, user_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get time from system result topic
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[out] time time from system result topic
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int IOT_Sys_GetTime(void *client, uint32_t *time)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(time, QCLOUD_ERR_INVAL);
|
||||
return _system_mqtt_get_resource(client, RESOURCE_TIME, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get ntp time from system result topic and set to system.
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int IOT_Sys_SyncNTPTime(void *client)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
|
||||
int rc = 0;
|
||||
uint32_t time_get = 0;
|
||||
uint64_t local_publish_before, local_ntptime = 0;
|
||||
|
||||
SystemResultInfo *result = NULL;
|
||||
|
||||
char system_result_topic[MAX_SIZE_OF_CLOUD_TOPIC];
|
||||
|
||||
HAL_Snprintf(system_result_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$sys/operation/result/%s/%s",
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
|
||||
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
|
||||
|
||||
rc = _system_mqtt_result_topic_check_and_sub(client, system_result_topic);
|
||||
if (rc) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
// prepare for publish
|
||||
result = IOT_MQTT_GetSubUsrData(client, system_result_topic);
|
||||
if (!result) {
|
||||
return -1;
|
||||
}
|
||||
local_publish_before = HAL_Timer_CurrentMs();
|
||||
|
||||
// publish and wait
|
||||
rc = _system_mqtt_get_resource_publish(client, system_result_topic, RESOURCE_TIME);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = _system_mqtt_result_wait(client, system_result_topic, &time_get);
|
||||
if (rc) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
local_ntptime =
|
||||
(result->time.ntptime2 + result->time.ntptime1 + result->result_recv_time - local_publish_before) / 2;
|
||||
|
||||
rc = HAL_Timer_SetSystimeMs(local_ntptime);
|
||||
if (rc) {
|
||||
Log_e("set systime ms failed, timestamp %lld, please check permission or other ret :%d", local_ntptime, rc);
|
||||
} else {
|
||||
Log_i("set systime ms success, timestamp %lld ms", local_ntptime);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get serverip from system result topic
|
||||
*
|
||||
* @param[in,out] client pointer to mqtt client
|
||||
* @param[out] server_ip serverip from system result topic
|
||||
* @return @see IotReturnCode
|
||||
*/
|
||||
int IOT_Sys_GetServerIp(void *client, char *server_ip)
|
||||
{
|
||||
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
|
||||
POINTER_SANITY_CHECK(server_ip, QCLOUD_ERR_INVAL);
|
||||
|
||||
return _system_mqtt_get_resource(client, RESOURCE_IP, server_ip);
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
ColumnLimit: 120
|
||||
DerivePointerAlignment: true
|
||||
PointerAlignment: Left
|
||||
SortIncludes: true
|
||||
IncludeBlocks: Preserve
|
||||
IndentPPDirectives: AfterHash
|
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @copyright
|
||||
*
|
||||
* Tencent is pleased to support the open source community by making IoT Hub available.
|
||||
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License(the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @file test_broadcast.cc
|
||||
* @brief
|
||||
* @author fancyxu (fancyxu@tencent.com)
|
||||
* @version 1.0
|
||||
* @date 2021-07-18
|
||||
*
|
||||
* @par Change Log:
|
||||
* <table>
|
||||
* <tr><th>Date <th>Version <th>Author <th>Description
|
||||
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
|
||||
* <tr><td>2022-01-26 <td>1.1 <td>hubert <td>add serverip test
|
||||
* </table>
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "mqtt_client_test.h"
|
||||
#include "qcloud_iot_common.h"
|
||||
|
||||
namespace mqtt_client_unittest {
|
||||
|
||||
/**
|
||||
* @brief Test system mqtt.
|
||||
*
|
||||
*/
|
||||
TEST_F(MqttClientTest, system_mqtt) {
|
||||
uint32_t time_stamp = 0;
|
||||
char server_ip[256] = {0};
|
||||
EXPECT_EQ(IOT_Sys_GetTime(client, &time_stamp), 0);
|
||||
EXPECT_EQ(IOT_Sys_SyncNTPTime(client), 0);
|
||||
ASSERT_EQ(IOT_Sys_GetServerIp(client, server_ip), 0);
|
||||
}
|
||||
|
||||
} // namespace mqtt_client_unittest
|
Reference in New Issue
Block a user