JetLinks MQTT直连设备事件上报实战(继电器场景)
1. 前言
JetLinks作为国产开源物联网平台,MQTT协议直连设备是最常用的接入方式之一。但很多开发者在实操中会遇到两个核心问题:一是分不清"事件上报"和"属性上报"的使用场景,二是JetLinks 2.10社区版存在"事件上报后无法直接在页面查看"的坑,导致测试时无从验证,本人在验证的时候折腾了半天,最后通过事件触发告警(需要编写规则引擎),告警记录可查看来验证事件是否上报成功,太难了,坑爹,也有可能是本人某些地方设置有问题,但是使用当前文档的方法测试是完全没问题的。
本文以继电器开关状态事件上报为例,从协议规范、物模型配置、代码实现到测试验证,完整拆解JetLinks MQTT直连设备的事件上报全流程,重点解决"社区版怎么验证事件上报成功"的核心痛点,内容可直接落地。
突然想到可能和存储有关系,开启TimescaleDB-单列模式,果然看到事件日志了,不过还是不影响本人的证明过程,问题奀,可防可控,看第7小结,如何设置即可愉快使用了
2. 事件上报规范(主题+报文)
2.1 上报主题
JetLinks对MQTT直连设备的事件上报主题做了固定约定,格式如下:
/{productId}/{deviceId}/event/{eventId}
- 传输方向:上行(设备 → 平台)
- 字段说明:
productId:平台创建产品时生成的产品唯一标识deviceId:产品下创建设备时生成的设备唯一标识eventId:物模型中定义的事件ID(需和配置完全一致)
2.2 报文格式
事件上报报文无需冗余字段,仅需传输核心业务数据即可,平台会自动补全时间戳、messageID等元数据:
json
{
"data": {
"switch2": "on"
}
}
2.3 核心疑问解答
(1)设备需要获取上报返回值吗?
不需要。MQTT协议层面可通过QoS(本文用QoS0,追求可靠性可改QoS1)保证消息传输,应用层无需平台返回"上报成功/失败",设备只需按规范发送即可。
(2)data字段和物模型怎么对应?
data内的字段需和物模型中事件的输出参数完全匹配 (字段名、数据类型一致),对应关系看下图一目了然:

图1:物模型事件输出参数与报文data字段对应关系
注意:data 这几个字符串不要改哈,jetlink官方的文档有时候看起不不是特别直接。
3. 物模型事件创建
3.1 创建步骤
在JetLinks平台对应产品下创建事件,核心配置如下:
- 事件ID:
switchchange(需和代码中EVENT_ID一致) - 事件名称:继电器状态变更
- 输出参数:
- 参数名:
switch2 - 数据类型:字符串
- 可选值:
on(开启)、off(关闭)
- 参数名:
3.2 概念解析
(1)输出参数的含义?
事件的"输出参数"是事件触发时携带的业务数据 ,用于描述事件的具体状态。比如本场景中,通过switch2的on/off区分继电器"开启"和"关闭"两种状态。
(2)明明属性也能传状态,为什么用事件?
很多新手会混淆"属性"和"事件",其中最核心的差异是事件可触发规则引擎的复杂联动,其次是存储和语义的区别,完整对比如下:
| 维度 | 属性上报 | 事件上报 |
|---|---|---|
| 核心语义 | 设备当前的静态状态 | 设备发生的动态行为/动作 |
| 存储逻辑 | 覆盖式存储(仅保留最新值) | 日志式存储(保留所有记录) |
| 规则引擎支持 | 仅能触发简单的"属性变更"规则 | 可触发复杂联动(告警、设备控制、第三方接口调用等) |
| 适用场景 | 实时查看当前状态(如"现在开关是开的") | 1. 追溯状态变更(如"什么时候从开变关") 2. 触发业务联动(如开关关闭时推送告警、开启时控制其他设备) |
继电器状态变更场景中,事件的核心价值并非仅记录历史,而是能基于"开关开启/关闭"这个行为触发规则引擎的复杂操作:比如开关关闭时推送短信告警、开关开启时联动其他设备启动,这些都是属性上报无法实现的核心能力。
(3)为什么不拆分成"开关开启/关闭"两个事件?
- 冗余:两个事件本质都是"状态变更",仅参数值不同,拆分无意义;
- 维护成本高:代码和物模型需维护两个事件ID,增加复杂度;
- 规则适配难:后续配置告警/联动时,需写两条规则,不如单事件+参数区分高效。
4. 完整代码实现(可直接运行,问题奀)
python
import json
import logging
import random
import time
import uuid
from paho.mqtt import client as mqtt_client
from paho.mqtt.client import MQTTv311
# ===================== 配置项 ======================
MQTT_BROKER = "192.168.111.53"
MQTT_PORT = 1883
MQTT_USERNAME = "admin"
MQTT_PASSWORD = "admin"
CLIENT_ID = "2019722818928308224"
# 设备基础信息
PRODUCT_ID = "2019049642691198976"
DEVICE_ID = "2019722818928308224"
# 事件配置(核心:事件标识 switchchange ,上报间隔5秒)
EVENT_ID = "switchchange" # 事件标识
REPORT_INTERVAL = 5 # 上报间隔:5秒
EVENT_REPORT_TOPIC = f"/{PRODUCT_ID}/{DEVICE_ID}/event/{EVENT_ID}" # 事件上报Topic
# ========== 继电器设备状态 ==========
DEVICE_STATE = {
"switch_status": False, # false=关闭,true=开启(核心布尔状态)
}
# 日志配置
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
def connect_mqtt() -> mqtt_client.Client:
"""MQTT连接逻辑(保留核心,移除功能调用相关)"""
def on_connect(client, userdata, flags, rc, properties=None):
rc_msg = {
0: "连接成功",
1: "协议版本错误",
2: "客户端ID非法",
3: "服务器不可用",
4: "用户名/密码错误",
5: "未授权",
}
if rc == 0:
logger.info(f"✅ MQTT连接成功({MQTT_BROKER}:{MQTT_PORT})")
else:
logger.error(f"❌ 连接失败(rc={rc}):{rc_msg.get(rc, '未知错误')}")
def on_disconnect(client, userdata, rc, properties=None):
if rc != 0:
logger.warning(f"⚠️ MQTT被动断开,将自动重连(rc={rc})")
# 创建客户端
client = mqtt_client.Client(
client_id=CLIENT_ID,
callback_api_version=mqtt_client.CallbackAPIVersion.VERSION1,
protocol=MQTTv311,
)
client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.auto_reconnect = True
client.reconnect_delay_set(min_delay=2, max_delay=10)
# 连接服务器
try:
client.connect(MQTT_BROKER, MQTT_PORT, keepalive=60)
except Exception as e:
logger.error(f"❌ TCP连接失败:{str(e)}")
raise
return client
def generate_event_payload() -> str:
"""构造事件上报报文(严格匹配格式:timestamp+messageId+data(布尔类型))"""
# ========== 关键修改:根据switch_status动态设置switch2的值 ==========
switch2_value = "on" if DEVICE_STATE["switch_status"] else "off"
payload = json.dumps(
{
"data": {
"switch": DEVICE_STATE["switch_status"],
"switch2": switch2_value, # 动态赋值为on/off
},
},
ensure_ascii=False,
)
return payload
def update_switch_status():
"""每5秒切换开关状态(true↔false)"""
DEVICE_STATE["switch_status"] = not DEVICE_STATE["switch_status"]
status_text = "开启" if DEVICE_STATE["switch_status"] else "关闭"
logger.info(f"🔌 开关状态已更新:{status_text}({DEVICE_STATE['switch_status']})")
def report_event_periodically(client: mqtt_client.Client):
"""周期性上报事件(每5秒一次)"""
logger.info(f"📌 开始周期性上报事件(间隔{REPORT_INTERVAL}秒)")
logger.info(f"📌 事件标识:{EVENT_ID}")
logger.info(f"📌 事件上报Topic:{EVENT_REPORT_TOPIC}")
logger.info(
f"📌 初始开关状态:{'关闭' if not DEVICE_STATE['switch_status'] else '开启'}"
)
while True:
try:
# 1. 切换开关状态
update_switch_status()
# 2. 生成事件上报报文
payload = generate_event_payload()
# 3. 发布事件消息
publish_result = client.publish(EVENT_REPORT_TOPIC, payload, qos=0)
if publish_result[0] == 0:
logger.info(f"✅ 事件上报成功:")
logger.info(f" 📝 主题: {EVENT_REPORT_TOPIC}")
logger.info(
f" 📝 报文: {json.dumps(json.loads(payload), ensure_ascii=False, indent=2)}"
)
else:
logger.error(f"❌ 事件上报失败,状态码:{publish_result[0]}")
# 4. 等待5秒后继续
time.sleep(REPORT_INTERVAL)
except KeyboardInterrupt:
logger.info("\n🛑 停止事件上报服务")
break
except Exception as e:
logger.error(f"❌ 事件上报异常:{str(e)}", exc_info=True)
time.sleep(REPORT_INTERVAL) # 异常后仍等待,避免频繁报错
def run():
"""启动开关状态事件上报服务"""
logger.info("🚀 启动JetLinks 开关状态MQTT事件上报服务")
client = connect_mqtt()
client.loop_start() # 启动非阻塞MQTT循环
try:
report_event_periodically(client)
finally:
client.loop_stop()
client.disconnect()
logger.info("🔌 MQTT连接已断开")
if __name__ == "__main__":
try:
run()
except Exception as e:
logger.error(f"❌ 程序异常退出:{str(e)}", exc_info=True)
4.3 代码说明
- 配置项:需替换为自己平台的
productId、deviceId、MQTT地址等信息; - 报文优化:移除了多余的
switch字段,仅保留物模型定义的switch2,严格匹配平台规范; - 异常处理:包含连接失败、上报异常、用户终止等场景的友好提示,便于调试。
代码都在本人的gitee上,代码无保留,热心群众
https://gitee.com/fujianxinxi/tearcher.git

5. 测试验证(社区版"曲线救国"方案)
5.1 测试背景
JetLinks 2.10社区版存在限制:事件上报后,无法直接在"设备事件"页面看到记录,需通过规则引擎+告警记录 验证,有点麻烦就是了,不过能用就行,先用再说。
5.2 测试步骤
步骤1:配置规则引擎(事件触发告警)
- 进入平台「规则引擎」→ 新建规则;
- 触发条件:选择"设备事件"→ 关联对应产品+事件ID(
switchchange); - 执行动作:选择"发送告警"→ 配置告警标题(如"继电器状态变更");
- 启用规则。
正常运行日志如下:

图2:Python代码运行日志------ 显示周期性上报on/off报文
步骤3:查看告警记录验证
进入平台「告警中心」→「告警记录」,可看到继电器状态变更的告警记录:

图3:Web端告警记录------ 事件触发的告警信息
5.3 验证结论
告警记录的触发时间、switch2参数值和代码上报的内容完全一致,证明事件已成功上报到平台。务必注意:不是代码发送有问题,是社区版本的功能限制导致无法直接看事件记录!
6. 踩坑记录(社区版必看)
坑点1:社区版事件页面无记录(最坑)

图4:社区版事件页面无记录------ 2.10版本社区版事件页面为空
- 现象:事件上报后,「运行状态 tab」页面始终为空,上图所示;
- 原因:JetLinks 2.10社区版未开放事件日志的直接展示功能;
- 解决方案:通过规则引擎关联告警,用告警记录验证事件上报结果(曲线救国,没招只能这样)。
坑点2:规则引擎配置(事件关联告警)

图5:规则引擎配置------ 事件关联告警的规则配置
- 关键:触发条件必须精准选择"对应产品+事件ID",否则告警无法触发;
- 提示:规则配置完成后需"启用",否则不生效。
坑点3:最终验证(告警记录)

图6:告警记录页面------ 最终验证事件上报的告警记录
- 核心:告警记录的"触发时间""参数值"需和代码上报的一致,才算验证通过;
- 吐槽:这种验证方式确实反人类,但社区版只能这么玩
7. 尴尬


突然想到可能和存储有关系,开启TimescaleDB-单列模式,果然看到事件日志了.
8. 结论
- JetLinks事件上报核心:主题固定、报文极简(仅保留物模型定义的data字段),无需额外元数据;
- 事件vs属性:状态变更用事件(追溯历史),实时状态用属性(查看当前),关联场景编辑复杂规则引擎;
- 社区版验证技巧:事件无直接展示时,通过"规则引擎+告警记录"曲线验证,这是2.10版本的可行方案;
- 避坑关键:代码配置项需和平台物模型/设备信息严格一致,报文字段不能多也不能少。
本文完整覆盖了JetLinks MQTT设备事件上报的全流程,解决了新手最易踩的"配置乱、验证难"问题。如果有其他JetLinks使用问题,欢迎在评论区交流~