Files
whtp/schema.md

523 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WH Telemetry Protocol Specification
WH Telemetry ProtocolWHTP是一套专为物联网场景设计的轻量级、强自描述性遥测数据上行协议采用 JSON 作为数据承载格式。WHTP 面向传感器数据、遥测采集、边缘网关等设备与云端的数据交互,兼顾数据结构规范、易于解析与灵活拓展,适用于农业、环境监测、工业自动化等多种场景。
协议设计核心特点:
- 自描述:字段类型、单位、置信度、枚举、错误信息等全部可内嵌于消息,无需依赖外部 schema 即可独立解析
- 结构统一:所有遥测消息采用统一的数据结构,便于前端/平台/存储适配与二次开发
- 严格类型:禁止 null、嵌套数组所有值类型需显式声明保证数据一致性与可校验性
- 批量与单点兼容支持标量、等间隔数组与非等间隔irregular三种采样模式灵活适配各类采集需求
- 可扩展性预留元数据metadata与用户自定义信息user支持平台与业务个性化扩展
- 错误与置信机制:内建错误码、错误信息与置信度表达,方便数据质量管理与自动化校验
消息结构总览:
- 根级字段:描述消息身份、设备、时间、环境等全局信息
- 字段组fields每个测量项field携带唯一 ID、测量值value、类型元信息metadata
## 根级字段
| 字段 | 必选 | 类型 | 说明 |
| --------- | ---- | --------------- | ---------------------------------------------- |
| version | 是 | number | 协议主版本号 |
| msg_type | 是 | string | 消息类别,目前仅 `"telemetry"` |
| mode | 否 | string | 工作模式,`"descriptive"`(默认)或 `"strict"` |
| dev_id | 是 | string | 设备唯一标识NanoID |
| dev_type | 是 | string / object | 设备型号标识 |
| timestamp | 是 | `Timestamp` | 消息生成时刻 |
| fields | 是 | `Field[]` | 采集字段数组 |
| metadata | 否 | object | 消息级元数据 |
### 版本
`version` 描述协议主版本号, 必填, 其类型为 `number`, 仅表示协议主版本号。当前版本为 **1**
### 消息类型
`msg_type` 描述消息类型, 必填, 其类型为 `string`, 目前仅允许 `"telemetry"`,用于上传遥测数据。
### 消息模式
`mode` 描述消息模式, 可选, 其类型为 `string`, 默认 `"descriptive"`
* `descriptive` —— 自描述模式,允许在字段 `metadata` 中携带全部上下文信息,消息本身即可被独立解析。
* `strict` (RFU, reserved for future use) —— 严格模式,不允许出现字段级 `metadata`,字段定义需通过外部文档约定,适用于带宽受限或固定 Schema 场景。(目前无实际意义, 需要与二进制协议配合)
### 设备ID
`dev_id` 描述设备唯一标识, 必填, 其类型为 `string`, 采用 [NanoID](https://github.com/ai/nanoid) 格式(如 `2B1oj2S5k7kS8mL_QaCs`),在设备生命周期内保持不变。
### 设备类型
```typescript
type DeviceType = string | { type_id: string } | { type_name: string }
```
`dev_type` 描述设备型号, 必填, 其类型为 `DeviceType`, 用于标识设备类别或型号。支持三种写法:
1. **字符串**:例如 `"device_type_name"`;与 `{ "type_name": string }` 等价
2. **对象** `{ "type_id": NanoID }`NanoID 为设备型号的唯一标识, 由平台分配
3. **对象** `{ "type_name": string }`
> [!NOTE]
>
> 以上三种写法在语义上等价, 由数据生产方按需选择
>
> - 对于长期稳定的系统, 推荐使用 `{ "type_id": NanoID }` 格式, 作为稳定的唯一标识符
> - 对于临时场景, 可以使用 `string` 或 `{ "type_name": string }` 格式
### 根级时间戳
`timestamp` 描述消息生成时刻, 其类型为 `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) 时间戳表示实际数据采样时刻, 应使用字段级时间戳进行所有时间序列分析
### 字段描述组
`fields` 描述消息中包含的测量项, 其类型为 `Field[]`, 数组中每个元素代表一个测量项, 其 `id` 在同一条消息内必须唯一, 关于 `Field` 对象的定义详见后文 [字段描述项](#%E5%AD%97%E6%AE%B5%E6%8F%8F%E8%BF%B0%E9%A1%B9) 章节
### 根级元数据
`metadata` 描述根级 (消息级) 元数据, 其类型为 `MessageMetadata`
```typescript
interface MessageMetadata {
enums?: EnumDefinition[];
location?: Location;
battery?: number;
seq?: number;
user?: Record<string, any>;
}
```
> [!NOTE]
> `user` 字段之外的 `metadata` 字段为已定义 (well-defined) 字段; 用户应优先使用已定义字段, 若不能满足需求才可使用[用户自定义字段](#%E7%94%A8%E6%88%B7%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AD%97%E6%AE%B5)
#### 全局枚举
```typescript
type InlineEnum = Record<string, number>
type EnumDefinition = { id: string; value: InlineEnum };
```
`metadata.enums` 字段类型为 `EnumDefinition[]`, 用于在字段 `fields[*].metadata.data_type` 中以 `enum:<id>` 形式引用, 如
```jsonc
"metadata": {
"enums": [
{
"id": "air_quality",
"value": { "good": 1, "moderate": 2, "bad": 3 }
}
]
}
```
等价于
```typescript
enum AirQuality {
GOOD = 1,
MODERATE = 2,
BAD = 3,
}
```
#### 位置信息
`metadata.location` 字段类型为 `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
}
```
#### 电量
`metadata.battery` 字段类型为数字, 表示设备剩余电量百分比, 其范围为 0100
#### 序列号
`metadata.seq` 字段类型为无符号整数, 每上传一条消息递增 1, 用于检测丢包或乱序, 达到上限后循环计数(默认为 32 位无符号整数回绕)
#### 根级用户自定义字段
`metadata.user` 字段为任意键值对, 为数据生产方保留的自定义信息区, 其类型为 `UserData`
```typescript
type UserData = Record<string, any>
```
## 字段描述项
每个字段 (field) 描述一个测量项, 其类型为 `Field`
### 字段标识符
`id` 描述字段标识符, 必填, 其类型为 `string`, ASCII 字符串,仅允许字母、数字与下划线,且不得以数字开头。长度建议 164 字符。例如 `temperature_sensor`
### 字段值
`value` 描述字段值, 其类型必须与 `metadata.data_type` 完全对应, 详见 ["数据类型"](#%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B) 章节
#### 标量 (scalar)
`string | number | boolean | enum<id>`, 如
```jsonc
{
"id": "example_number",
"value": 42,
"metadata": { "data_type": "number" }
}
```
```jsonc
{
"id": "example_string",
"value": "hello",
"metadata": { "data_type": "string" }
}
```
```jsonc
{
"id": "example_enum",
"value": 1, // interpreted as "good"
"metadata": { "data_type": "enum:this", "enum": { "1": "good", "2": "moderate", "3": "bad" } }
}
```
> [!NOTE]
> 枚举类型 `enum:this` 表示该字段使用内联枚举, 其定义见下文 ["枚举类型"](#%E6%9E%9A%E4%B8%BE%E7%B1%BB%E5%9E%8B) 章节
#### 等间隔数组 (array)
`T[]`,通过 `sample_interval` 指定采样间隔, T 为标量类型, 如
```jsonc
{
"id": "example_array",
"value": [1, 2, 3],
"metadata": { "data_type": "array<number>", "sample_interval": 0.5 }
}
```
> [!NOTE]
> 强烈建议所有等间隔数组都显式填写 `sample_interval` 字段, 若不填写则默认采样间隔为 1s
#### 非等间隔irregular
`{ v: T[], t: Timestamp[] }`, T 为标量类型, 如
```jsonc
{
"id": "example_irregular",
"value": { "v": [1,2,3], "t": [1715145600,1715145601,1715145603] },
"metadata": { "data_type": "irregular<number>" }
}
```
`t` 数组必须与 `v` 数组长度相同, 且 `t` 数组中的时间戳必须严格递增, 否则会被视为非法数据
> [!IMPORTANT]
>
> - `null` 与 `undefined` 不被支持
> - 嵌套数组(如 `array<array<number>>`)亦为非法类型
### 字段级元数据
field 级字段元数据, 用于描述 `value` 的类型, 采样间隔, 单位, 标签, 置信度, 错误码, 错误信息, 内联枚举, 用户自定义信息等, 其类型为 `FieldMetadata`
#### 数据类型
`data_type` 用于描述 `value` 的类型, 其类型为 `DataType`
```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** 直接对应于 JSON 支持的基本数据类型。所有字段的最基本取值都必须属于原始型别。
```haskell
type PrimitiveType = string | number | boolean
```
其中:
- `string`:任意长度的 UTF-8 文本字符串
- `number`IEEE-754 双精度浮点数
- `boolean`:布尔值,仅允许 true 或 false不支持 0/1 或字符串)
##### 枚举类型
在本协议中,枚举类型 (`enum`, [Enumeration](https://en.wikipedia.org/wiki/Enumeration)) 本质上是一种特殊的 number 类型。其取值必须限定在一组预先定义的枚举值中。枚举的具体定义可以来源于:
- 全局定义:`metadata.enums`(适用于多个字段复用的场景)
- 字段内联定义:`fields[*].metadata.enum`(只对当前字段有效)
每个枚举值都必须对应唯一的数值number。在类型系统中枚举类型可以视为带有标签约束的 number:
```haskell
newtype Enum = number
```
##### 标量类型
标量类型 (scalar) 为 `string | number | boolean | Enum` 的统称, 其定义见上文
```haskell
type ScalarType = PrimitiveType | Enum
```
##### 等间隔数组类型
本协议中的数组类型在 `field` 中的含义为 **等采样率** 的数组 (array), 其定义为:
```haskell
type ArrayType T = T[] -- array<T>
```
##### 非等间隔数组类型
本协议中的非等间隔数组类型在 `field` 中的含义为 **非等采样率** 或 **采样率未知** 的数组, 其定义为:
```haskell
type IrregularType T = { v: T[], t: Timestamp[] } -- irregular<T>
```
`t` 数组必须与 `v` 数组长度相同, 且 `t` 数组中的时间戳必须严格递增, 否则会被视为非法数据
等间隔数组与非等间隔数组统称为批量 (batch) 数据
```haskell
type BatchType T = ArrayType T | IrregularType T
```
#### 字段时间戳
`timestamp` 描述字段时间戳, 可选, 其类型为 `Timestamp`, 若不存在则取 `root.timestamp` 的值; 表示该字段 **采样时刻** 的时间戳
#### 标签
`label` 描述字段展示名, 可选, 其类型为 `string`, 便于人机界面直观呈现。实际是否使用/如何使用由数据消费方决定。
示例:`"label": "温度"`
#### 错误码
`error_code` 描述错误码, 可选, 其类型为 `number`, 非零整数。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 信号丢失,无位置信息 |
| 128511 | 预留 | — | 预留给未来通用错误 |
| 5121023 | 应用 | — | 留给具体应用 / 产品线自定义 |
| 102465535 | 厂商自定义 | — | 厂商或项目私有错误码区间 |
#### 错误信息
`error_msg` 描述错误信息, 可选, 其类型为 `string`, 对 `error_code` 进行人类可读的补充说明。
#### 置信度
```typescript
type Confidence = number | number[]
```
`confidence` 描述置信度, 可选, 其类型为 `Confidence`, 用于表征 value 数据的可靠性,其取值类型可为单个数值(`number`)或数值数组(`number[]`),具体规则如下:
- 当 confidence 为单个数值时,表示该字段所有采样点共用相同的置信度。此形式适用于任意类型的 value包括单点和批量数据
- 当 confidence 为数值数组时,表示每个采样点各自的置信度。此形式仅适用于批量数据(即 value 为数组或不等间隔序列);此时 confidence 数组长度必须与采样点数量严格一致。
置信度的取值范围为 0 至 1其中 0 表示完全不可信1 表示完全可信100%)。
> [!NOTE]
> 当一个字段包含非零的 `error_code` 时, 强烈建议将置信度设置为 0 以表示该字段值不可靠
> [!WARNING]
> - 对于单值scalar数据不应填写数组形式的置信度若填写则无意义解析器将会忽略
> - 对于批量数据,既可填写单一置信度(所有采样点共享),也可填写置信度数组(每个采样点单独指定)
> - 如未指定 confidence 字段,则默认所有采样点的置信度为 1
#### 采样间隔
`sample_interval` 描述采样间隔, 可选, 其类型为 `SampleInterval`, 用于描述 `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 秒)
> [!IMPORTANT]
> 强烈建议所有等间隔数组都显式填写 `sample_interval` 字段
#### 单位
`unit` 描述单位, 可选, 其类型为 `string`, 推荐使用 [UCUM](https://ucum.org/ucum) 码(如 `"Cel"` 代表摄氏度),也支持自由字符串。
示例:`"unit": "Cel"`
#### 内联枚举
`enum` 描述内联枚举, 可选, 其类型为 `InlineEnum`, 当 `data_type` 为 `enum:this` 及其衍生类型时, 用于定义内联枚举值
```typescript
type InlineEnum = Record<string, number>
```
#### 字段级用户自定义字段
`user` 描述字段级用户自定义字段, 可选, 类型定义同 [根级用户自定义字段](#%E6%A0%B9%E7%BA%A7%E7%94%A8%E6%88%B7%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AD%97%E6%AE%B5)
## 伪代码类型定义
以下以 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;
}
```