463 lines
16 KiB
Markdown
463 lines
16 KiB
Markdown
# WH Telemetry Protocol
|
||
|
||
WH Telemetry Protocol(简称 WHTP)是一套以 JSON 为载体的遥测数据上行协议,强调自描述能力与轻量化设计,适用于物联网设备与云端之间的高效数据传输。
|
||
|
||
## 总览
|
||
|
||
WHTP 消息由根级字段与 `fields` 数组两部分组成。根级字段描述消息本身及其环境;`fields` 数组承载具体测量项。根级字段概览如下:
|
||
|
||
| 字段 | 必选 | 类型 | 说明 |
|
||
| --------- | ---- | --------------- | ---------------------------------------------- |
|
||
| version | 是 | number | 协议主版本号 |
|
||
| msg_type | 是 | string | 消息类别,目前仅 `"telemetry"` |
|
||
| mode | 否 | string | 工作模式,`"descriptive"`(默认)或 `"strict"` |
|
||
| dev_id | 是 | string | 设备唯一标识(UUID v4) |
|
||
| dev_type | 是 | string / object | 设备型号标识 |
|
||
| timestamp | 是 | Timestamp | 消息生成时刻 |
|
||
| fields | 是 | Field[] | 采集字段数组 |
|
||
| metadata | 否 | object | 消息级元数据 |
|
||
|
||
## 根级字段
|
||
|
||
### Version (`version`)
|
||
|
||
必填。正整数,仅表示协议主版本号。当前版本为 **1**。
|
||
|
||
### 消息类型 (`msg_type`)
|
||
|
||
必填。字符串。目前仅允许 `"telemetry"`,用于上传遥测数据。
|
||
|
||
### 消息模式 (`mode`)
|
||
|
||
可选,字符串,默认 `"descriptive"`。
|
||
|
||
* `descriptive` —— 自描述模式,允许在字段 `metadata` 中携带全部上下文信息,消息本身即可被独立解析。
|
||
* `strict` (RFU, reserved for future use) —— 严格模式,不允许出现字段级 `metadata`,字段定义需通过外部文档约定,适用于带宽受限或固定 Schema 场景。(目前无实际意义, 需要与二进制协议配合)
|
||
|
||
### 设备 ID (`dev_id`)
|
||
|
||
必填。设备唯一标识,采用 UUID v4 格式(如 `148413b4-c352-49a9-9c48-9d15276a99e7`),在设备生命周期内保持不变。
|
||
|
||
### 设备类型 (`dev_type`)
|
||
|
||
```typescript
|
||
type DeviceType = string | { type_id: string } | { type_name: string }
|
||
```
|
||
|
||
必选。用于标识设备类别或型号。支持三种写法:
|
||
|
||
1. **字符串**:例如 `"device_type_name"`;与 `{ "type_name": string }` 等价
|
||
2. **对象** `{ "type_id": UUID }`;UUID 为设备型号的唯一标识, 由平台分配
|
||
3. **对象** `{ "type_name": string }`。
|
||
|
||
> [!NOTE]
|
||
>
|
||
> 以上三种写法在语义上等价, 由数据生产方按需选择
|
||
>
|
||
> - 对于长期稳定的系统, 推荐使用 `{ "type_id": UUID }` 格式, 作为稳定的唯一标识符
|
||
> - 对于临时场景, 可以使用 `string` 或 `{ "type_name": string }` 格式
|
||
|
||
### 根级时间戳 (`timestamp`)
|
||
|
||
```typescript
|
||
type Timestamp =
|
||
| number // UNIX seconds (default), same as `{ unix_s: number }`
|
||
| { unix_s: number }
|
||
| { unix_ms: number }
|
||
| { unix_us: number }
|
||
| { unix_ns: number }
|
||
| { iso8601: string } // e.g. "2025-01-01T00:00:00Z"
|
||
```
|
||
|
||
所有时间字段(如顶层 `timestamp`、field 级 `timestamp`、irregular `timestamp[]`)应采用以下格式之一:
|
||
|
||
- `number`:默认解释为 UNIX 秒(UTC)
|
||
- `object`:显式指定格式,支持以下键:
|
||
- `unix_s` → UNIX 秒
|
||
- `unix_ms` → UNIX 毫秒
|
||
- `unix_us` → UNIX 微秒
|
||
- `unix_ns` → UNIX 纳秒
|
||
- `iso8601` → [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) 标准格式(必须为 UTC,以 Z 结尾或包含 +00:00 时区偏移)
|
||
|
||
> [!WARNING]
|
||
> `"timestamp": "2025-01-01T00:00:00Z"` 为非法, 应使用 `"timestamp": { "iso8601": "2025-01-01T00:00:00Z" }` 或 `"timestamp": 1715145600`
|
||
|
||
> [!NOTE]
|
||
> 根级 (root-level) 时间戳表示消息生成或入队时刻, 字段级 (field-level) 时间戳表示实际数据采样时刻, 应使用字段级时间戳进行所有时间序列分析
|
||
|
||
### field 字段 (`fields`)
|
||
|
||
必填。`Field` 对象数组,数组中每个元素代表一个测量项,其 `id` 在同一条消息内必须唯一。关于 `Field` 对象的定义详见后文 "field 字段" 章节。
|
||
|
||
### 消息元数据 (`metadata`)
|
||
|
||
`user` 字段之外的 `metadata` 字段为已定义 (well-defined) 字段, 其定义见下文
|
||
|
||
```typescript
|
||
interface MessageMetadata {
|
||
enums?: EnumDefinition[];
|
||
location?: Location;
|
||
battery?: number;
|
||
seq?: number;
|
||
user?: Record<string, any>;
|
||
}
|
||
```
|
||
|
||
#### 全局枚举 (`enums`)
|
||
|
||
可选。全局枚举定义表,用于在字段 `fields[*].metadata.data_type` 中以 `enum:<id>` 形式引用。
|
||
|
||
```typescript
|
||
type InlineEnum = Record<string, number>
|
||
type EnumDefinition = { id: string; value: InlineEnum };
|
||
```
|
||
|
||
示例
|
||
|
||
```jsonc
|
||
"metadata": {
|
||
"enums": [
|
||
{
|
||
"id": "air_quality",
|
||
"value": { "good": 1, "moderate": 2, "bad": 3 }
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
等价于
|
||
|
||
```cpp
|
||
enum class AirQuality {
|
||
GOOD = 1,
|
||
MODERATE = 2,
|
||
BAD = 3,
|
||
};
|
||
```
|
||
|
||
#### 位置信息 (`location`)
|
||
|
||
可选。表示消息采集时的设备地理位置
|
||
|
||
```typescript
|
||
type Location = {
|
||
coordinates: [number, number]; // 经度 (longitude)、纬度 (latitude)(度); 顺序不可颠倒
|
||
coordinate_system?: "wgs84" | "gcj02" | "bd09"; // 可选,指定坐标系, 默认 "gcj02"
|
||
accuracy?: number; // 可选,水平精度(米)
|
||
altitude?: number; // 可选,海拔高度(米)
|
||
}
|
||
```
|
||
|
||
示例
|
||
|
||
```jsonc
|
||
{
|
||
"coordinates": [120.123456, 24.123456], // 经度、纬度(度)
|
||
"coordinate_system": "wgs84",
|
||
"accuracy": 10,
|
||
"altitude": 100
|
||
}
|
||
```
|
||
|
||
#### 电量 (`battery`)
|
||
|
||
可选。数字,范围 0–100,对应设备剩余电量百分比。
|
||
|
||
#### 序列号 (`seq`)
|
||
|
||
可选。无符号整数。每上传一条消息递增 1,用于检测丢包或乱序。达到上限后循环计数(默认为 32 位无符号整数回绕)。
|
||
|
||
#### 用户自定义信息 (`user`)
|
||
|
||
可选。任意键值对,为数据生产方保留的自定义信息区
|
||
|
||
```typescript
|
||
type UserData = Record<string, any>
|
||
```
|
||
|
||
## 消息描述字段 (fields)
|
||
|
||
### 标识符 (`id`)
|
||
|
||
必填。ASCII 字符串,仅允许字母、数字与下划线,且不得以数字开头。长度建议 1–64 字符。例如 `temperature_sensor`。
|
||
|
||
### 值 (`value`)
|
||
|
||
字段实际测量值,其类型必须与 `metadata.data_type` 完全对应,形态分三类:
|
||
|
||
1. **标量** — `string | number | boolean | enum`
|
||
```jsonc
|
||
"value": 42
|
||
```
|
||
2. **等间隔数组 (array)** — `T[]`,通过 `sample_interval` 指定采样间隔
|
||
```jsonc
|
||
"value": [1, 2, 3],
|
||
"metadata": { "data_type": "array<number>", "sample_interval": 0.5 }
|
||
```
|
||
3. **非等间隔(irregular)** — `{ v: T[], t: Timestamp[] }`
|
||
```jsonc
|
||
"value": { "v": [1,2,3], "t": [1715145600,1715145601,1715145603] },
|
||
"metadata": { "data_type": "irregular<number>" }
|
||
```
|
||
`t` 数组必须与 `v` 数组长度相同, 且 `t` 数组中的时间戳必须严格递增, 否则会被视为非法数据
|
||
|
||
`null` 与 `undefined` 不被支持,嵌套数组(如 `array<array<number>>`)亦为非法。
|
||
|
||
### 元数据 (`metadata`)
|
||
|
||
field 级字段元数据, 用于描述 `value` 的类型, 采样间隔, 单位, 标签, 置信度, 错误码, 错误信息, 内联枚举, 用户自定义信息等
|
||
|
||
#### 数据类型 (`data_type`)
|
||
|
||
必填。字符串。用于描述 `value` 的类型。
|
||
|
||
```typescript
|
||
type DataType =
|
||
| ScalarType
|
||
| ArrayType ScalarType
|
||
| IrregularType ScalarType
|
||
```
|
||
|
||
所有合法的 `data_type` 如下:
|
||
|
||
```typescript
|
||
type DataType =
|
||
| "string"
|
||
| "number"
|
||
| "boolean"
|
||
| `enum:${string}` // global enum
|
||
| "enum:this" // inline enum
|
||
| `array<string>`
|
||
| `array<number>`
|
||
| `array<boolean>`
|
||
| `array<enum:${string}>`
|
||
| `array<enum:this>`
|
||
| `irregular<string>`
|
||
| `irregular<number>`
|
||
| `irregular<boolean>`
|
||
| `irregular<enum:${string}>`
|
||
| `irregular<enum:this>`
|
||
```
|
||
|
||
- `null` 和 `undefined` 是非法的
|
||
- 嵌套是非法的
|
||
|
||
下面分别介绍各个类型
|
||
|
||
##### 原始型別 (`primitive`)
|
||
|
||
协议中的 **原始型别(primitive)** 直接对应于 JSON 支持的基本数据类型。所有字段的最基本取值都必须属于原始型别。
|
||
|
||
```haskell
|
||
type PrimitiveType = string | number | boolean
|
||
```
|
||
|
||
其中:
|
||
|
||
- `string`:任意长度的 UTF-8 文本字符串
|
||
- `number`:IEEE-754 双精度浮点数
|
||
- `boolean`:布尔值,仅允许 true 或 false(不支持 0/1 或字符串)
|
||
|
||
##### 枚举类型 (`enums`)
|
||
|
||
在本协议中,枚举类型本质上是一种特殊的 number 类型。其取值必须限定在一组预先定义的枚举值中。枚举的具体定义可以来源于:
|
||
|
||
- 全局定义:`metadata.enums`(适用于多个字段复用的场景)
|
||
- 字段内联定义:`fields[*].metadata.enum`(只对当前字段有效)
|
||
|
||
每个枚举值都必须对应唯一的数值(number)。在类型系统中,枚举类型可以视为带有标签约束的 number:
|
||
|
||
```haskell
|
||
newtype Enum = number
|
||
```
|
||
|
||
##### 标量类型 (`scalar`)
|
||
|
||
标量类型为 `string | number | boolean | Enum` 的统称, 其定义见上文
|
||
|
||
```haskell
|
||
type ScalarType = PrimitiveType | Enum
|
||
```
|
||
|
||
##### 等间隔数组类型 (`array`)
|
||
|
||
本协议中的数组类型在 `field` 中的含义为 **等采样率** 的数组, 其定义为:
|
||
|
||
```haskell
|
||
type ArrayType T = T[] -- array<T>
|
||
```
|
||
|
||
##### 非等间隔数组类型 (`irregular`)
|
||
|
||
本协议中的非等间隔数组类型在 `field` 中的含义为 **非等采样率** 或 **采样率未知** 的数组, 其定义为:
|
||
|
||
```haskell
|
||
type IrregularType T = { v: T[], t: Timestamp[] } -- irregular<T>
|
||
```
|
||
|
||
`t` 数组必须与 `v` 数组长度相同, 且 `t` 数组中的时间戳必须严格递增, 否则会被视为非法数据
|
||
|
||
等间隔数组与非等间隔数组统称为批量 (batch) 数据
|
||
|
||
```haskell
|
||
type BatchType T = ArrayType T | IrregularType T
|
||
```
|
||
|
||
#### field 时间戳 (`timestamp`)
|
||
|
||
可选。若不存在则取 `root.timestamp` 的值; 表示该字段 **采样时刻** 的时间戳, 其类型为 `Timestamp`
|
||
|
||
#### 标签 (`label`)
|
||
|
||
可选。字段展示名,UTF-8 字符串,便于人机界面直观呈现。实际是否使用/如何使用由数据消费方决定。
|
||
|
||
示例:`"label": "温度"`
|
||
|
||
#### 错误码 (`error_code`)
|
||
|
||
可选。非零整数。0 表示无错误;其他值与以下错误码表对应,用于描述测量异常。
|
||
|
||
| 代码 | 分类 | 名称 | 描述 |
|
||
| ---------- | ---------- | --------------------- | ------------------------------------------ |
|
||
| 0 | 通用 | OK | 无错误(**不应在 `error_code` 字段出现**) |
|
||
| 1 | 通用 | UNKNOWN_ERROR | 未知错误,无法归类 |
|
||
| 2 | 通用 | UNSUPPORTED_DATA_TYPE | 不支持的数据类型或格式 |
|
||
| 3 | 通用 | INVALID_VALUE | 字段值非法(超范围、格式错等) |
|
||
| 16 | 设备 | DEVICE_BUSY | 设备繁忙,暂无法提供数据 |
|
||
| 17 | 设备 | DEVICE_INIT_FAILED | 设备初始化失败 |
|
||
| 32 | 传输 | PACKET_LOST | 报文丢失(重传失败) |
|
||
| 33 | 传输 | CRC_ERROR | 校验和 / CRC 错误 |
|
||
| 48 | 供电 | LOW_BATTERY | 电量过低,测量值可能不可靠 |
|
||
| 64 | 传感器 | SENSOR_DISCONNECTED | 传感器掉线或未接入 |
|
||
| 65 | 传感器 | SENSOR_MALFUNCTION | 传感器故障(无响应、数据乱流等) |
|
||
| 66 | 传感器 | SENSOR_OUT_OF_RANGE | 超出测量范围 |
|
||
| 80 | 校准 | CALIBRATION_REQUIRED | 需要校准或校准过期 |
|
||
| 96 | 存储 | STORAGE_FULL | 本地存储空间不足 |
|
||
| 112 | GNSS | GPS_FIX_LOST | GNSS 信号丢失,无位置信息 |
|
||
| 128–511 | 预留 | — | 预留给未来通用错误 |
|
||
| 512–1023 | 应用 | — | 留给具体应用 / 产品线自定义 |
|
||
| 1024–65535 | 厂商自定义 | — | 厂商或项目私有错误码区间 |
|
||
|
||
#### 错误信息 (`error_msg`)
|
||
|
||
可选。UTF-8 字符串,对 `error_code` 进行人类可读的补充说明。
|
||
|
||
#### 置信度 (`confidence`)
|
||
|
||
```typescript
|
||
type Confidence = number | number[]
|
||
```
|
||
|
||
置信度用于表征 value 数据的可靠性,其取值类型可为单个数值(`number`)或数值数组(`number[]`),具体规则如下:
|
||
|
||
- 当 confidence 为单个数值时,表示该字段所有采样点共用相同的置信度。此形式适用于任意类型的 value(包括单点和批量数据)。
|
||
- 当 confidence 为数值数组时,表示每个采样点各自的置信度。此形式仅适用于批量数据(即 value 为数组或不等间隔序列);此时 confidence 数组长度必须与采样点数量严格一致。
|
||
|
||
置信度的取值范围为 0 至 1,其中 0 表示完全不可信,1 表示完全可信(100%)。
|
||
|
||
> [!NOTE]
|
||
> 当一个字段包含非零的 `error_code` 时, 强烈建议将置信度设置为 0 以表示该字段值不可靠
|
||
|
||
> [!WARNING]
|
||
> - 对于单值(scalar)数据,不应填写数组形式的置信度,若填写则无意义,解析器将会忽略
|
||
> - 对于批量数据,既可填写单一置信度(所有采样点共享),也可填写置信度数组(每个采样点单独指定)
|
||
> - 如未指定 confidence 字段,则默认所有采样点的置信度为 1
|
||
|
||
|
||
#### 采样间隔 (`sample_interval`)
|
||
|
||
可选。采样间隔, 用于描述 `value` 为等间隔数组时, 数组中每个元素的时间间隔 (建议所有等间隔数组都显式填写该字段)
|
||
|
||
```typescript
|
||
type TimeDelta = { ms?: number, s?: number, m?: number, h?: number, d?: number, w?: number }
|
||
type SampleInterval =
|
||
| number // seconds
|
||
| TimeDelta
|
||
```
|
||
|
||
- 若使用 `TimeDelta`, 则总采样间隔为
|
||
`ms + s * 1000 + m * 60 * 1000 + h * 60 * 60 * 1000 + d * 24 * 60 * 60 * 1000 + w * 7 * 24 * 60 * 60 * 1000`ms (各个 field 不允许小数)
|
||
- 若使用 `number`, 则表示采样间隔为 `number` 秒 (允许小数)
|
||
|
||
若不指定, 则默认为 1s
|
||
|
||
示例:`"sample_interval": 0.5`(两次采样间隔 0.5 秒)
|
||
|
||
#### 单位 (`unit`)
|
||
|
||
可选。测量值单位。推荐使用 [UCUM](https://ucum.org/ucum) 码(如 `"Cel"` 代表摄氏度),也支持自由字符串。
|
||
|
||
示例:`"unit": "Cel"`
|
||
|
||
#### 内联枚举 (`enum`)
|
||
|
||
可选。当 `data_type` 为 `enum:this` 及其衍生类型时, 用于定义内联枚举值
|
||
|
||
```typescript
|
||
type InlineEnum = Record<string, number>
|
||
```
|
||
|
||
#### 用户自定义信息 (`user`)
|
||
|
||
可选。任意键值对,为数据生产方保留的自定义信息区:
|
||
|
||
```typescript
|
||
type UserData = Record<string, any>
|
||
```
|
||
|
||
## 伪代码类型定义
|
||
|
||
以下以 TypeScript 伪代码展示核心结构,帮助快速对接实现:
|
||
|
||
```typescript
|
||
// 时间戳
|
||
type Timestamp =
|
||
| number // UNIX seconds
|
||
| { unix_s: number }
|
||
| { unix_ms: number }
|
||
| { unix_us: number }
|
||
| { unix_ns: number }
|
||
| { iso8601: string };
|
||
|
||
|
||
// 采样间隔
|
||
type SampleInterval = number | { ms?: number; s?: number; m?: number; h?: number; d?: number; w?: number };
|
||
|
||
// 枚举定义
|
||
type InlineEnum = Record<string, number>
|
||
type EnumDefinition = { id: string; value: InlineEnum };
|
||
|
||
// Field 元数据
|
||
interface FieldMetadata {
|
||
data_type: string;
|
||
timestamp?: Timestamp;
|
||
sample_interval?: SampleInterval; // valid only when data_type is array<T>
|
||
unit?: string;
|
||
label?: string;
|
||
confidence?: number | number[];
|
||
error_code?: number;
|
||
error_msg?: string;
|
||
enum?: InlineEnum; // inline enum when data_type === "enum:this"
|
||
user?: Record<string, any>;
|
||
}
|
||
|
||
// Field
|
||
interface Field {
|
||
id: string;
|
||
value: DataType;
|
||
metadata: FieldMetadata;
|
||
}
|
||
|
||
// 根消息
|
||
interface Message {
|
||
version: 1;
|
||
msg_type: "telemetry";
|
||
mode?: "descriptive" | "strict"; // 目前仅支持 descriptive 模式
|
||
dev_id: string;
|
||
dev_type?: string | { type_id: string } | { type_name: string };
|
||
timestamp: Timestamp;
|
||
fields: Field[];
|
||
metadata?: MessageMetadata;
|
||
}
|
||
```
|