> ## Documentation Index
> Fetch the complete documentation index at: https://dragonwingdocs-staging.qualcomm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 消息

Qualcomm® 传感中枢（QSH）框架使用基于协议缓冲区编码消息、数据流和传感器唯一标识符（SUID）的统一通信模型。该模型是应用处理器上的客户端应用程序与低功耗处理器上的传感器驱动程序和算法共同使用的通信约定。

<Note>
  `.proto` 文件定义了双方使用的消息模式。客户端应用程序使用它们来编码通过 `sendRequest()` 发送的请求。驱动程序和算法作者使用它们来定义其传感器接受和发出的内容。
</Note>

## 传感器之间的通信

在 QSH 框架内，每个传感器驱动程序和算法都被视为一个传感器。这意味着传感器间的通信（例如计步算法消费加速度计数据，或抬起唤醒算法消费陀螺仪数据）使用与应用处理器上客户端应用程序相同的请求和事件消息模型。

所有发往传感器、来自传感器以及传感器之间的通信均通过数据流上的请求和事件消息进行。消息负载使用 nanopb 生成器、编码器和解码器以协议缓冲区格式进行编码。QSH 框架管理每条消息的负载长度、消息 ID 和时间戳元数据。

下图展示了数据客户端与数据源之间通过数据流进行的通信：

<div className="flex flex-col items-center gap-2">
  <img src="https://mintcdn.com/qualcomm-staging/uWQLnBcS9sxlZhgB/Technologies/Sensors/media/qcs6490/sensors-comunication2.svg?fit=max&auto=format&n=uWQLnBcS9sxlZhgB&q=85&s=13feb671e8a0488316326fc1bfae07b4" alt="图：数据客户端与数据源之间的传感器通信" width="812" height="256" data-path="Technologies/Sensors/media/qcs6490/sensors-comunication2.svg" />

  <p className="text-sm text-gray-700">图：数据客户端与数据源之间的传感器通信</p>
</div>

如图所示：

* 客户端到传感器（请求）：客户端发送请求消息以启用、禁用或重新配置传感器。每个请求都针对特定的 SUID。客户端管理器验证请求并将其路由到目标传感器，目标传感器再将其传递给相应的传感器实例进行处理。

* 传感器到客户端（事件）：传感器实例以异步方式向其注册的客户端发送事件消息。客户端可以是应用处理器上的应用程序，也可以是运行在低功耗处理器上的其他传感器和算法。事件由新的传感器数据、配置更改、刷新（flush）完成或错误触发。

以下时序图展示了客户端应用程序、客户端管理器与传感器或驱动程序之间的完整消息生命周期：

```mermaid theme={null}
%%{init: {'theme': 'default'}}%%
sequenceDiagram
    participant App as Client Application
    participant CM as Client Manager
    participant S as Sensor / Driver
    App->>CM: sns_client_request_msg(SUID, msg_id, payload)
    CM-->>App: sns_client_resp_msg (ack)
    CM->>S: Client request
    S-->>CM: sensor event data
    CM-->>App: sns_client_event_msg(SUID, timestamp, payload)
```

以下各节描述了客户端应用程序、客户端管理器和传感器之间交换的消息类型。

## 客户端消息

客户端应用程序通过三种消息类型与 QSH 框架交互：

1. 通过 `sendRequest()` API 发送 `sns_client_request_msg` 请求消息。此消息的负载字段携带协议缓冲区编码的、特定于传感器的请求。

2. 接收 `sns_client_resp_msg` 响应消息。客户端管理器在收到请求后立即发送此确认。它确认请求已正确编码且目标 SUID 可达。此阶段只执行最少的处理。

3. 使用通过 `setCallback()` 注册的回调，接收一个或多个 `sns_client_event_msg` 事件消息。每条事件消息属于单个 SUID。如果客户端在多个 SUID 上有活动请求，则每个 SUID 的事件会以单独的消息传递。单条事件消息可以携带一个或多个传感器样本。

### 请求消息字段

发往客户端管理器的所有请求都使用 `sns_client_request_msg` 作为最外层协议缓冲区消息。此消息包含以下字段：

* SUID：请求的目标地址。客户端管理器会拒绝发送到无效或不可用 SUID 的任何请求。

* msg\_id：一个数字标识符，告知目标传感器如何解释编码后的负载。消息 ID 在单个传感器内是唯一的，但两个不同的传感器可能对不同的消息类型使用相同的 ID。例如：

  * `SNS_STD_SENSOR_MSGID_SNS_STD_SENSOR_CONFIG`：客户端发送给传感器的标准流式请求。
  * `SNS_STD_SENSOR_MSGID_SNS_STD_SENSOR_EVENT`：来自数据源传感器的标准传感器事件。

* 请求（`sns_std_request`）：发送给传感器的信息包。`sns_std_request::payload` 字段携带特定于传感器的配置，以对应于 `msg_id` 的协议缓冲区消息形式进行编码。有关详细信息，请参阅特定于传感器的 `.proto` 文件。

* 重采样器配置（可选）：控制客户端接收传感器数据的速率。使用以下选项之一：

  * `SNS_RESAMPLER_RATE_FIXED`：以精确的速率传送数据。例如，如果客户端请求 200 Hz 而传感器以 240 Hz 运行，则样本会被插值降至 200 Hz。
  * `SNS_RESAMPLER_RATE_MINIMUM`：以不低于请求的速率传送数据。例如，如果客户端请求 200 Hz 而传感器以 240 Hz 运行，则样本以 240 Hz 传送。

* 阈值配置（可选）：过滤事件，使客户端仅在满足条件时才接收数据。支持以下阈值类型：

  * `SNS_THRESHOLD_TYPE_RELATIVE_VALUE`：当当前值与上次报告值之间的差值超过配置的阈值时触发。
  * `SNS_THRESHOLD_TYPE_RELATIVE_PERCENT`：当当前值与上次报告值之间的差异超过上次报告值的一定百分比时触发。
  * `SNS_THRESHOLD_TYPE_ABSOLUTE`：当当前值越过固定阈值时触发。
  * `SNS_THRESHOLD_TYPE_ANGLE`：当当前四元数与上次报告的四元数之间的角度超过配置的阈值（以弧度为单位）时触发。仅适用于四元数传感器。

* 挂起配置（可选）：控制客户端处理器挂起时的系统行为。它包含以下子字段：

  * `client_proc_type`：标识客户端运行所在的处理器。来自该处理器上任何客户端的刷新请求会导致该处理器上的所有客户端都收到一次刷新。

  * `delivery_type`：指定处理器挂起期间是否传送事件：

    * `SNS_STD_DELIVERY_WAKEUP`：事件一旦可用即传送，无论处理器状态如何。如果请求的 `batch_period` 超出系统容量，则在缓冲区满时发送数据。使用此选项时，`flush_period` 实际上会被忽略。
    * `SNS_STD_DELIVERY_NO_WAKEUP`：处理器挂起期间保留事件，并在处理器恢复时传送所有待处理事件。

  * `nowakeup_msg_ids`：不得唤醒客户端处理器的消息 ID 列表。仅当其他具有唤醒能力的事件已在发送时，才会传送这些消息。

  有关完整字段参考，请参阅 `<workspace>/build-qcom-wayland/workspace/sources/sensinghub/sensing-hub/apis/proto/sns_client.proto`，其中 <code>\<workspace></code> 是您的工作目录。

### 批处理

在 `sns_client_request_msg::sns_std_request` 中，客户端可以使用以下批处理字段控制数据传送的方式和时间：

* `batching::batch_period`：两次数据传送之间的最大时间（以微秒为单位）。自上次传送以来生成的所有事件都会保留，直到此定时器触发。在并发场景中，事件可能会提前传送。刷新请求会立即覆盖此定时器。批处理默认禁用（`batch_period = 0`）。

* `batching::flush_period`：向客户端管理器和物理传感器提示应保留多少历史数据（以微秒为单位）。早于此值的数据可能会被丢弃。如果未设置，则默认为 `batch_period`，即仅保留一个批次。设置后，`flush_period` 必须大于或等于 `batch_period`。

* `batching::flush_only`：如果为 `True`，客户端管理器仅在客户端显式发送刷新请求时才传送事件。否则，批处理将持续到达到 `flush_period`，届时最旧的数据将被丢弃。

* `batching::max_batch`：如果为 `True`，指示传感器使用其最大硬件批处理容量。如果 `flush_only` 和 `max_batch` 均为 `True`，则 `flush_only` 优先。

### 事件消息字段

传送给客户端的所有事件都使用 `sns_client_event_msg` 作为最外层协议缓冲区消息。此消息包含在 `sns_client_report_ind_msg` 的负载字段中，后者是传输层的指示包装器。`sns_client_event_msg` 消息包含以下字段：

* SUID：标识生成事件的数据源。如果客户端在多个 SUID 上有活动请求，则每个 SUID 的事件会以单独的 `sns_client_event_msg` 消息传送。

* `events::msg_id`：标识事件类型，并使用与请求相同的数字 ID 空间 — 它告知客户端在解码负载时应使用哪个 proto 消息。

* `events::timestamp`：事件发生的时间，以 QTimer 时钟节拍为单位。对于传感器数据事件，这是物理样本在硬件中被采集的时间。对于框架生成的事件（配置更新、错误、刷新完成），这是事件被创建的时间。

* `events::payload`：编码后的事件数据。使用对应于 `msg_id` 的特定于传感器的 proto 缓冲区解码此字段。

事件会在以下情况下生成：

* 以配置的采样率或批处理周期提供新的传感器数据。
* 传感器配置更改已处理（`SNS_STD_SENSOR_MSGID_SNS_STD_SENSOR_PHYSICAL_CONFIG_EVENT`）。
* 刷新请求完成（`SNS_STD_MSGID_SNS_STD_FLUSH_EVENT`）。
* 传感器、传感器实例或框架中发生错误（`SNS_STD_MSGID_SNS_STD_ERROR_EVENT`）。

### 消息负载

请求或事件中特定于传感器的内容分别包含在 `sns_std_request` 和 `sns_client_event` 的 `payload` 字段中。这些字段包含一个协议缓冲区编码的消息，其结构由 `msg_id` 定义。如果消息类型不携带额外数据，该字段也可能为空。

客户端使用与之通信的传感器的 `.proto` 文件。每种传感器类型都有对应的 `.proto` 文件。例如，`sns_accel.proto` 描述了如何启用加速度计数据流。每个传感器都会将其 `.proto` 文件列表作为其属性的一部分发布。

* 数据类型：每个传感器都会公布一个数据类型属性。数据类型映射到定义特定于传感器 API 的一组唯一 `.proto` 文件。同一类型的所有传感器必须支持一组最低限度的请求和事件消息，并且可以定义特定于其实现的其他可选消息。

* 标准化消息：以下 Qualcomm 定义的消息可以发送给任何传感器。它们在 `sns_std.proto` 文件中定义。

  * `SNS_STD_MSGID_SNS_STD_ATTR_REQ`：查询传感器已发布的属性。传感器以包含所有属性的 `SNS_STD_MSGID_SNS_STD_ATTR_EVENT` 进行响应。

    * `sns_std_attr_req::register_updates`：如果为 `True`，则每当传感器的属性发生变化时，客户端都会收到 `sns_std_attr_event` 通知。
    * `sns_std_attr_event::attributes`：传感器发布的所有属性的列表，作为对 `sns_std_attr_req` 的响应或在属性发生变化时返回。

  * `SNS_STD_MSGID_SNS_STD_FLUSH_REQ`：强制将此传感器的所有批处理数据立即传送给客户端。这会刷新硬件缓冲区（例如加速度计上的物理 FIFO）以及客户端管理器保留的任何数据。对于游戏旋转矢量（GRV）等算法传感器，这还会刷新所有底层物理传感器（加速度计、陀螺仪）的 FIFO。

  * `SNS_CLIENT_MSGID_SNS_CLIENT_DISABLE_REQ`：取消此传感器的活动请求。例如，如果客户端之前发送了 `SNS_STD_SENSOR_MSGID_SNS_STD_SENSOR_CONFIG` 以启用加速度计流式传输，则发送 `DISABLE_REQ` 会停止该客户端的数据流。

  * `SNS_STD_MSGID_SNS_STD_FLUSH_EVENT`：由传感器针对刷新请求发送。表示与该刷新对应的所有数据均已传送，且后续不会再有刷新事件。

  * `SNS_STD_MSGID_SNS_STD_ERROR_EVENT`：由传感器、传感器实例或框架生成的错误事件。

  有关完整消息参考，请参阅 `<workspace>/build-qcom-wayland/workspace/sources/sensinghub/sensing-hub/apis/proto/sns_client.proto`。

* 标准化传感器消息：除上述消息外，以下 Qualcomm 推荐的消息适用于标准传感器。这些消息是可选的；您也可以定义自己的请求和事件消息。它们在 `sns_std_sensor.proto` 中定义：

  * `SNS_STD_SENSOR_MSGID_SNS_STD_SENSOR_CONFIG`：为物理传感器（加速度计、陀螺仪、磁力计）和某些算法传感器（旋转矢量、重力、线性加速度）启用流式传输。
  * `SNS_STD_SENSOR_MSGID_SNS_STD_ON_CHANGE_CONFIG`：为按变化触发的传感器（例如接近传感器、环境光和步伐检测）启用流式传输。
  * `SNS_STD_SENSOR_MSGID_SNS_STD_SENSOR_PHYSICAL_CONFIG_EVENT`：由物理传感器在处理客户端请求后发送。表示实际的运行参数，例如传感器产生的采样率。
  * `SNS_STD_SENSOR_MSGID_SNS_STD_SENSOR_EVENT`：传感器针对活动流式请求产生的数据样本。

* SUID 查找：在发送任何请求之前，客户端必须知道目标传感器的 SUID。SUID 查找传感器提供此功能。客户端发送一个指定数据类型字符串（例如 `accel`）的 `sns_suid_req` 消息，并接收所有匹配的 SUID。空数据类型字符串会返回系统上的所有 SUID。

  SUID 查找传感器具有一个固定的、众所周知的 SUID，发布在 `sns_suid.proto` 中。

  如果客户端希望在给定类型的新传感器可用时收到通知，可将 `register_updates` 字段设置为 `True`。收到 SUID 后，客户端可以向每个 SUID 发送 `sns_std_attr_req` 来检查属性并选择最合适的传感器。

  `sns_suid_req` 消息包含以下字段。有关更多信息，请参阅 `sns_suid.proto` 文件和示例代码。

**表：SUID 请求**

<table>
  <thead>
    <tr><th>字段</th><th>必需或可选</th><th>数据类型</th><th>描述</th></tr>
  </thead>

  <tbody>
    <tr><td><code>data\_type</code></td><td>必需</td><td>字符串</td><td>要查询的传感器的数据类型，例如 <code>accel</code> 或 <code>gyro</code>。</td></tr>
    <tr><td><code>register\_updates</code></td><td>可选</td><td>布尔值</td><td>如果为 <code>True</code>，则每当公布此数据类型的传感器变为可用或不可用时，客户端都会收到新的 SUID 事件。</td></tr>
    <tr><td><code>default\_only</code></td><td>可选</td><td>布尔值</td><td><ul><li>如果为 <code>True</code> 且已为该数据类型显式配置了默认传感器，则仅返回该传感器的 SUID。如果未配置默认值，则返回第一个匹配传感器的 SUID。</li><li>如果为 <code>False</code>，则在所有匹配该数据类型的 SUID 变为可用时全部返回。</li></ul></td></tr>
  </tbody>
</table>

### 传感器属性

每个传感器都会发布一个由数字 ID 标识的属性列表。属性描述传感器的能力、运行参数以及它接受的值范围。客户端使用 `SNS_STD_MSGID_SNS_STD_ATTR_REQ` 查询属性，并在 `SNS_STD_MSGID_SNS_STD_ATTR_EVENT` 响应中接收属性。

下表列出了标准传感器属性：

**表：传感器属性**

<table>
  <thead>
    <tr><th>属性 ID</th><th>属性名称</th><th>是否必需？</th><th>数据类型</th><th>描述</th></tr>
  </thead>

  <tbody>
    <tr><td>0</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_NAME</code></td><td>是</td><td>字符串</td><td>人类可读的传感器名称。</td></tr>
    <tr><td>1</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_VENDOR</code></td><td>是</td><td>字符串</td><td>人类可读的供应商名称。</td></tr>
    <tr><td>2</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_TYPE</code></td><td>是</td><td>字符串</td><td>此传感器使用的数据类型，在传感器 proto 文件中定义。</td></tr>
    <tr><td>3</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_AVAILABLE</code></td><td>是</td><td>布尔值</td><td>指示此传感器当前是否对客户端可用。</td></tr>
    <tr><td>4</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_VERSION</code></td><td>是</td><td>整数</td><td>表示传感器驱动程序版本的 64 位整数，格式为 <code>major\[31:16].minor\[15:8].revision\[7:0]</code>。例如：major <code>0x0002</code>、minor <code>0x00</code>、revision <code>0x36</code> 得到 <code>DRIVER\_VERSION 0x00020036</code>。</td></tr>
    <tr><td>5</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_API</code></td><td>是</td><td>字符串</td><td>此传感器使用的 <code>.proto</code> 文件名列表。其他 proto 依赖项在这些文件内作为导入指定。主要用于测试自动化。</td></tr>
    <tr><td>6</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_RATES</code></td><td>否</td><td>浮点数</td><td>传感器支持的采样率列表，单位为 Hz。</td></tr>
    <tr><td>7</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_RESOLUTIONS</code></td><td>否</td><td>浮点数</td><td>传感器支持的采样分辨率列表。</td></tr>
    <tr><td>8</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_FIFO\_SIZE</code></td><td>否</td><td>整数</td><td>支持的 FIFO 深度，以样本数为单位。</td></tr>
    <tr><td>9</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_ACTIVE\_CURRENT</code></td><td>否</td><td>整数</td><td>活动电流消耗值数组，单位为 µA。</td></tr>
    <tr><td>10</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_SLEEP\_CURRENT</code></td><td>否</td><td>整数</td><td>非活动（睡眠）电流消耗，单位为 µA。</td></tr>
    <tr><td>11</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_RANGES</code></td><td>否</td><td>浮点数</td><td>支持的工作测量范围。</td></tr>
    <tr><td>12</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_OP\_MODES</code></td><td>否</td><td>字符串</td><td>传感器支持的工作模式，例如 <code>\[LPM, HIGH\_PERF, NORMAL, OFF]</code>。</td></tr>
    <tr><td>13</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_DRI</code></td><td>否</td><td>布尔值</td><td>支持的中断类型：<code>True</code> = 数据就绪中断（DRI）；<code>False</code> = 带内中断（IBI）。</td></tr>
    <tr><td>14</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_STREAM\_SYNC</code></td><td>否</td><td>布尔值</td><td>指示传感器是否支持同步流式传输。</td></tr>
    <tr><td>15</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_EVENT\_SIZE</code></td><td>否</td><td>整数</td><td>此传感器产生的协议缓冲区编码数据事件的大小（以字节为单位）。HAL 使用该值确定最大批处理容量。</td></tr>
    <tr><td>16</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_STREAM\_TYPE</code></td><td>是</td><td>整数</td><td>流式类型：<code>0</code> = 连续周期性，<code>1</code> = 按变化触发，<code>2</code> = 单次。</td></tr>
    <tr><td>17</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_DYNAMIC</code></td><td>否</td><td>布尔值</td><td>如果传感器可以在运行时连接或断开，则为 <code>True</code>。</td></tr>
    <tr><td>18</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_HW\_ID</code></td><td>否</td><td>整数</td><td>用于区分多个同类型传感器的硬件标识符。</td></tr>
    <tr><td>19</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_RIGID\_BODY</code></td><td>否</td><td>整数</td><td>传感器的物理位置：<code>0</code> = 显示屏侧，<code>1</code> = 键盘侧，<code>2</code> = 外部设备。</td></tr>
    <tr><td>21</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_PHYSICAL\_SENSOR</code></td><td>否</td><td>布尔值</td><td>如果这是物理传感器，则为 <code>True</code>；如果是虚拟（算法）传感器，则为 <code>False</code>。</td></tr>
    <tr><td>22</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_PHYSICAL\_SENSOR\_TESTS</code></td><td>否</td><td>整数</td><td>支持的物理传感器自检列表，使用 <code>sns\_physical\_sensor\_test\_type</code> 中的枚举值。</td></tr>
    <tr><td>23</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_SELECTED\_RESOLUTION</code></td><td>否</td><td>浮点数</td><td>每个已配置动态范围值的活动测量分辨率。</td></tr>
    <tr><td>24</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_SELECTED\_RANGE</code></td><td>否</td><td>Float\[2]</td><td>活动动态范围。有关默认值，请参阅供应商提供的传感器硬件需求规格。</td></tr>
    <tr><td>25</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_ADDITIONAL\_LOW\_LATENCY\_RATES</code></td><td>否</td><td>浮点数</td><td>可供低延迟专用客户端使用的附加采样率，单位为 Hz。这些扩展了 <code>SNS\_STD\_SENSOR\_ATTRID\_RATES</code> 中的速率，如果被非专用客户端使用，可能会影响系统性能。</td></tr>
    <tr><td>26</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_PASSIVE\_REQUEST</code></td><td>否</td><td>布尔值</td><td>如果传感器支持被动请求，则为 <code>True</code>。如果为 <code>False</code>，则所有请求均视为主动请求。</td></tr>
    <tr><td>29</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_TRANSPORT\_MTU\_SIZE</code></td><td>否</td><td>整数</td><td>传输传感器的最大传输单元（MTU）大小，以字节为单位。</td></tr>
    <tr><td>30</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_HLOS\_INCOMPATIBLE</code></td><td>否</td><td>布尔值</td><td>如果传感器与其数据类型的 HLOS 规范不兼容，则为 <code>True</code>。</td></tr>
    <tr><td>31</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_SERIAL\_NUM</code></td><td>否</td><td>字符串</td><td>传感器序列号。</td></tr>
    <tr><td>32</td><td><code>SNS\_STD\_SENSOR\_ATTRID\_TECH\_USED</code></td><td>否</td><td>整数数组</td><td>此传感器使用的技术。有关值列表，请参阅 <code>sns\_std\_type.proto</code> 中的 <code>sns\_tech</code>。</td></tr>
  </tbody>
</table>

<Note>
  属性 ID 20 为保留项，目前未分配。
</Note>

有关更多信息，请参阅 `sns_std_sensor.proto` 文件和示例代码。proto 文件位于设备上的 `/etc/sensors/proto/` 目录中。

## 协议缓冲区

QSH 客户端请求和事件消息是包含协议缓冲区编码负载的不透明内存缓冲区。客户端可以使用 nanopb 库支持的任何语言生成这些消息，将其编码为字节流，并将该字节流复制到 `sns_client_request_msg` 的负载字段中。同样，客户端从 `sns_client_report_ind_msg` 中提取负载，并使用相应的 `.proto` 定义对其进行解码。

有关更多信息，请参阅 [Protocol Buffers](https://developers.google.com/protocol-buffers/) 和 [nanopb](https://github.com/nanopb/nanopb)。
