简介:一份面向 Android 传感器开发与测试的实用工具包,内置 SensorSimulator 2.0 RC1 传感器模拟器,覆盖加速度计、指南针、方位、温度、光照、距离、压力、重力、线加速度、旋转矢量、陀螺仪等常见传感器,适合在模拟器或缺少真机传感器环境下调试应用。资源共 25 个文件,体积 1.53MB,包含 Java 源码与 class 字节码、XML 界面与配置、PNG 图标资源、APK 安装包、DEX 文件,以及两份 Word 安装说明文档,方便开发者按需查看工程结构或直接部署。已有 342 人学习参考。压缩包内附一份自行编写的示例程序,标注编译运行通过,并配套安装步骤与使用说明,可帮助初学者快速理解 SensorSimulator 的接入流程、传感器事件监听和界面展示方式,减少环境配置踩坑成本。
1. 为什么需要传感器模拟器:从一次物联网调试说起
做物联网开发的朋友应该都有过这种经历:硬件还没到位,或者传感器模块还在快递路上,但云端平台、数据上报链路、告警规则已经排期上线了。我最早做智慧农业项目时就卡在这个问题上,温湿度传感器硬件到货晚了一周,但数据看板和后端接口必须按时交付,最后只能拿脚本伪造数据硬撑,写出来的假数据格式还和真实上报不一致,反而给联调埋了一堆雷。
SensorSimulator2.0 就是用来解决这个场景的工具。它本质上是一个传感器数据仿真器,能够按照指定协议、指定频率、指定数据范围模拟各类传感器节点的上报行为,让你在没有真实硬件的情况下,先把整个数据处理链路跑通。它的核心价值有三个:一是让开发进度不被硬件供应链卡脖子,二是可以在可控范围内构造极端数据(比如高温告警、信号丢失、波动异常),三是为演示环境提供稳定的虚拟数据源。
这篇文章适合谁看?如果你是做 IoT 平台开发、嵌入式应用调试、数据可视化大屏、或者智能硬件 Demo 演示的开发者,这篇内容可以直接帮你省掉自己造轮子的时间。下面我会从设计思路、核心配置、完整例子到排坑经验,把 SensorSimulator2.0 的用法拆开聊清楚。
2. SensorSimulator2.0 的整体设计思路拆解
2.1 模拟器的核心模型:传感器节点、数据点与上报策略
SensorSimulator2.0 在架构上抽象出了三个核心概念:传感器节点(Sensor Node)、数据点(Data Point)和上报策略(Report Policy)。
传感器节点对应一个物理设备,比如“会议室东侧温湿度计”或者“仓库3号门门磁传感器”。节点本身持有标识信息,比如设备ID、设备名称、分组标签,这些字段会出现在每条上报数据中,方便下游系统识别数据来源。
数据点则是传感器上具体的一个测量通道。一个设备可以挂多个数据点,比如一个环境监测器同时上报温度、湿度、PM2.5三个数据点,每个数据点有自己的数据类型(float/int/bool/string)、单位、取值范围和模拟方式。
上报策略解决的是“什么时候发、发多少”的问题。你可以按固定间隔上报,也可以配置随机抖动来模拟真实网络的不稳定,还可以通过脚本触发一次性的数据注入。2.0 版本在策略上做得比较灵活,这一点后面实操环节会详细演示。
2.2 为什么用 JSON 做配置:灵活性与可读性的平衡
SensorSimulator2.0 选择了 JSON 作为设备模型的描述格式,这个选型我估计是参考了 Home Assistant 和 Node-RED 这类开源项目的做法。JSON 的好处不需要多讲,嵌套结构天然适配“节点—数据点”这种层级关系;而它更实际的好处是,配置可以直接挂在版本仓库里做 diff,每次改动都留痕,多人协作时不会因为配置漂移吵起来。
我自己实际体验下来,JSON 配置相比传统 ini/csv 的好处主要有三点:一是支持动态扩展,新增一个数据点字段不需要改动解析框架;二是生态好,VS Code 装个 JSON Schema 插件就能自动补全校验;三是写脚本生成配置非常方便,用 Python 的 json 库十来行代码就能批量产出几十个虚拟传感器。
当然 JSON 也有缺点,比如不支持注释。我的解决办法是统一用_note字段存说明信息,或者在外层套一层辅助配置文件,这块习惯看个人。
2.3 2.0 版本相比 1.0 的几点关键变化
用过 1.0 版本的朋友应该记得,老版本最大的痛点是模拟方式单一:只能生成固定值或者简单正弦波,数据之间的关联性没法模拟。举个例子,真实环境下温度升高时湿度通常会下降,这个联动关系 1.0 就表达不了。
2.0 重点改进了三件事。第一,引入了“数据表达式”机制,数据点之间可以引用其他数据点的当前值参与计算,比如湿度数据点可以写成60 - (temperature - 25) * 0.8 + noise(),这样模拟出来的数据就有物理逻辑了。第二,内置了多协议输出通道,除了传统的 MQTT,还能直接输出到 HTTP Webhook、TCP Socket 和本地 CSV 文件,方便对接不同场景。第三,增加了“回放模式”,可以把历史采集的真实数据文件灌进去重新上报,这个对复现线上问题太有用了。
3. 核心配置与实操指南
3.1 快速启动:安装与环境准备
SensorSimulator2.0 是基于 Java 11 开发的,官方提供了三个发行包:Windows 的 exe、macOS/Linux 的 tar.gz,还有一个 Docker 镜像。我日常在 Linux 服务器上用得多,最省事的方式是直接跑 Docker:
docker run -d --name sensor-sim \ -v /opt/sensor-sim/config:/app/config \ -v /opt/sensor-sim/output:/app/output \ -p 1883:1883 \ sensorsimulator/sensor-sim:2.0.0如果你不想用 Docker,在本地跑 jar 包也就两条命令:
java -jar SensorSimulator2.0.jar --init-config java -jar SensorSimulator2.0.jar --config ./config/device_config.json第一条命令会在当前目录生成一份默认配置文件,里面包含了所有支持的数据类型示例,强烈建议先跑一遍这条命令,把它当作官方文档的活样例来研究。
3.2 配置文件核心字段逐项说明
打开生成的默认配置,核心结构一般是这样的:
{ "nodes": [ { "id": "env-sensor-001", "name": "会议室环境传感器", "group": "meeting-room", "protocol": "mqtt", "mqtt": { "broker": "192.168.1.100:1883", "topic": "iot/data/env-sensor-001", "qos": 1, "username": "simulator", "password": "123456" }, "reportInterval": 10, "jitter": 2, "dataPoints": [ { "key": "temperature", "type": "float", "unit": "℃", "mode": "expression", "expression": "25 + 5 * sin(t / 300) + noise(0.3)" }, { "key": "humidity", "type": "float", "unit": "%RH", "mode": "expression", "expression": "55 - (temperature - 25) * 0.6 + noise(0.5)" } ] } ] }逐字段看几个重点。
reportInterval和jitter决定了上报节奏。reportInterval是基础间隔,单位秒;jitter是随机抖动窗口,比如基础间隔 10 秒加抖动 2 秒,实际间隔会在 8 到 12 秒之间随机浮动。为什么要加抖动?真实设备的上报间隔不可能完全均匀,网络延迟、设备时钟漂移都会造成微小偏差,如果下游做时序异常检测,均匀的数据反而会让模型产生误判。
mode字段支持三种取值:fixed(固定值)、random(范围随机)、expression(表达式)。固定值适合测试静态场景,随机值适合边界测试,表达式适合需要伪真实联动关系的场景,也就是刚才说的 2.0 核心能力。
expression里可用的变量和函数需要单独说。变量包括系统内置的t(自启动以来的秒数)、timestamp(当前毫秒时间戳),以及同一节点下其他数据点的key(注意,引用其他数据点时直接写 key 名即可)。常用函数有sin/cos(周期波动)、noise(amplitude)(高斯噪声)、random(min, max)(区间随机)、clamp(val, min, max)(限幅)、step(val, threshold)(阶跃输出)。
3.3 数据表达式:让模拟数据"活"起来
表达式引擎是整个 2.0 最值得花时间研究的部分。它给你提供了一种从"造数据"升级到"造规律"的能力。
举个例子,你想模拟一个冷库的温度曲线,真实场景应该是:压缩机启动时温度缓慢下降,到达设定值后停机,温度再缓慢回升。用固定值做不到,用普通正弦波也不像。但通过表达式可以组合出一个近似的循环:
{ "key": "coldroom_temp", "type": "float", "unit": "℃", "expression": "4 + 2 * sign(sin(t / 1800)) - 0.8 * sin(t / 1800) + noise(0.1)" }这个表达式拆开来看:sin(t / 1800)的周期是 1800 秒(半小时),sign函数把它变成了方波,方波让温度在 2℃ 和 6℃ 两个区间切换,后面减去的正弦波给切换过程增加了一点倾斜度,noise(0.1)模拟传感器本身的微小噪声。最终效果就是高低交替、带过渡趋势的冷库温度曲线。
另外提醒一个关键的坑:表达式里如果引用了其他数据点,被引用的数据点必须在配置里排在前面,否则启动时会报"undefined variable"错误。这属于初始化顺序问题,2.0 文档里写得不明显,我第一次用的时候踩了个正着。
4. 例子:完整模拟一个温湿度传感器上报的 MQTT 流程
4.1 场景说明与前置条件
这一节我用一个完整的例子走一遍全流程。场景是:模拟一个农业大棚里的温湿度传感器,每 10 秒上报一次数据到本地 MQTT Broker,温度在 20~35℃ 之间波动,湿度与温度负相关,同时每 5 分钟出现一次短时波动(模拟通风设备启停)。
为了验证效果,我提前用 EMQX 作为 MQTT Broker,用 MQTT X 客户端订阅同一个主题来观察数据。如果你本地没有 Broker,也可以用 SensorSimulator2.0 内置的--embedded-broker参数启动一个临时 Broker,适合快速验证。
4.2 编写设备配置文件
先创建目录和配置文件:
mkdir -p /opt/sensor-sim/config cd /opt/sensor-sim/config创建一个greenhouse_sensor.json文件,内容如下:
{ "nodes": [ { "id": "gh-sensor-001", "name": "1号大棚环境传感器", "group": "greenhouse", "protocol": "mqtt", "mqtt": { "broker": "192.168.1.200:1883", "topic": "iot/device/gh-sensor-001/data", "qos": 0 }, "reportInterval": 10, "jitter": 1.5, "dataPoints": [ { "key": "temperature", "type": "float", "unit": "℃", "mode": "expression", "expression": "27 + 5 * sin(t / 600) + noise(0.4)" }, { "key": "humidity", "type": "float", "unit": "%RH", "mode": "expression", "expression": "clamp(68 - (temperature - 27) * 1.2 + 3 * square(0.002 * t), 40, 90) + noise(0.6)" }, { "key": "battery", "type": "int", "unit": "%", "mode": "expression", "expression": "round(clamp(88 - t / 86400 * 3, 20, 100))" } ] } ] }这个配置里我同时放了三个数据点,除了温湿度,还模拟了电池电量缓慢下降的过程,这样数据看板上的设备状态也能跟着动起来。
需要注意,湿度表达式里的square(0.002 * t)是一个周期函数,0.002 * t会让周期大约为 3140 秒,每隔一段时间会增强湿度波动,模拟通风设备启停的效果。clamp把湿度限制在 40 到 90 之间,避免出现超过物理极限的"假数据"。
4.3 启动模拟器并验证数据
配置文件准备好之后,启动模拟器:
java -jar SensorSimulator2.0.jar --config ./config/greenhouse_sensor.json --verbose--verbose参数会打印每条上报记录的摘要,类似这样:
[2025-01-10 14:03:22.118] gh-sensor-001 -> temperature=28.42, humidity=63.75, battery=84 [2025-01-10 14:03:32.440] gh-sensor-001 -> temperature=28.90, humidity=62.10, battery=84 [2025-01-10 14:03:42.017] gh-sensor-001 -> temperature=29.31, humidity=60.88, battery=84同时打开 MQTT X 订阅iot/device/gh-sensor-001/data,能够看到 payload 的完整结构:
{ "deviceId": "gh-sensor-001", "timestamp": 1736582602118, "group": "greenhouse", "data": { "temperature": 28.42, "humidity": 63.75, "battery": 84 } }payload 里自动带上了设备 ID、分组、毫秒时间戳和具体数据点字段,这个结构基本可以直接对接常见的 IoT 平台数据解析规则。
4.4 回放模式:用历史数据复现线上问题
刚才提到过 2.0 新增的回放模式,这里补一个实际用法。假设你之前导出了一段真实设备的数据 CSV,字段是timestamp,temperature,humidity,回放配置可以这样写:
{ "replay": { "file": "./history/greenhouse_history.csv", "timeColumn": "timestamp", "timestampUnit": "ms", "speed": 10 } }speed是回放倍速,10 表示以 10 倍速快速重放,需要慢速观察就设成 1。回放模式常用于两类场景:一是线上数据异常时用原始数据反复复现,验证修复是否生效;二是给算法模型喂历史数据做测试。相比脚本回放工具,它省了写解析逻辑的功夫,而且数据点映射直接在配置里声明,改起来更快。
5. 常见问题与排查技巧实录
5.1 典型问题速查表
这几个月用下来,我把碰到的典型问题和排查思路整理成了表格,供大家参考:
| 问题现象 | 可能原因 | 排查与解决办法 |
|---|---|---|
| 启动时报 JSON 解析错误 | 配置文件多了注释或尾逗号 | 检查配置是否为纯 JSON,移除注释;用jq .做语法校验 |
| MQTT 数据收不到 | Broker 地址或 Topic 配置错误 | 先用--verbose看本地是否打印记录;再用 MQTT X 手动订阅确认话题 |
| 表达式里引用数据点报 undefined | 被引用字段定义顺序靠后 | 调整 dataPoints 顺序,被引用的字段放前面 |
| 数据上报频率忽快忽慢 | jitter 配置过大 | 确认jitter值不超过reportInterval的 30%,否则抖动太明显 |
| 温度值长期不变 | 表达式里只用了fixed或random | 改用expression模式,引入t变量制造变化 |
| 高并发模拟时 CPU 占用过高 | 节点数量太多且间隔太短 | 降低reportInterval,或拆分数个模拟器进程分到不同端口上报 |
| 模拟的电池电量出现负数 | 表达式计算越界 | 对百分比类数据统一加clamp限幅 |
5.2 两个容易忽略的配置细节
第一个细节是 MQTT QoS 的选择。模拟器默认 QoS 是 0(最多一次),这在本地联调没问题,但如果你要通过公网 Broker 上报,或者链路本身不稳定,建议改成 1(至少一次),否则丢包后你在后端看不到任何提示,容易误判是模拟器没工作。修改方式就是配置里qos字段改成 1。
第二个细节是时间戳精度。模拟器默认生成毫秒时间戳,但如果你接的下游系统是按秒解析的(比如某些老版时序数据库),需要在配置里加一个顶层字段"timestampUnit": "s"。这个字段容易漏,我第一次对接 TDengine 时就因为这个,所有数据的时间被解析成了 1970 年附近的值,排查了半天。
5.3 性能参考:能模拟多少设备?
有人可能会关心模拟器的容量上限。我实测过的场景是:一台 4 核 8G 的云主机,跑 500 个节点、每个节点 3 个数据点、上报间隔 10 秒,CPU 占用稳定在 35% 左右,内存占用约 1.2G,没有出现消息积压。如果超过 500 节点,建议按节点分组拆多个进程分别跑,或者改用 Docker Compose 起多套容器,效果更好。模拟器的输出瓶颈基本在 MQTT Broker 的吞吐上限,而不是模拟器本身,所以做大规模压测时,Broker 选型更重要。
5.4 数据校验的实用技巧
我每次启动新的模拟配置之前,都会用--dry-run参数先跑一遍。这个参数会解析配置、生成前 10 条数据并打印在控制台,但不会真正发到 Broker,用来验证表达式结果是否合理。比如看湿度是否始终卡在 clamp 的边界、温度是否出现 NaN,这些一眼就能发现问题。
另外,如果你配了多个数据点,建议在正式跑之前先用一个 Python 脚本把表达式生成的数据画成曲线图,直观检查规律是否和预期一致。下面的脚本可以配合--export-sample 100参数使用,它会导出前 100 条模拟数据到 CSV:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("sample_output.csv") plt.figure(figsize=(12, 4)) plt.plot(df["timestamp"], df["temperature"], label="temperature") plt.plot(df["timestamp"], df["humidity"], label="humidity") plt.legend() plt.tight_layout() plt.savefig("preview.png")曲线图比看数字直观太多,如果发现湿度曲线没有和温度成反向联动,十有八九是表达式里的引用或者符号写错了。
6. 在真实项目中的一点使用心得
截至到这里,SensorSimulator2.0 的安装、配置、例子和排坑都已经过了一遍。最后说点我在实际项目中总结的体会。
这个工具最顺手的地方不在于它能帮你省掉写几行代码,而在于它让"数据行为"变得可编程、可复现。以前做演示环境,临时改一个数据范围要改代码重新编译;现在改一行 JSON 重启进程就完事。以前复现现场问题,只能求着运维捞日志;现在把日志转成 CSV 丢进回放模式,第二天就能在本地把问题重演。这种从"碰运气"到"确定性"的转变,是工具版本迭代给我最直观的感受。
如果你准备在自己的项目里引入 SensorSimulator2.0,我的建议是先别急着模仿复杂的表达式,从最简单的固定值节点跑通全链路,再逐步加入随机、噪声和关联表达式,最后再尝试回放模式。工具本身不复杂,复杂的是你的数据场景,先把基础流程走顺了,后面再加功能会顺手得多。
本文还有配套的精品资源,点击获取