feat: 移植腾讯云物联网开发平台 C SDK

This commit is contained in:
fancyxu
2022-07-01 11:06:09 +08:00
parent 2be1169b0b
commit 0acc079ed6
195 changed files with 36646 additions and 0 deletions

View File

@@ -0,0 +1,148 @@
<!-- TOC -->
- [一、编写目的](#%E4%B8%80%E7%BC%96%E5%86%99%E7%9B%AE%E7%9A%84)
- [二、代码格式](#%E4%BA%8C%E4%BB%A3%E7%A0%81%E6%A0%BC%E5%BC%8F)
- [行长度](#%E8%A1%8C%E9%95%BF%E5%BA%A6)
- [缩进](#%E7%BC%A9%E8%BF%9B)
- [文件编码](#%E6%96%87%E4%BB%B6%E7%BC%96%E7%A0%81)
- [分行](#%E5%88%86%E8%A1%8C)
- [条件语句](#%E6%9D%A1%E4%BB%B6%E8%AF%AD%E5%8F%A5)
- [循环和开关语句](#%E5%BE%AA%E7%8E%AF%E5%92%8C%E5%BC%80%E5%85%B3%E8%AF%AD%E5%8F%A5)
- [指针](#%E6%8C%87%E9%92%88)
- [水平留白](#%E6%B0%B4%E5%B9%B3%E7%95%99%E7%99%BD)
- [垂直留白](#%E5%9E%82%E7%9B%B4%E7%95%99%E7%99%BD)
<!-- /TOC -->
## 一、编写目的
该文档用作提供 SDK 开发的代码格式说明,开发者提交代码前应仔细阅读。
本项目根目录提供了`.clang-format`格式文件,请优先使用`clang-format`进行代码格式统一。
如有冲突,请以`clang-format`的结果为准。
## 二、代码格式
### 行长度
最长120行
### 缩进
四个空格
### 文件编码
UTF-8 不能有BOM头。
### 分行
1.对齐式
```c
bool retval = DoSomething(averyveryveryverylongargument1,
argument2, argument3);
```
2.缩进式
```c
if (...) {
...
...
if (...) {
DoSomething(
argument1, argument2, // 4 空格缩进
argument3, argument4);
}
```
### 条件语句
```c
if (condition) {
DoSomething(); // 4 空格缩进.
}
```
### 循环和开关语句
- default不可达时用assert
- case使用大括号非强制要求
```c
switch (var) {
case 0: { // 4 空格缩进
... // 4 空格缩进
break;
}
case 1: {
...
break;
}
default: {
assert(false);
}
}
```
### 指针
```
char* ptr = NULL;
```
### 水平留白
行尾不留白
```c
void f(bool b) { // 左大括号前总是有空格
...
int i = 0; // 分号前不加空格
// 列表初始化中大括号内的空格是可选的,
// 如果加了空格,那么两边都要加上
int x[] = { 0 };
int x[] = {0};
if (b) { // if 条件语句和循环语句关键字后均有空格
} else { // else 前后有空格
}
while (test) {} // 圆括号内部不紧邻空格
switch (i) {
for (int i = 0; i < 5; ++i) {
// 循环和条件语句的圆括号内可以有空格,
// 但这种情况很少,要保持一致
switch ( i ) {
if ( test ) {
for ( int i = 0; i < 5; ++i ) {
// 循环里内分号后恒有空格,分号前可以加个空格
for ( ; i < 5 ; ++i) {
switch (i) {
case 1: // switch case 的冒号前无空格
...
case 2: break; // 如果冒号后有代码,加个空格
// 赋值运算符前后总是有空格
x = 0;
// 其它二元操作符前后也恒有空格,对于表达式的子式可以不加空格
// 圆括号内部没有紧邻空格
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);
// 在参数和一元操作符之间不加空格
x = -5;
++x;
if (x && !y)
...
```
### 垂直留白
看情况,根据个人习惯,增强可读性即可

View File

@@ -0,0 +1,528 @@
<!-- TOC -->
- [编写目的](#%E7%BC%96%E5%86%99%E7%9B%AE%E7%9A%84)
- [代码规范描述](#%E4%BB%A3%E7%A0%81%E8%A7%84%E8%8C%83%E6%8F%8F%E8%BF%B0)
- [头文件](#%E5%A4%B4%E6%96%87%E4%BB%B6)
- [头文件保护](#%E5%A4%B4%E6%96%87%E4%BB%B6%E4%BF%9D%E6%8A%A4)
- [内联及结构体](#%E5%86%85%E8%81%94%E5%8F%8A%E7%BB%93%E6%9E%84%E4%BD%93)
- [前置声明](#%E5%89%8D%E7%BD%AE%E5%A3%B0%E6%98%8E)
- [内联函数](#%E5%86%85%E8%81%94%E5%87%BD%E6%95%B0)
- [头文件包含顺序](#%E5%A4%B4%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB%E9%A1%BA%E5%BA%8F)
- [作用域](#%E4%BD%9C%E7%94%A8%E5%9F%9F)
- [静态变量](#%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F)
- [局部变量](#%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F)
- [函数](#%E5%87%BD%E6%95%B0)
- [输出参数](#%E8%BE%93%E5%87%BA%E5%8F%82%E6%95%B0)
- [简短函数](#%E7%AE%80%E7%9F%AD%E5%87%BD%E6%95%B0)
- [语言特性](#%E8%AF%AD%E8%A8%80%E7%89%B9%E6%80%A7)
- [const](#const)
- [整形](#%E6%95%B4%E5%BD%A2)
- [位可移植性](#%E4%BD%8D%E5%8F%AF%E7%A7%BB%E6%A4%8D%E6%80%A7)
- [预处理宏](#%E9%A2%84%E5%A4%84%E7%90%86%E5%AE%8F)
- [NULL和0](#null%E5%92%8C0)
- [sizeof](#sizeof)
- [别名](#%E5%88%AB%E5%90%8D)
- [动态分配](#%E5%8A%A8%E6%80%81%E5%88%86%E9%85%8D)
- [命名约定](#%E5%91%BD%E5%90%8D%E7%BA%A6%E5%AE%9A)
- [通用命名规则](#%E9%80%9A%E7%94%A8%E5%91%BD%E5%90%8D%E8%A7%84%E5%88%99)
- [文件命名](#%E6%96%87%E4%BB%B6%E5%91%BD%E5%90%8D)
- [类型命名](#%E7%B1%BB%E5%9E%8B%E5%91%BD%E5%90%8D)
- [变量命名](#%E5%8F%98%E9%87%8F%E5%91%BD%E5%90%8D)
- [常量命名、枚举型变量以及宏](#%E5%B8%B8%E9%87%8F%E5%91%BD%E5%90%8D%E6%9E%9A%E4%B8%BE%E5%9E%8B%E5%8F%98%E9%87%8F%E4%BB%A5%E5%8F%8A%E5%AE%8F)
- [函数命名](#%E5%87%BD%E6%95%B0%E5%91%BD%E5%90%8D)
- [注释](#%E6%B3%A8%E9%87%8A)
- [注释风格](#%E6%B3%A8%E9%87%8A%E9%A3%8E%E6%A0%BC)
- [注释示例](#%E6%B3%A8%E9%87%8A%E7%A4%BA%E4%BE%8B)
- [格式](#%E6%A0%BC%E5%BC%8F)
- [行长度](#%E8%A1%8C%E9%95%BF%E5%BA%A6)
- [非ASCII字符](#%E9%9D%9Eascii%E5%AD%97%E7%AC%A6)
- [空格](#%E7%A9%BA%E6%A0%BC)
- [函数声明与定义](#%E5%87%BD%E6%95%B0%E5%A3%B0%E6%98%8E%E4%B8%8E%E5%AE%9A%E4%B9%89)
- [浮点字面量](#%E6%B5%AE%E7%82%B9%E5%AD%97%E9%9D%A2%E9%87%8F)
- [函数调用](#%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8)
- [列表初始化](#%E5%88%97%E8%A1%A8%E5%88%9D%E5%A7%8B%E5%8C%96)
- [条件语句](#%E6%9D%A1%E4%BB%B6%E8%AF%AD%E5%8F%A5)
- [循环和开关选择语句](#%E5%BE%AA%E7%8E%AF%E5%92%8C%E5%BC%80%E5%85%B3%E9%80%89%E6%8B%A9%E8%AF%AD%E5%8F%A5)
- [指针和引用表达式](#%E6%8C%87%E9%92%88%E5%92%8C%E5%BC%95%E7%94%A8%E8%A1%A8%E8%BE%BE%E5%BC%8F)
- [布尔表达式](#%E5%B8%83%E5%B0%94%E8%A1%A8%E8%BE%BE%E5%BC%8F)
- [返回值](#%E8%BF%94%E5%9B%9E%E5%80%BC)
- [变量及数组初始化](#%E5%8F%98%E9%87%8F%E5%8F%8A%E6%95%B0%E7%BB%84%E5%88%9D%E5%A7%8B%E5%8C%96)
- [预处理指令](#%E9%A2%84%E5%A4%84%E7%90%86%E6%8C%87%E4%BB%A4)
- [水平留白](#%E6%B0%B4%E5%B9%B3%E7%95%99%E7%99%BD)
- [垂直留白](#%E5%9E%82%E7%9B%B4%E7%95%99%E7%99%BD)
<!-- /TOC -->
## 编写目的
该文档用作提供 SDK 开发的代码规范说明,开发者提交代码前应仔细阅读。
## 代码规范描述
### 头文件
#### 头文件保护
头文件应支持c++引用,并提供保护头,根据开源治理要求,本项目中保护头定义如下:
`IOT_HUB_DEVICE_C_SDK_<dir_path>_INC_<file_name>_`
比如 `mqtt_client.h`
```c
#ifndef IOT_HUB_DEVICE_C_SDK_SERVICES_MQTT_CLIENT_INC_MQTT_CLIENT_H_
#define IOT_HUB_DEVICE_C_SDK_SERVICES_MQTT_CLIENT_INC_MQTT_CLIENT_H_
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif // IOT_HUB_DEVICE_C_SDK_SERVICES_MQTT_CLIENT_INC_MQTT_CLIENT_H_
```
#### 内联及结构体
头文件中若声明联函数及结构体,则必须定义,即如果对外提供结构体以及内联函数,请务必放到头文件中定义。
#### 前置声明
尽量避免前置声明,而采用包含头文件。
#### 内联函数
内联函数不超过10行。
C语言使用内联函数需要注意C99才开始支持并且一般需要编译器开了优化选项才支持这个跟C++不一样。
#### 头文件包含顺序
通常系统头文件会放到相关头文件中,以保证相关头文件的易用性。
```c
dir2/foo2.h
C
`.h`
`.h`
```
---
### 作用域
#### 静态变量
在源文件中定义一个不需要被外部引用的变量时,可以将它们声明为`static`。但是不要在头文件中这么做。
#### 局部变量
将函数变量尽可能置于最小作用域内,并在变量声明时进行初始化。
```c
int i = 1;
while (const char* p = strchr(str, '/')) str = p + 1;
```
---
### 函数
#### 输出参数
1. 推荐优先使用返回值作为函数输出。函数的参数列表排序为:输入参数在前,输出参数在后。
2. 输入参数,通常是值参或者是 const 引用,纯输出参数和输入兼输出参数则是非 const 指针。
3. 参数排序时,请将所有的纯输入参数置于所有输出参数之前。
以上从易读性角度考虑,不做强制要求,但是在函数的`doxygen`注释中需要通过`@param[in]``@param[out]``@param[in,out]`列出。
#### 简短函数
如果一个函数超过了 40 行,则可以思考下,能否在不破坏程序结构的前提之下,对函数进行拆分。
以上从易读性角度考虑,当`review`时超过3次无法看懂即需考虑进行拆分。
---
### 语言特性
#### const
在 API 里,只要合理,就应该使用 const
- 如果函数不会修改传你入的引用或指针类型参数,该参数应声明为 const
- 按值传入的函数参数const 对调用者没有影响,因此不建议在函数声明中使用
- 尽可能将方法声明为 const除非它们改变了对象的逻辑状态否则不能安全地并发调用它们。
#### 整形
1. 在 C 整型中, 只使用 `int`。在合适的情况下,推荐使用标准类型如 `size_t``ptrdiff_t`。大多数情况下,`int`即可覆盖。
2. `<stdint.h>`定义了 `int16_t``uint32_t``int64_t` 等整型,在需要确保整型大小时可以使用它们代替 short、unsigned long long 等。
#### 位可移植性
1. 请记住sizeof(void *) != sizeof(int)。如果需要指针大小的整数,请使用 `intptr_t`
2. 根据需要使用大括号初始化来创建 64 位常量
```c
int64_t my_value{0x123456789};
uint64_t my_mask{3ULL << 48};
```
#### 预处理宏
使用宏时要非常谨慎,尽量以内联函数,枚举和常量代替。往用宏展开性能关键的代码,现在可以用内联函数替代。用宏表示常量可被 const 或constexpr 变量代替。用宏 "缩写" 长变量名可被引用代替。
下面给出的用法模式可以避免使用宏带来的问题;如果你要用宏,尽可能遵守:
- 不要在 .h 文件中定义宏,常见的通用配置需要在头文件中定义,宏命名应清晰且易区分。
- 在马上要使用时才进行 #define,使用后要立即 #undef
- 不要只是对已经存在的宏使用 #undef,选择一个不会冲突的名称。
- 不要用 ## 处理函数,类和变量的名字。
#### NULL和0
整数用 0浮点数用 0.0,指针用 NULL字符用 '\0'。
#### sizeof
尽可能用 sizeof(varname) 代替 sizeof(type)。
#### 别名
不要把仅用于实现的别名放到公共 API 里;公共 API 里只放用户使用的别名。
在定义公共别名时,把新名字的意图写入文档,包括:是否保证始终和当前指向的类型相同,或是否打算使用更有限的兼容性。这让用户知道:是否可以将这些类型视为可替换类型,或者是否必须遵循更特定的规则,并且帮助实现部分保留别名更改的自由度。
#### 动态分配
如果必须使用动态分配,那么更倾向于将所有权保持在分配者手中。如果其他地方要使用这个对象,最好传递它的拷贝,或者传递一个不用改变所有权的指针或引用。
### 命名约定
#### 通用命名规则
函数命名、变量命名、文件命名要有描述性,少用缩写。
#### 文件命名
文件名要全部小写,可以包含下划线 "`_`" 或连字符 "`-`",依照项目的约定.如果没有约定,那么 "`_`" 更好。
不要使用已经存在于 /usr/include 下的文件名,如 db.h。
#### 类型命名
结构体、类型定义 (typedef)、枚举——均使用相同约定,即以大写字母开始,每个单词首字母均大写,不包含下划线。
```c
struct UrlTableProperties {
string name;
int num_entries;
};
// 枚举
enum UrlTableErrors { ...
```
#### 变量命名
变量 (包括函数参数) 和数据成员名一律小写,单词之间用下划线连接。其中全局变量中`static`修饰的以`sg`开头,非`static`修饰的以`g`开头
```c
static int sg_count = 0;
int g_count = 0;
```
#### 常量命名、枚举型变量以及宏
声明为const 的变量、枚举型变量或者宏,或在程序运行期间其值始终保持不变的,全部使用大写,单词用下划线`_`隔开。
```c
const MAX_COUNT = 10;
#define MAX_SIZE_OF_NUM 10
enum PinStateType {
PIN_OFF,
PIN_ON
};
```
#### 函数命名
内部函数命名均已小写形式,加下划线隔开
```c
const char *iot_ca_get(void);
```
对外API以及跨平台移植接口使用大小写混合用下划线隔开并以`IOT`或者`HAL`开头
```c
void *IOT_OTA_Init(const char *product_id, const char *device_name, void *ch_signal);
int HAL_AT_Uart_Send(void *data, uint32_t size)
```
---
### 注释
#### 注释风格
统一使用`doxegen`注释风格。
#### 注释示例
- 文件头:所有文件的开头应该包含以下内容:
```c
/**
* @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_packet_deserialize.c
* @brief implements mqtt packet deserialize. Reference paho.mqtt.embedded-c &
* http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.pdf
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-05-21
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-05-21 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
```
- 函数注释:参数的输入/输出必须指明
```c
/**
* @brief Deserialize the supplied (wire) buffer into publish data. See 3.3.
*
* @param[in] buf the raw buffer data, of the correct length determined by the remaining length field
* @param[in] buf_len the length in bytes of the data in the supplied buffer
* @param[out] flags the MQTT dup, qos, retained flag
* @param[out] packet_id returned integer - the MQTT packet identifier
* @param[out] topic_name returned string - the MQTT topic in the publish
* @param[out] topic_len returned integer - the length of the MQTT topic
* @param[out] payload returned byte buffer - the MQTT publish payload
* @param[out] payload_len returned integer - the length of the MQTT payload
* @return @see MQTTPacketErrCode
*/
```
- 结构体注释:结构体成员必须采用`/**< xxx */`才能显示在`doxygen`
```c
/**
* @brief Defines the MQTT "Last Will and Testament" (LWT) settings for the connect packet.
*
*/
typedef struct {
const char* topic_name; /**< The LWT topic to which the LWT message will be published */
const char* message; /**< The LWT payload */
uint8_t retained; /**< The retained flag for the LWT message */
uint8_t qos; /**< The quality of service setting for the LWT message */
} MQTTPacketWillOptions;
```
- 宏注释:对于需要注释的宏,需要在之前使用以下格式才能显示在`doxygen`
```
/**
* @brief header 1 byte + remaining length 1~4 byte(s).
*
*/
#define MAX_NO_OF_REMAINING_LENGTH_BYTES 4
```
---
### 格式
除了命名,格式请以`clang-format`格式化结果为准,可参考[SDK代码格式说明](./SDK代码格式说明.md)
#### 行长度
每一行代码最大字符数为 120。
#### 非ASCII字符
尽量不使用非 ASCII 字符,使用时必须使用 UTF-8 编码。
非 ASCII 字符(中文等)只能用于注释当中,含有中文注释的代码文件必须使用 UTF-8 编码保存不能有BOM头。
#### 空格
只使用空格,每次缩进 4 个空格。
不要在代码中使用制表符。你应该设置编辑器将制表符转为空格。
#### 函数声明与定义
返回类型和函数名在同一行,参数也尽量放在同一行,如果放不下就对形参分行,分行方式与函数调用一致。
- 使用好的参数名。
- 只有在参数未被使用或者其用途非常明显时,才能省略参数名。
- 如果返回类型和函数名在一行放不下,分行。
- 如果返回类型与函数声明或定义分行了,不要缩进。
- 左圆括号总是和函数名在同一行。
- 函数名和左圆括号间永远没有空格。
- 圆括号与参数间没有空格。
- 左大括号总在最后一个参数同一行的末尾处,不另起新行。
- 右大括号总是单独位于函数最后一行,或者与左大括号同一行。
- 右圆括号和左大括号间总是有一个空格。
- 所有形参应尽可能对齐。
- 缺省缩进为 4 个空格。
- 换行后的参数保持 4 个空格的缩进。
#### 浮点字面量
浮点字面常量都要带小数点小数点两边都要有数字即使它们使用指数表示法。如果所有浮点数都采用这种熟悉的形式可以提高可读性不会被误认为是整数指数符号的E/e不会被误认为十六进制数字。可以使用整数字面常量初始化浮点变量(假设变量类型可以精确地表示该整数),但是请注意,指数表示法中的数字绝不能用整数。
```c
float f = 1.0f;
float f2 = 1; // 也可以用整数字面常量初始化浮点数
long double ld = -0.5L;
double d = 1248.0e6;
```
#### 函数调用
允许3种格式在一行内写完函数调用在圆括号里对参数分行或者参数另起一行且缩进四格。
原则上,尽可能精简行数,比如把多个参数适当地放在同一行里。
函数调用遵循如下形式:
```c
bool retval = DoSomething(argument1, argument2, argument3);
```
如果同一行放不下,可断为多行,后面每一行都和第一个实参对齐,左圆括号后和右圆括号前不要留空格:
```c
bool retval = DoSomething(averyveryveryverylongargument1,
argument2, argument3);
```
参数也可以放在次行,缩进四格:
```c
if (...) {
...
...
if (...) {
DoSomething(
argument1, argument2, // 4 空格缩进
argument3, argument4);
}
```
除非影响到可读性,尽量把多个参数放在同一行,以减少函数调用所需的行数。有人认为把每个参数都独立成行,不仅更好读,而且方便编辑参数
#### 列表初始化
列表初始化格式,函数调用如何格式化,就如何格式化列表初始化。
#### 条件语句
建议不在圆括号内使用空格。关键字 if 和 else 另起一行。
在所有情况下if 和左圆括号间都要有个空格。右圆括号和左大括号之间也要有个空格
```c
if (condition) { // 好 - IF 和 { 都与空格紧邻
```
如果能增强可读性,简短的条件语句允许写在同一行。只有在语句简单并且没有使用 else 子句时允许使用:
```c
if (x == kFoo) return new Foo();
if (x == kBar) return new Bar();
```
通常,单行语句不需要使用大括号,如果用也没问题;复杂的条件或循环语句用大括号可读性会更好。也有一些项目要求 if必须总是使用大括号
```c
if (condition) {
DoSomething(); // 4 空格缩进.
}
```
#### 循环和开关选择语句
switch 语句中的 case 块可以使用大括号也可以不用,取决于个人选择。如果有不满足 case 条件的枚举值switch 应该总是包含一个 default匹配 (如果有输入值没有 case 去处理, 编译器将给出 warning)。如果default 永远执行不到,简单的使用 assert语句。
```c
switch (var) {
case 0: { // 2 空格缩进
... // 4 空格缩进
break;
}
case 1: {
...
break;
}
default: {
assert(false);
}
}
```
空循环体应使用 {} 或 continue而不是一个简单的分号。
#### 指针和引用表达式
句点或箭头前后不要有空格。 `(*, &)` 作为指针/地址操作符时之后不能有空格。`(*, &)`在用于声明指针变量或参数时,空格前置后置都可以。
#### 布尔表达式
如果一个布尔表达式超过标准行宽 `<line-length>`,断行方式要统一。比如,逻辑操作符要么都在行尾,要么都在行首。
#### 返回值
不要在 return 表达式里加上非必须的圆括号。
#### 变量及数组初始化
变量及数组初始化用 =() 和 {} 均可。
#### 预处理指令
预处理指令不要缩进,从行首开始。即使预处理指令位于缩进代码块中,指令也应从行首开始。
```c
// 好 - 指令从行首开始
if (lopsided_score) {
#if DISASTER_PENDING // 正确 - 从行首开始
DropEverything();
# if NOTIFY // 非必要 - # 后跟空格
NotifyClient();
# endif
#endif
BackToNormal();
}
```
#### 水平留白
水平留白的使用取决于代码的位置。永远不要在行尾添加没意义的留白。
#### 垂直留白
这不仅仅是规则而是原则问题: 不在万不得已,不要使用空行,尤其是:两个函数定义之间的空行不要超过 2 行,函数体首尾不要留空行,函数体中也不要随意添加空行。
基本原则是: 一屏显示的代码越多,程序的控制流越容易理解。当然,过于密集的代码块和过于疏松的代码块同样难看,这取决于你的判断,但通常是垂直留白越少越好。
下面的规则可以让加入的空行更有效:
- 函数体内开头或结尾的空行可读性微乎其微。
- 在多重 if-else 块里加空行或许有点可读性。

View File

@@ -0,0 +1,34 @@
<!-- TOC -->
- [简介](#%E7%AE%80%E4%BB%8B)
- [数据模板创建](#%E6%95%B0%E6%8D%AE%E6%A8%A1%E6%9D%BF%E5%88%9B%E5%BB%BA)
- [数据模板描述文件导出](#%E6%95%B0%E6%8D%AE%E6%A8%A1%E6%9D%BF%E6%8F%8F%E8%BF%B0%E6%96%87%E4%BB%B6%E5%AF%BC%E5%87%BA)
- [数据模板模板代码生成](#%E6%95%B0%E6%8D%AE%E6%A8%A1%E6%9D%BF%E6%A8%A1%E6%9D%BF%E4%BB%A3%E7%A0%81%E7%94%9F%E6%88%90)
<!-- /TOC -->
## 简介
本文介绍基于物联开发平台 IoT Explorer 创建的数据模板如何生成模板代码。
## 数据模板创建
参阅[产品定义](https://cloud.tencent.com/document/product/1081/34739?!preview&!editLang=zh#.E6.95.B0.E6.8D.AE.E6.A8.A1.E6.9D.BF)创建数据模板
## 数据模板描述文件导出
数据模板描述文件是一个 JSON 格式的文件描述了产品定义的属性、事件及其他信息在平台导出此json文件如下图示:
![data_template_json](https://main.qcloudimg.com/raw/0951d7c3f540ca716442e08651a0efa5.jpg)
## 数据模板模板代码生成
将下载的json文件拷贝到tools目录执行`./data_template_codegen.py -c xx/example_config.json -d ../targetdir/`命令,则会根据json文件在target目录生成所定义产品的数据模板及事件的配置文件,
将这个生成的文件拷贝到`app/data_template`的目录下,
data_template_app.c 示例了智能灯的数据模板处理框架,可以基于此框架修改业务逻辑。
```bash
./data_template_codegen.py -c example_config.json
加载 .\example_config.json 文件成功
文件 ./data_template_config_header.include 生成成功
文件 ./data_template_config_src_c.include 生成成功
```

View File

@@ -0,0 +1,340 @@
<!-- TOC -->
- [简介](#%E7%AE%80%E4%BB%8B)
- [数据模板协议](#%E6%95%B0%E6%8D%AE%E6%A8%A1%E6%9D%BF%E5%8D%8F%E8%AE%AE)
- [概述](#%E6%A6%82%E8%BF%B0)
- [设备属性上报](#%E8%AE%BE%E5%A4%87%E5%B1%9E%E6%80%A7%E4%B8%8A%E6%8A%A5)
- [设备远程控制](#%E8%AE%BE%E5%A4%87%E8%BF%9C%E7%A8%8B%E6%8E%A7%E5%88%B6)
- [获取设备最新上报信息](#%E8%8E%B7%E5%8F%96%E8%AE%BE%E5%A4%87%E6%9C%80%E6%96%B0%E4%B8%8A%E6%8A%A5%E4%BF%A1%E6%81%AF)
- [设备事件上报](#%E8%AE%BE%E5%A4%87%E4%BA%8B%E4%BB%B6%E4%B8%8A%E6%8A%A5)
- [设备行为调用](#%E8%AE%BE%E5%A4%87%E8%A1%8C%E4%B8%BA%E8%B0%83%E7%94%A8)
<!-- /TOC -->
## 简介
用户创建完产品后即可定义数据模板基于导出数据模板json配置文件通过SDK提供的脚本工具可以自动生成数据模板配置代码设备调试阶段的在线调试会接收设备的上报数据并可在控制台下发控制指令到设备进行调试。本文档介绍数据模板协议。
## 数据模板协议
### 概述
物联网开发平台定义了一套通用的方法,实现设备的统一描述、统一控制,进而提供数据的流转和计算服务,实现不同设备的互联互通、数据的流转和融合,助力应用落地。
产品定义了数据模板以后,设备可以按照数据模板中的定义上报属性、事件,并可对设备下发远程控制指令,即对可写的设备属性进行修改。数据模板的管理详见 [产品定义](https://cloud.tencent.com/document/product/1081/34739)。数据模板协议包括了以下几部分。
- 设备属性上报:设备端将定义的属性根据设备端的业务逻辑向云端上报。
- 设备远程控制:从云端向设备端下发控制指令,即设置设备可写属性。
- 获取设备最新上报信息:获取设备最新的上报数据。
- 设备事件上报:设备可根据定义的数据模板中的事件,当事件被触发,则根据设备事件上报的协议上报告警、故障等事件信息。
- 设备行为:云端可以通过 RPC 的方式来要求设备执行某个动作行为,适用于应用需要实时获取设备的执行结果的场景。
### 设备属性上报
1.当设备需要向云端上报数据时,开发平台为设备设定了默认的 Topic
- 上行请求 Topic`$thing/up/property/{ProductID}/{DeviceName}`
- 上行响应 Topic`$thing/down/property/{ProductID}/{DeviceName}`
2.请求
- 设备端请求报文示例:
```json
{
"method":"report",
"clientToken":"123",
"timestamp":1212121221,
"params":{
"power_switch":1,
"color":1,
"brightness":32
}
}
```
- 参数说明:
|参数|类型|说明|
|---|---|---|
|method|String|`report`表示设备属性上报|
|clientToken|String|用于上下行消息配对标识|
|timestamp|Integer|属性上报的时间SDK默认省略|
|params|JSON|JSON 结构内为设备上报的属性值|
- sdk接口`IOT_DataTemplate_PropertyReport`
3.响应
- 云端返回设备端报文示例:
```json
{
"method":"report_reply",
"clientToken":"123",
"code":0,
"status":"some message where error"
}
```
- 响应参数说明:
|参数|类型|说明|
|---|---|---|
|method|String|`report_reply`表示云端接收设备上报后的响应报文|
|clientToken|String|用于上下行消息配对标识|
|code|Integer|`0`表示云端成功收到设备上报的属性|
|status|String|当code非0的时候, 提示错误信息|
- sdk回调`PropertyMessageCallback.method_control_callback`
### 设备远程控制
1.云端下发控制指令使用的 Topic
- 下发 Topic`$thing/down/property/{ProductID}/{DeviceName}`
- 响应 Topic`$thing/up/property/{ProductID}/{DeviceName}`
2.请求
- 远程控制请求消息格式:
```json
{
"method": "control",
"clientToken": "123",
"params": {
"power_switch": 1,
"color": 1,
"brightness": 66
}
}
```
- 请求参数说明:
|参数|类型|说明|
|---|---|---|
|method|String|control 表示云端向设备发起控制请求|
|clientToken|String|用于上下行消息配对标识|
|timestamp|Integer|属性上报的时间SDK默认省略|
|params|JSON|JSON 结构内为设备上报的属性值|
- sdk回调`PropertyMessageCallback.method_control_callback`
3.响应
- 设备响应远程控制请求消息格式:
```json
{
"method":"control_reply",
"clientToken":"123",
"code":0,
"status":"some message when error"
}
```
- 请求参数说明:
|参数|类型|说明|
|---|---|---|
|method|String|`control_reply`表示设备向云端下发的控制指令的请求响应|
|clientToken|String|用于上下行消息配对标识|
|code|Integer|0 表示设备成功接收到云端下发的控制指令|
|status|String|当code非0的时候, 提示错误信息|
- sdk接口`IOT_DataTemplate_PropertyControlReply`
### 获取设备最新上报信息
1.设备从云端接收最新消息使用的 Topic
- 请求 Topic`$thing/up/property/{ProductID}/{DeviceName}`
- 响应 Topic`$thing/down/property/{ProductID}/{DeviceName}`
2.请求
- 请求消息格式:
```json
{
"method": "get_status",
"clientToken": "123",
"type" : "control",
"showmeta": 0
}
```
- 请求参数说明:
|参数|类型|说明|
|---|---|---|
|method|String|`get_status`表示获取设备最新上报的信息|
|clientToken|String|消息ID回复的消息将会返回该数据用于请求响应消息的对比|
|type|String|表示获取什么类型的信息。`control`表示离线下发的消息;`report`表示设备上报的信息。SDK默认省略获取所有消息|
|showmeta|Integer|标识回复消息是否带`metadata`缺省为0表示不返回`metadata`主要是时间戳信息。SDK默认省略|
- sdk接口`IOT_DataTemplate_PropertyGetStatus`
3.响应
- 响应消息格式:
```json
{
"method": "get_status_reply",
"clientToken": "123",
"code": 0,
"type": "report",
"data": {
"reported": {
"power_switch": 1,
"color": 1,
"brightness": 66
}
}
}
```
- 响应参数
|参数|类型|说明|
|---|---|---|
|method|String|get_status_reply 表示获取设备最新上报信息的 reply 消息|
|clientToken|String|消息 ID回复的消息将会返回该数据用于请求响应消息的对比|
|code|Integer|0标识云端成功收到设备上报的属性|
|type|String|表示获取什么类型的信息。control 表示离线下发的消息report 表示设备上报的信息|
|data|JSON|返回具体设备上报的最新数据内容|
- sdk回调`PropertyMessageCallback.method_get_status_reply_callback`
### 设备事件上报
1.当设备需要向云端上报事件时,开发平台为设备设定了默认的 Topic
- 上行请求 Topic`$thing/up/event/<产品>/<设备>`
- 上行响应 Topic`$thing/down/event/<产品>/<设备>`
2.请求
- 设备端请求报文示例:
```json
{
"method":"event_post",
"clientToken":"123",
"version":"1.0",
"eventId":"PowerAlarm",
"type":"fatal",
"timestamp":1212121221,
"params":{
"Voltage":2.8,
"Percent":20
}
}
```
- 请求参数说明:
|参数|类型|说明|
|---|---|---|
|method|String|`event_post`表示事件上报|
|clientToken|String|消息 ID回复的消息将会返回该数据用于请求响应消息的对比|
|version|String|协议版本默认为1.0|
|eventId|String|事件的 Id在物模型事件中定义|
|type|String|事件类型,`info`:信息;`alert`:告警;`fault`:故障|
|timestamp|String|事件的参数,在物模型事件中定义|
|params|String|事件的参数,在物模型事件中定义|
- sdk接口`IOT_DataTemplate_EventPost`
3.响应
- 响应消息格式:
```json
{
"method": "event_reply",
"clientToken": "123",
"version": "1.0",
"code": 0,
"status": "some message where error",
"data": {}
}
```
- 响应参数:
|参数|类型|说明|
|---|---|---|
|method|String|`event_reply`表示是云端返回设备端的响应|
|clientToken|String|消息 ID回复的消息将会返回该数据用于请求响应消息的对比|
|version|String|协议版本默认为1.0|
|code|Integer|事件上报结果0表示成功|
|status|String|事件上报结果描述|
|data|JSON|事件上报返回的内容|
- sdk回调`EventMessageCallback.method_event_reply_callback`
### 设备行为调用
1.当应用通过云端向设备发起某个行为调用时,开发平台为设备行为的处理设定了默认的 Topic
- 应用调用设备行为 Topic`$thing/down/action/<产品>/<设备>`
- 设备响应行为执行结果 Topic`$thing/up/action/<产品>/<设备>`
2.请求
- 应用端发起设备行为调用报文示例:
```json
{
"method": "action",
"clientToken": "20a4ccfd-d308-11e9-86c6-5254008a4f10",
"actionId": "openDoor",
"timestamp": 1212121221,
"params": {
"userid": "323343"
}
}
```
- 请求参数说明:
|参数|类型|说明|
|---|---|---|
|method|String|`action`表示是调用设备的某个行为|
|clientToken|String|消息 ID回复的消息将会返回该数据用于请求响应消息的对比|
|actionId|String|`actionId`是数据模板中的行为标识符,由开发者自行根据设备的应用场景定义|
|timestamp|Integer|行为调用的当前时间SDK默认省略|
|params|String|行为的调用参数,在数据模板的行为中定义|
- sdk回调`ActionMessageCallback.method_action_callback`
3.响应
- 响应消息格式:
```json
{
"method": "action_reply",
"clientToken": "20a4ccfd-d308-11e9-86c6-5254008a4f10",
"code": 0,
"status": "some message where error",
"response": {
"Code": 0
}
}
```
- 响应参数:
|参数|类型|说明|
|---|---|---|
|method|String|`action_reply`表示是设备端执行完指定的行为向云端回复的响应|
|clientToken|String|消息 ID回复的消息将会返回该数据用于请求响应消息的对比|
|code|Integer|行为执行结果0表示成功|
|status|String|行为执行失败后的错误信息描述|
|response|String|设备行为中定义的返回参数,设备行为执行成功后,向云端返回执行结果|
- sdk接口`IOT_DataTemplate_ActionReply`

View File

@@ -0,0 +1,162 @@
<!-- TOC -->
- [数据模板业务逻辑开发发](#%E6%95%B0%E6%8D%AE%E6%A8%A1%E6%9D%BF%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91%E5%BC%80%E5%8F%91%E5%8F%91)
- [下行业务逻辑实现](#%E4%B8%8B%E8%A1%8C%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91%E5%AE%9E%E7%8E%B0)
- [上行业务逻辑实现现](#%E4%B8%8A%E8%A1%8C%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91%E5%AE%9E%E7%8E%B0%E7%8E%B0)
<!-- /TOC -->
## 数据模板业务逻辑开发发
数据模板示例`data_template_app.c`已实现数据、事件收发及响应的通用处理框架。可以基于此示例开发业务逻辑,
上下行业务逻辑处理的入口函数分别为 `_usr_init``_method_control_callback``_cycle_report``_method_action_callback`
- 属性
属性即对设备能力的描述,譬如智能灯,通过 `开关``颜色``亮度`三个属性实现对智能灯的能力描述,通过对属性的修改即可实现对设备的控制。
- 事件
设备发生特定情况,譬如灯的开关状态发生了变化,上报事件。应用侧收到事件后按预设逻辑推送事件。
- 行为
控制设备执行特定的行为,并将执行的结果返回。行为与属性的区别,概念上行为是数据和方法的组合,行为有执行结果的返回。属性只有数据,修改属性后设备侧是否执行成功很难在属性本身体现。
### 下行业务逻辑实现
- 服务端下行的数据SDK已按数据模板协议完成了 json 数据的解析。
- 用户根据数据模板数据进行相应的业务逻辑处理,以下提供基于智能灯的示例,用户可以根据自己的物模型进行修改
```c
static void _handle_property_callback(void *client, int is_get_status)
{
for (UsrPropertyIndex i = USR_PROPERTY_INDEX_POWER_SWITCH; i <= USR_PROPERTY_INDEX_POWER; i++) {
if (usr_data_template_property_status_get(i)) { // 如果状态改变,说明收到了相应的属性
DataTemplatePropertyValue value;
switch (i) {
case USR_PROPERTY_INDEX_POWER_SWITCH:
case USR_PROPERTY_INDEX_COLOR:
case USR_PROPERTY_INDEX_BRIGHTNESS: // read only, just for example
case USR_PROPERTY_INDEX_POWER: // read only, just for example
value = usr_data_template_property_value_get(i); // 获取相应的属性值
Log_d("recv %s:%d", usr_data_template_property_key_get(i), value.value_int);
break;
case USR_PROPERTY_INDEX_NAME: // read only, just for example
value = usr_data_template_property_value_get(i);
Log_d("recv %s:%s", usr_data_template_property_key_get(i), value.value_string);
break;
case USR_PROPERTY_INDEX_POSITION: // read only, just for example
for (UsrPropertyPositionIndex j = USR_PROPERTY_POSITION_INDEX_LONGITUDE;
j <= USR_PROPERTY_POSITION_INDEX_LATITUDE; j++) {
value = usr_data_template_property_struct_value_get(i, j);
Log_d("recv %s:%d", usr_data_template_property_struct_key_get(i, j), value.value_int);
}
break;
default:
break;
}
usr_data_template_property_status_reset(i); //处理完需要重置属性状态
}
}
}
```
```c
static void _method_action_callback(UtilsJsonValue client_token, UtilsJsonValue action_id, UtilsJsonValue params,
void *usr_data)
{
UsrActionIndex index;
int rc;
DataTemplatePropertyValue value_time, value_color, value_total_time;
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);
// 数据模板行为 json 格式解析
rc = usr_data_template_action_parse(action_id, params, &index);
if (rc) {
return;
}
// 修改和添加下行行为数据的具体业务逻辑
switch (index) {
case USR_ACTION_INDEX_LIGHT_BLINK:
value_time = usr_data_template_action_input_value_get(USR_ACTION_INDEX_LIGHT_BLINK,
USR_ACTION_LIGHT_BLINK_INPUT_INDEX_TIME);
value_color = usr_data_template_action_input_value_get(USR_ACTION_INDEX_LIGHT_BLINK,
USR_ACTION_LIGHT_BLINK_INPUT_INDEX_COLOR);
value_total_time = usr_data_template_action_input_value_get(USR_ACTION_INDEX_LIGHT_BLINK,
USR_ACTION_LIGHT_BLINK_INPUT_INDEX_TOTAL_TIME);
Log_i("light[%d] blink %d every %d s ", value_color.value_enum, value_time.value_int,
value_total_time.value_int);
// 下行行为数据处理结果的回复
usr_data_template_action_reply(usr_data, buf, sizeof(buf), index, client_token, 0, "{\"err_code\":0}");
break;
default:
break;
}
}
```
### 上行业务逻辑实现现
- SDK提供了属性、事件、行为的上行接口用户可以按数据模板协议自行构造数据然后调用接口上报相应数据。
1. 属性上报接口`usr_data_template_property_report`
2. 事件上报接口`usr_data_template_event_post`
3. 行为回复接口`usr_data_template_action_reply`
- 数据模板示例 data_template_app.c 已提供了属性、事件、行为数据构造和上报的框架。
1.对于属性上报,调用修改对应属性参数的接口 `usr_data_template_property_value_set`,然后调用属性上报接口`usr_data_template_property_report`会将需要上报的属性信息上报。
```c
// 参考 _usr_init 函数,设备在业务运行过程中修改属性后需要调用以下对应函数修改对应的属性
// usr_data_template_property_report(client, buf, sizeof(buf)); 函数进行属性数据的上报
static void _usr_init(void)
{
usr_data_template_init();
DataTemplatePropertyValue value;
value.value_int = 0;
usr_data_template_property_value_set(USR_PROPERTY_INDEX_POWER_SWITCH, value);
value.value_enum = 0;
usr_data_template_property_value_set(USR_PROPERTY_INDEX_COLOR, value);
value.value_int = 10;
usr_data_template_property_value_set(USR_PROPERTY_INDEX_BRIGHTNESS, value);
value.value_string = "light";
usr_data_template_property_value_set(USR_PROPERTY_INDEX_NAME, value);
value.value_int = 30;
usr_data_template_property_struct_value_set(USR_PROPERTY_INDEX_POSITION, USR_PROPERTY_POSITION_INDEX_LONGITUDE,
value);
value.value_int = 30;
usr_data_template_property_struct_value_set(USR_PROPERTY_INDEX_POSITION, USR_PROPERTY_POSITION_INDEX_LATITUDE,
value);
value.value_string = "high";
usr_data_template_property_value_set(USR_PROPERTY_INDEX_POWER, value);
}
```
2.对于事件,在事件产生的地方,构造事件的 json 串并调用事件上报接口`usr_data_template_event_post`上报。
```c
static void _cycle_report(void *client)
{
char buf[256];
static QcloudIotTimer sg_cycle_report_timer;
if (IOT_Timer_Expired(&sg_cycle_report_timer)) {
// 添加和修改业务需要上报的事件数据
usr_data_template_event_post(client, buf, sizeof(buf), USR_EVENT_INDEX_STATUS_REPORT,
"{\"status\":0,\"message\":\"ok\"}");
IOT_Timer_Countdown(&sg_cycle_report_timer, 60);
}
}
```
3.对于行为,在行为的回调 `_method_action_callback` 中,添加业务逻辑执行对应的行为,根据行为执行结果,构造行为的输出参数,调用行为回复接口`usr_data_template_action_reply`上报行为执行结果。
```c
usr_data_template_action_reply(usr_data, buf, sizeof(buf), index, client_token, 0, "{\"err_code\":0}");
```