第8章 接入真实传感器并发布v0.2

v0.1已经证明设备事件能够到达服务器,并能呈现给用户。本章把按键输入替换为选定的真实传感器。项目将建立“采样—时间戳—数据窗口—上传—存储”链路,并发布v0.2。

真实传感器
→ 连续采样
→ 数据质量检查
→ 固定数据窗口
→ 受控上传
→ 服务器存储
→ 数据回放验证

本章关注传感数据怎样成为可追溯的工程构建物。传感器电气连接、总线驱动和采样原理在《感知与异构计算系统原型》中深入学习;本章负责接口、模式、版本、变更评审、故障证据和数据合规。

早期嵌入式程序常在主循环中直接读取传感器。设备改变后,采样、处理和上传代码往往需要同时修改。稳定的采样接口把硬件差异限制在适配器中,但需要额外维护时间、状态和错误信息。

完成本章后,应当能够:

8.1 从硬件课程接收传感器适配器

8.1.1 交付物必须形成可评审变更

硬件课程完成传感器连接与底层读取后,应把代码提交到任务分支。压缩包或聊天软件不能保留完整的版本关系。合并请求至少包含:

hardware/BOM.md
hardware/pin-map.md
hardware/sensor-configuration.md
firmware/include/sensor_source.h
firmware/src/<sensor_adapter>.cpp
firmware/tests/<sensor_adapter_test>.cpp
docs/verification/board-test-<日期>.md

其中:

没有数据手册来源、硬件版本和板级证据的驱动文件,不能直接作为主项目基线。

8.1.2 评审硬件交付接口

本课程评审以下内容:

[ ] 文件位于约定目录
[ ] 构建命令可从仓库根目录执行
[ ] 引脚与物料清单一致
[ ] 采样单位和轴顺序明确
[ ] 初始化失败可被上层识别
[ ] 读取函数不会隐式执行网络操作
[ ] 传感器配置能够进入运行日志
[ ] 没有凭据、个人路径和临时构建文件
[ ] 板级验证记录对应当前提交

寄存器设置是否正确由硬件课程负责解释,但合并请求必须能够追溯到数据手册位置和实际板卡测试。

8.2 定义稳定的采样接口

8.2.1 样本包含数值和身份

以三轴输入为例:

struct SensorSample {
  std::uint32_t timestamp_us;
  float x;
  float y;
  float z;
  std::uint32_t sequence;
  std::uint8_t status;
};

字段含义:

真实实现可以使用定点数以节省资源,但对外契约必须说明缩放比例。变量名中的_us用于明确时间单位。

8.2.2 初始化和读取失败要可见

接口示意:

enum class SensorResult {
  kOk,
  kNotReady,
  kBusError,
  kDataOverrun,
  kInvalidConfiguration
};

class SensorSource {
 public:
  virtual SensorResult begin() = 0;
  virtual SensorResult read(SensorSample& sample) = 0;
  virtual const SensorConfiguration& configuration() const = 0;
  virtual ~SensorSource() = default;
};

上层不能把所有失败都处理为“没有新数据”。总线错误、配置错误和采样过载对应不同修复措施,也会影响数据是否可用于训练和评价。

8.3 建立采样时间和数据窗口

8.3.1 采样率必须通过时间记录验证

若目标采样率为f,理想采样间隔为:

Δt = 1 / f

实际系统会有调度和总线抖动。验证时计算:

采样率目标来自数据和模型需求,真实结果来自设备记录。不要只检查配置常量是否写成目标值。

8.3.2 窗口是数据与模型的共同接口

连续数据按固定规则组成窗口:

样本流:s0 s1 s2 s3 s4 s5 s6 s7,后续样本按序延续
窗口1:  [s0 s1 s2 s3]
窗口2:        [s2 s3 s4 s5]
窗口3:              [s4 s5 s6 s7]

窗口长度决定一次判断使用多长时间信息,步长决定相邻窗口重叠程度。这两个参数由《人工智能数据科学》课程确定并交付,本仓库用配置和数据清单保存。

firmware/config/data_window.json示例:

{
  "schema_version": "1.0",
  "sample_rate_hz": 50,
  "window_samples": 100,
  "stride_samples": 50,
  "channels": ["x", "y", "z"],
  "units": "m/s^2"
}

示例数值必须替换为项目实际批准参数。修改任何一项都可能使已有数据和模型不兼容,必须建立议题并通知数据科学课程。

8.3.3 采集与发送使用不同任务

基本结构为:

传感器/FIFO
→ 采集任务
→ 有界环形缓冲区
→ 窗口任务
→ 有界上传队列
→ 通信任务

网络延迟不能阻塞固定节奏采样。两个队列都必须有容量上限和溢出策略:

AI可能生成无限增长的动态容器,在资源受限设备上会产生不可预测行为。评审时把缓冲容量与第6章RAM预算对照。

8.3.4 缓冲区隔离不同处理节奏

采集程序若直接等待网络发送,网络阻塞就会改变采样间隔。早期实现也常用轮询反复检查状态,造成处理器时间浪费。缓冲区把生产数据与消费数据分开,使二者按各自节奏运行。

缓冲区不能消除资源限制。容量过小会丢失数据,容量过大又会占用内存。采用有界队列后,系统还必须明确溢出、重试和删除规则。这些现象都可以通过计数器和时间记录验证。

8.4 定义传感数据模式

8.4.1 数据窗口要携带配置

contracts/examples/sensor-window.example.json:

{
  "schema_version": "1.0",
  "session_id": "session-example-001",
  "window_id": "window-example-0001",
  "device_id": "course-device-001",
  "hardware_revision": "rev-a",
  "firmware_version": "0.2.0-dev",
  "sensor": {
    "kind": "motion-3axis",
    "sample_rate_hz": 50,
    "range": "project-defined",
    "units": "m/s^2",
    "channels": ["x", "y", "z"]
  },
  "window": {
    "first_sequence": 1000,
    "sample_count": 2,
    "start_timestamp_us": 20000000,
    "samples": [
      [0.02, -0.01, 9.80],
      [0.03, -0.01, 9.79]
    ]
  },
  "quality": {
    "dropped_samples": 0,
    "sensor_errors": 0,
    "buffer_overruns": 0
  }
}

这是一条结构示例,不代表真实采样结果。真实上传必须使用项目批准的采样率、量程、单位和窗口长度。

8.4.2 模式检查结构,不判断信号是否正确

contracts/sensor-window.schema.json至少检查:

JSON模式可以发现字段缺失和类型错误。它不能判断传感器方向、量程和数据饱和情况。这些问题需要板级测试和数据分析。

执行:

python -m json.tool contracts/sensor-window.schema.json
python -m json.tool contracts/examples/sensor-window.example.json
python tools/project.py contract

8.5 设计采集会话和数据清单

8.5.1 一次采集要有明确身份

每次用于分析或训练的数据采集建立session_id,记录:

# 采集会话记录

- session_id:
- 采集目的:
- 操作人员编号:
- 日期与场地:
- 授权状态:
- 硬件修订:
- 传感器配置:
- 固件提交:
- 数据窗口配置:
- 开始/结束时间:
- 异常:
- 数据保存位置:
- 散列值:

“相同动作”在不同安装位置、量程或固件下可能产生不同数据。缺少这些元数据的数据难以用于可复现分析。

8.5.2 原始数据与公开仓库分离

数据文件可能体积较大,也可能包含个人或场景信息。项目仓库默认只保存:

完整原始数据放在课程批准的受控存储中。采集会话清单示例如下:

artifact_id,session_id,path_or_uri,sha256,bytes,access,license_or_consent,created_at
data-0001,session-example-001,<受控位置>,<实际散列>,<实际大小>,restricted,<记录编号>,<时间>

路径和统一资源标识符(Uniform Resource Identifier,URI)不得包含个人令牌。Git提交不能自动扩大数据访问权限。

8.6 接入服务器采集接口

传感数据和产品事件承担不同作用:

对象 用途 默认上传策略
EVENT-001 产品已经确认的事件 正常运行可上传
SENSOR-WINDOW-001 原始或预处理传感窗口 只在授权采集模式上传

不要为了调试长期上传全部原始数据。设备应有明确的采集模式:

NORMAL:只发送产品事件和必要运行状态
COLLECT:在授权会话中发送传感窗口
DIAGNOSTIC:在现场调试时输出受限诊断信息

模式变化进入日志,并由配置控制。服务器采集接口必须验证:

8.7 处理断网、缓存与重传

8.7.1 先定义失败状态

上传队列中的窗口至少区分:

READY
→ SENDING
→ ACKNOWLEDGED
→ RETRYABLE_FAILED
→ PERMANENT_FAILED
→ DROPPED

网络超时通常可以重试;模式错误和未授权请求不能通过重复发送解决。AI生成“所有失败都无限重试”的实现会占满队列和网络。

8.7.2 使用有界缓存

缓存设计要回答:

本章至少实现有界队列、重试次数和丢弃计数。是否使用非易失存储由产品需求、电源风险和硬件条件决定,并用技术决策记录保存。

8.7.3 退避避免故障放大

重试间隔应逐步增加并设置上限:

第1次失败 → 短等待
第2次失败 → 延长等待
后续失败 → 逐步延长等待
达到上限 → 保持受控频率或停止并报告

具体参数需要结合网络、功耗和数据时效确定。重试不得阻塞采集任务。

8.8 建立数据回放适配器

8.8.1 回放用于重复验证

真实传感器输入会随动作和环境变化。将一次合格采集保存为脱敏测试数据后,可通过ReplaySensorSource按时间顺序读入相同样本:

受控测试数据
→ ReplaySensorSource
→ 数据窗口
→ 数据模式编码
→ 上传与服务器

回放使用与真实传感器相同的SensorSource和窗口接口,因此可以重复验证上层代码。它不能证明传感器驱动、电气连接和现场信号质量正确,真实设备测试仍然必须保留。

8.8.2 固定回放数据的身份

回放记录应包含:

测试不能只写“使用sample.csv”。文件内容变化后,散列值能够表明测试基线已经改变。

8.9 约束AI处理硬件和数据任务

8.9.1 不让AI猜测硬件接口

实施请求:

任务:把已合并的SensorSource适配器接入数据窗口。

必须读取:
- hardware/sensor-configuration.md
- hardware/pin-map.md
- firmware/include/sensor_source.h
- firmware/config/data_window.json
- contracts/sensor-window.schema.json

只允许修改:
- firmware/include/data_window.h
- firmware/src/data_window.cpp
- firmware/src/main.cpp
- 对应测试

要求:
1. 使用文档中已经核实的单位、通道顺序和采样率;
2. 缓冲区容量必须是编译期可见的有界值;
3. 记录丢失样本、传感器错误和缓冲溢出;
4. 不修改驱动寄存器、物料清单、数据模式和模型;
5. 不把网络发送放进采样函数;
6. 不烧录、不提交、不推送。

若AI给出数据手册中没有的寄存器或引脚,拒绝该变化并回到硬件交付文件核对。

8.9.2 检查AI是否改变数据含义

差异审查重点:

[ ] x/y/z顺序没有改变
[ ] 单位换算只有一个权威位置
[ ] 时间单位没有混用
[ ] 样本序号连续规则明确
[ ] 窗口长度和步长来自版本化配置
[ ] 错误样本没有被静默当作正常值
[ ] 缓冲上限与RAM预算一致
[ ] 采集模式和上传权限没有被绕过

代码能够运行不代表数据含义正确。数据含义变化必须同步更新JSON模式、数据清单和已有模型的适用范围。

8.10 组织跨课程合并

建议合并顺序:

硬件适配器MR
→ 数据JSON模式合并请求
→ 窗口与缓冲MR
→ 服务器采集MR
→ 数据回放MR
→ v0.2集成MR

每个合并请求更新到最新main后再验证。硬件适配器和数据窗口同时修改main.cpp时,先合并较底层接口,另一分支再同步并根据已合并接口调整。

解决冲突时核对:

不要让AI只根据冲突标记选择一侧;它必须读取两个议题和当前规格,人工再决定最终语义。

8.11 验证真实数据链路

8.11.1 设备侧检查

在指定硬件上执行:

python tools/project.py doctor
python tools/project.py build-firmware
python tools/project.py flash
python tools/project.py monitor

完成:

编号 条件 检查
S1 设备静止 传感器持续输出,无总线错误
S2 沿一个方向缓慢改变姿态 对应轴变化,单位和方向符合文档
S3 连续运行指定时间 序号、时间戳、溢出计数可解释
S4 进入COLLECT模式 产生正确数量的数据窗口
S5 退出COLLECT模式 停止上传原始窗口

不要求学生在本章评价动作分类准确率。

8.11.2 端云与故障检查

编号 条件 检查
D1 正常网络 窗口被接受并写入采集会话清单
D2 重复window_id 服务器不重复保存
D3 暂停服务器 采样继续,上传进入可重试状态
D4 恢复服务器 在策略允许范围内重传
D5 队列达到上限 按已记录策略丢弃并增加计数
D6 无授权采集会话 原始数据被拒绝

记录实际窗口标识、设备提交、服务器提交、模式版本、硬件修订和错误计数。

8.11.3 回放检查

python tools/project.py replay-data --manifest <测试清单>

核对预期窗口数、散列和服务器记录。回放通过与实机通过分别记录,不能合并为一句“数据测试通过”。

8.12 发布v0.2

更新:

发布检查:

[ ] 硬件交付MR已评审
[ ] 真实传感器数据可读取
[ ] 时间戳、序号、单位和窗口可解释
[ ] 数据模式与服务器一致
[ ] 采集模式受控
[ ] 断网和队列上限行为已验证
[ ] 数据回放可重复
[ ] 原始数据未误入公开仓库
[ ] 硬件、固件、数据和数据模式版本可追溯

创建版本:

git switch main
git pull --ff-only
git tag -a v0.2.0 -m "v0.2.0 sensor data pipeline"
git push origin v0.2.0

发布说明如实列出尚未接入正式模型、当前采样场景和数据使用限制。

8.13 本章小结

本章把真实传感器接入了v0.1端云系统。系统形成可追溯的数据窗口、受控上传、有界缓存和数据回放链路。硬件课程提供经过板级验证的适配器。数据科学课程确认窗口和数据含义。本课程用Git契约、数据清单、合并请求和版本标签连接各项成果。

第9章将接收正式数据集和基线模型。重点是验证模型包是否完整,以及它是否符合v0.2数据契约。项目还要建立模型、数据、代码和评价结果之间的版本关系。

8.14 综合实践

  1. 为本组真实传感器定义样本、时间和错误状态。形成接口说明,并由另一组只依据说明编写一个替代数据源。
  2. 比较两种采样配置。参数应注明数据手册来源、实际测量条件,以及配置变化对数据和模型的影响。
  3. 根据内存和网络约束设计数据窗口、缓存上限和溢出策略。用受控实验说明为什么选择该方案。
  4. 选择一段经过授权的真实数据,建立回放适配器。比较回放与实机结果,并列出回放不能证明的硬件性质。
  5. 通过合并请求接收硬件适配器和数据模式,形成v0.2.0的物料清单、固件、数据清单和验证记录。