12 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平台对应产品下创建事件,核心配置如下:

  1. 事件ID:switchchange(需和代码中EVENT_ID一致)
  2. 事件名称:继电器状态变更
  3. 输出参数:
    • 参数名:switch2
    • 数据类型:字符串
    • 可选值:on(开启)、off(关闭)

3.2 概念解析

(1)输出参数的含义?

事件的"输出参数"是事件触发时携带的业务数据 ,用于描述事件的具体状态。比如本场景中,通过switch2on/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 代码说明

  • 配置项:需替换为自己平台的productIddeviceId、MQTT地址等信息;
  • 报文优化:移除了多余的switch字段,仅保留物模型定义的switch2,严格匹配平台规范;
  • 异常处理:包含连接失败、上报异常、用户终止等场景的友好提示,便于调试。

代码都在本人的gitee上,代码无保留,热心群众
https://gitee.com/fujianxinxi/tearcher.git

5. 测试验证(社区版"曲线救国"方案)

5.1 测试背景

JetLinks 2.10社区版存在限制:事件上报后,无法直接在"设备事件"页面看到记录,需通过规则引擎+告警记录 验证,有点麻烦就是了,不过能用就行,先用再说

5.2 测试步骤

步骤1:配置规则引擎(事件触发告警)
  1. 进入平台「规则引擎」→ 新建规则;
  2. 触发条件:选择"设备事件"→ 关联对应产品+事件ID(switchchange);
  3. 执行动作:选择"发送告警"→ 配置告警标题(如"继电器状态变更");
  4. 启用规则。

正常运行日志如下:

图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. 结论

  1. JetLinks事件上报核心:主题固定、报文极简(仅保留物模型定义的data字段),无需额外元数据;
  2. 事件vs属性:状态变更用事件(追溯历史),实时状态用属性(查看当前),关联场景编辑复杂规则引擎;
  3. 社区版验证技巧:事件无直接展示时,通过"规则引擎+告警记录"曲线验证,这是2.10版本的可行方案;
  4. 避坑关键:代码配置项需和平台物模型/设备信息严格一致,报文字段不能多也不能少。

本文完整覆盖了JetLinks MQTT设备事件上报的全流程,解决了新手最易踩的"配置乱、验证难"问题。如果有其他JetLinks使用问题,欢迎在评论区交流~

相关推荐
乾元12 小时前
终端安全(EDR):用深度学习识别未知勒索软件
运维·人工智能·网络协议·安全·网络安全·自动化·安全架构
九.九13 小时前
CANN HCOMM 底层机制深度解析:集合通信算法实现、RoCE 网络协议栈优化与多级同步原语
网络·网络协议·算法
wbs_scy13 小时前
Linux 进阶指令实操指南:文件查看、时间管理、搜索压缩全场景覆盖(附高频案例)
linux·运维·服务器
安科瑞刘鸿鹏1713 小时前
高速路灯故障难定位?用 ASL600 实现精确单灯监测与维护预警
运维·网络·物联网·安全
Lethehong13 小时前
实测可用|一文搞定OpenClaw部署,免费kimi-k2.5+飞书远程,新手也能秒上手
linux·运维·服务器·玩转openclaw·云端创意实践
逍遥德13 小时前
Sring事务详解之02.如何使用编程式事务?
java·服务器·数据库·后端·sql·spring
承渊政道13 小时前
Linux系统学习【Linux基础开发工具】
linux·运维·笔记·学习·centos·编辑器
笨蛋不要掉眼泪13 小时前
Redis哨兵机制全解析:原理、配置与实战故障转移演示
java·数据库·redis·缓存·bootstrap
艾莉丝努力练剑13 小时前
【Linux:文件】基础IO
linux·运维·c语言·c++·人工智能·io·文件