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)输出参数的含义?

事件的"输出参数"是事件触发时携带的业务数据 ,用于描述事件的具体状态。比如本场景中,通过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:配置规则引擎(事件触发告警)
  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使用问题,欢迎在评论区交流~

相关推荐
Decay__5 小时前
05_MySQL 常用 SQL 大全 —— 诊断、管理、优化辅助 SQL 与三大系统库速查
运维·数据库·sql·mysql·架构
贞明电子7 小时前
工业三色报警灯怎么接入项目系统?开关量与RS485控制应用指南
运维·网络·微信公众平台·贞明电子
明天也要努力学习7 小时前
HTTP 协议的基本格式和 fiddler 的用法
网络协议·http·fiddler
HideF7 小时前
Linux
linux·服务器·网络
万联WANFLOW7 小时前
海外网络故障怎么排查?Cloudflare 三季度断网报告解读与 mtr 实战
运维·网络·故障分析
sbjdhjd7 小时前
网工进阶笔记:H3CSE 园区网全栈技术与协议汇总
网络·笔记·云计算·ssh·网络工程·vlan·aaa
小徐不徐说9 小时前
C++ 接口与插件设计:从工厂注册到 Qt 动态插件
数据库·c++·qt·系统架构·程序设计
Ruiery9 小时前
Linux 6.6内核内存管理深度解析(十三):slab/slub 分配器
linux·运维·服务器
论文复现现场10 小时前
WRF、FEA 能用 L40S 加速吗?求解器检查、断点恢复与长期运行
java·数据库·算法·wrf·有限元分析·gpu计算·断点续算
taktok_ai10 小时前
用 cc-switch 连接 Taktok
网络