# WH Telemetry Protocol Specification ## 概述 WH Telemetry Protocol(WHTP)是一套专为物联网场景设计的轻量级、强自描述性遥测数据上行协议,采用 JSON 作为数据承载格式。WHTP 面向传感器数据、遥测采集、边缘网关等设备与云端的数据交互,兼顾数据结构规范、易于解析与灵活拓展,适用于农业、环境监测、工业自动化等多种场景。 ### 核心特点 - 自描述:字段类型、单位、置信度、枚举、错误信息等全部可内嵌于消息,无需依赖外部 schema 即可独立解析 - 结构统一:所有遥测消息采用统一的数据结构,便于前端/平台/存储适配与二次开发 - 严格类型:禁止 null、嵌套数组,所有值类型需显式声明,保证数据一致性与可校验性 - 批量与单点兼容:支持标量、等间隔数组与非等间隔(irregular)三种采样模式,灵活适配各类采集需求 - 可扩展性:预留元数据(metadata)与用户自定义信息(user),支持平台与业务个性化扩展 - 错误与置信机制:内建错误码、错误信息与置信度表达,方便数据质量管理与自动化校验 ### 消息结构总览 - 根级字段:描述消息身份、设备、时间、环境等全局信息 - 字段组(fields):每个测量项(field)携带唯一 ID、测量值(value)、类型元信息(metadata) > [!NOTE] > 本文档假设读者熟悉 [Typescript](https://www.typescriptlang.org/) 类型系统, 以及 [JSON](https://www.json.org/json-en.html) 格式, 并使用 [Typescript](https://www.typescriptlang.org/) 作为伪代码/类型定义语言 > [!NOTE] > 请配合 [JSON Schema](https://json-schema.org/) 使用 (见 [schema.json](./schema.json)), 以获得更好的类型推导与校验体验 ## 根级字段 | 字段 | 必选 | 类型 | 说明 | | --------- | ---- | --------------- | ---------------------------------------------- | | 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; } ``` > [!NOTE] > `user` 字段之外的 `metadata` 字段为已定义 (well-defined) 字段; 用户应优先使用已定义字段, 若不能满足需求才可使用 [用户自定义字段](#%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 type InlineEnum = Record type EnumDefinition = { id: string; value: InlineEnum }; ``` `metadata.enums` 字段类型为 `EnumDefinition[]`, 用于在字段 `fields[*].metadata.data_type` 中以 `enum:` 形式引用, 如 ```jsonc "metadata": { "enums": [ { "id": "air_quality", "value": { "good": 1, "moderate": 2, "bad": 3 } } ] } ``` 等价于 ```typescript enum AirQuality { GOOD = 1, MODERATE = 2, BAD = 3, } ``` 详见 [枚举类型](#%E6%9E%9A%E4%B8%BE%E7%B1%BB%E5%9E%8B) #### 位置信息 `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` 字段类型为数字, 表示设备剩余电量百分比, 其范围为 0–100 #### 序列号 `metadata.seq` 字段类型为无符号整数, 每上传一条消息递增 1, 用于检测丢包或乱序, 达到上限后循环计数(默认为 32 位无符号整数回绕) #### 根级用户自定义字段 `metadata.user` 字段为任意键值对, 为数据生产方保留的自定义信息区, 其类型为 `UserData` ```typescript type UserData = Record ``` ## 字段描述项 每个字段 (field) 描述一个测量项, 其类型为 `Field` ### 字段标识符 `id` 描述字段标识符, 必填, 其类型为 `string`, ASCII 字符串,仅允许字母、数字与下划线,且不得以数字开头。长度建议 1–64 字符。例如 `temperature_sensor`。 ### 字段值 `value` 描述字段值, 其类型必须与 `metadata.data_type` 完全对应, 详见 ["数据类型"](#%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B) 章节 #### 标量 (scalar) `string | number | boolean | enum`, 如 ```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": { "good": 1, "moderate": 2, "bad": 3 } } } ``` > [!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", "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" } } ``` `t` 数组必须与 `v` 数组长度相同, 且 `t` 数组中的时间戳必须严格递增, 否则会被视为非法数据 > [!IMPORTANT] > > - `null` 与 `undefined` 为非法值 > - 嵌套数组(如 `array>`)为非法类型 ### 字段级元数据 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` | `array` | `array` | `array` | `array` | `irregular` | `irregular` | `irregular` | `irregular` | `irregular` ``` > [!IMPORTANT] > > - `null` 与 `undefined` 为非法值 > - 嵌套数组(如 `array>`)为非法类型 下面分别介绍各个类型 ##### 原始型別 协议中的 **原始型别(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` 的统称, 其定义见上文 ```typescript type ScalarType = PrimitiveType | Enum ``` ##### 等间隔数组类型 本协议中的数组类型在 `field` 中的含义为 **等采样率** 的数组 (array), 其定义为: ```typescript type ArrayType = T[] // array ``` ##### 非等间隔数组类型 本协议中的非等间隔数组类型在 `field` 中的含义为 **非等采样率** 或 **采样率未知** 的数组, 其定义为: ```typescript type IrregularType = { v: T[], t: Timestamp[] } // irregular ``` `t` 数组必须与 `v` 数组长度相同, 且 `t` 数组中的时间戳必须严格递增, 否则会被视为非法数据 等间隔数组与非等间隔数组统称为批量 (batch) 数据 ```typescript type BatchType = ArrayType | IrregularType ``` #### 字段时间戳 `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 信号丢失,无位置信息 | | 128–511 | 预留 | — | 预留给未来通用错误 | | 512–1023 | 应用 | — | 留给具体应用 / 产品线自定义 | | 1024–65535 | 厂商自定义 | — | 厂商或项目私有错误码区间 | #### 错误信息 `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` 及其衍生类型时, 用于定义内联枚举值, 详见 [枚举类型](#%E6%9E%9A%E4%B8%BE%E7%B1%BB%E5%9E%8B) ```typescript type InlineEnum = Record ``` #### 字段级用户自定义字段 `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 type EnumDefinition = { id: string; value: InlineEnum }; // Field 元数据 interface FieldMetadata { data_type: string; timestamp?: Timestamp; sample_interval?: SampleInterval; // valid only when data_type is array unit?: string; label?: string; confidence?: number | number[]; error_code?: number; error_msg?: string; enum?: InlineEnum; // inline enum when data_type === "enum:this" user?: Record; } // 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; } ```