10 jetlinks-mqtt-直连设备-属性-读取-返回

1. 前言

JetLinks 作为轻量级物联网开源平台,MQTT 是其设备与平台通信的核心协议。本文聚焦物联网设备侧核心场景 ------ 响应平台属性读取指令,基于 Python 结合 paho-mqtt 库实现 MQTT 3.1.1 协议适配,完成读取指令监听、指令解析、属性值回复全流程,适配教学实操与基础设备开发需求。

2. mqtt主题及报文格式

  1. 监听读取属性主题

    • 主题格式

      text 复制代码
      /{productId:产品ID}/{deviceId:设备ID}/properties/read
      /2017982230047920128/2017982371131723776/properties/read
    • 报文格式

    json 复制代码
    {\"messageId\":\"消息ID,回复时需要一致.\",\"properties\":[\"属性ID\"]}
    json 复制代码
     {
      "headers": {
          "deviceName": "温度传感器",
          "productName": "温度传感器",
          "productId": "2017982230047920128",
          "_uid": "fJweyt5Yv0esfoVqkNct4Nm_A7JsRcSL",
          "traceparent": "00-3ed76ec4e72903d3f15b8ecc55f8c4eb-306e5f94318dd59f-01"
      },
      "messageId": "2018333322761248768",
      "deviceId": "2017982371131723776",
      "timestamp": 1770043137623,
      "properties": [
          "temperature"
      ],
      "messageType": "READ_PROPERTY",
      "replyType": "READ_PROPERTY_REPLY"
    }
  2. 上报主题及报文

    • 主题格式
    text 复制代码
    /{productId:产品ID}/{deviceId:设备ID}/properties/read/reply
    /2017982230047920128/2017982371131723776/read/reply
    • 报文格式
    json 复制代码
    {
      "messageId": "消息ID,与读取指令中的ID一致",
      "properties": {
          "属性ID": "属性值"
      }
    }

主要是物模型的设置,参考本人的这篇文章https://blog.csdn.net/weixin_43951955/article/details/157659141?spm=1001.2014.3001.5501

4. python实现

python 复制代码
import json
import logging
import time
from paho.mqtt import client as mqtt_client
from paho.mqtt.client import MQTTv311
import random  # 用于生成示例属性值(模拟真实设备)

# ===================== 配置项 ======================
MQTT_BROKER = "192.168.147.134"
MQTT_PORT = 1883
MQTT_USERNAME = "admin"
MQTT_PASSWORD = "admin"
CLIENT_ID = "2017982371131723776"

# 设备基础信息(用于构造监听/回复主题)
PRODUCT_ID = "2017982230047920128"
DEVICE_ID = "2017982371131723776"

# 监听/回复主题配置(重点)
READ_TOPIC = f"/{PRODUCT_ID}/{DEVICE_ID}/properties/read"  # 监听读取指令的主题
REPLY_TOPIC = f"/{PRODUCT_ID}/{DEVICE_ID}/properties/read/reply"  # 回复属性的主题

# 设备默认属性(模拟真实设备的属性值)
DEVICE_PROPERTIES = {
    "temperature": 25.0,  # 温度(示例)
    "humidity": 60.0,  # 湿度(可扩展)
}

# 日志配置
logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)


def connect_mqtt() -> mqtt_client.Client:
    """优化后的连接逻辑:增加重连间隔、断开原因打印"""
    reconnect_count = 0  # 重连计数(教学调试用)

    def on_connect(client, userdata, flags, rc, properties=None):
        nonlocal reconnect_count
        reconnect_count = 0  # 连接成功重置计数
        rc_msg = {
            0: "连接成功",
            1: "协议版本错误",
            2: "客户端ID非法",
            3: "服务器不可用",
            4: "用户名/密码错误",
            5: "未授权",
        }
        if rc == 0:
            logger.info(f"✅ MQTT 3.1.1 连接成功({MQTT_BROKER}:{MQTT_PORT})")
        else:
            logger.error(f"❌ 连接失败(rc={rc}):{rc_msg.get(rc, '未知错误')}")

    def on_disconnect(client, userdata, rc, properties=None):
        nonlocal reconnect_count
        reconnect_count += 1
        if rc == 0:
            logger.info("🔌 主动断开连接")
        else:
            # 打印断开原因(教学场景:帮助学生理解服务器反馈)
            disconnect_msg = {
                1: "协议错误",
                2: "客户端ID重复",
                3: "服务器不可用",
                4: "用户名密码错误",
                5: "未授权",
                128: "订阅越权",
            }
            logger.warning(
                f"⚠️  被动断开(rc={rc}):{disconnect_msg.get(rc, '服务器主动断开')},第{reconnect_count}次重连..."
            )
            # 延迟重连(避免频繁请求触发服务器限流)
            time.sleep(min(reconnect_count * 2, 10))  # 重连间隔:2s→4s→...→10s

    # 创建客户端(MQTT 3.1.1 + 自动重连)
    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 parse_read_command(payload: str) -> tuple:
    """解析读取属性指令,返回(messageId, 要读取的属性列表)"""
    try:
        payload_data = json.loads(payload)
        message_id = payload_data.get("messageId")
        properties = payload_data.get("properties", [])
        return message_id, properties
    except json.JSONDecodeError:
        logger.error(f"❌ 读取指令解析失败:{payload}")
        return None, []


def generate_reply_payload(message_id: str, properties: list) -> str:
    """构造回复报文,严格匹配指定格式"""
    # 构造属性值(模拟真实设备,给温度加随机波动)
    reply_properties = {}
    for prop in properties:
        if prop == "temperature":
            # 温度值随机波动±0.5℃,保留1位小数
            reply_properties[prop] = round(
                DEVICE_PROPERTIES[prop] + random.uniform(-0.5, 0.5), 1
            )
        elif prop in DEVICE_PROPERTIES:
            # 其他属性返回默认值(可扩展波动逻辑)
            reply_properties[prop] = DEVICE_PROPERTIES[prop]
        else:
            logger.warning(f"⚠️  未知属性:{prop},跳过回复")

    # 构造标准回复报文
    reply_payload = json.dumps(
        {"messageId": message_id, "properties": reply_properties}, ensure_ascii=False
    )
    return reply_payload


def on_message(client, userdata, msg):
    """监听读取指令,解析后回复属性值"""
    topic = msg.topic
    payload = msg.payload.decode("utf-8", errors="ignore")

    # 只处理读取属性的指令(过滤其他无关消息)
    if topic == READ_TOPIC:
        logger.info(f"\n📩 收到属性读取指令:")
        logger.info(f"   📌 指令Topic: {topic}")
        logger.info(
            f"   📝 指令报文: {json.dumps(json.loads(payload), ensure_ascii=False, indent=2)}"
        )

        # 1. 解析读取指令
        message_id, properties = parse_read_command(payload)
        if not message_id or not properties:
            logger.error("❌ 读取指令解析失败,跳过回复")
            return

        # 2. 构造回复报文
        reply_payload = generate_reply_payload(message_id, properties)

        # 3. 发布回复报文
        result = client.publish(REPLY_TOPIC, reply_payload, qos=0)
        status = result[0]
        if status == 0:
            logger.info(f"✅ 属性回复成功:")
            logger.info(f"   📌 回复Topic: {REPLY_TOPIC}")
            logger.info(
                f"   📝 回复报文: {json.dumps(json.loads(reply_payload), ensure_ascii=False, indent=2)}"
            )
        else:
            logger.error(f"❌ 属性回复失败,状态码:{status}")
    else:
        # 非读取指令的消息,仅打印(可选关闭)
        logger.info(f"\n📩 收到其他消息:")
        logger.info(f"   📌 Topic: {topic}")
        logger.info(f"   📝 消息体: {payload}")


def subscribe_read_topic(client: mqtt_client.Client):
    """仅订阅属性读取指令的Topic(精准监听,避免无关消息)"""
    client.subscribe(READ_TOPIC, qos=0)
    client.on_message = on_message
    logger.info(f"📌 已订阅属性读取指令Topic:{READ_TOPIC},等待指令...")


def run():
    """启动MQTT监听器,监听读取指令并回复"""
    logger.info("🚀 启动JetLinks MQTT属性读取响应服务(教学稳定版)")
    # 1. 建立MQTT连接
    client = connect_mqtt()
    # 2. 订阅读取指令Topic
    subscribe_read_topic(client)
    # 3. 持续监听MQTT消息(阻塞式)
    client.loop_forever()


if __name__ == "__main__":
    try:
        run()
    except KeyboardInterrupt:
        logger.info("\n🛑 退出监听")
    except Exception as e:
        logger.error(f"❌ 程序异常:{str(e)}", exc_info=True)

4.1. MQTT 3.1.1 协议适配

  • 重要性:JetLinks 平台对 MQTT 协议版本兼容性要求严格,5.0 版本易出现通信异常,3.1.1 是嵌入式设备/教学场景的最优选择;
  • 代码实现 :显式指定 protocol=MQTTv311,同时适配 paho-mqtt 2.0+ 的回调 API 版本,避免协议/API 不兼容导致连接失败;
  • 辅助保障:自动重连+分级重连间隔(2s→10s),解决网络波动导致的断开问题,保证长期在线。

4.2. 主题与报文格式

这是设备与 JetLinks 平台通信的"约定",错一处就会导致平台无法识别:

  • 监听主题精准 :仅订阅 /productId/deviceId/properties/read,只处理平台下发的属性读取指令,过滤无关消息;
  • 回复主题合规 :严格使用 /productId/deviceId/properties/read/reply(修正了示例中缺失的 properties 层级,匹配平台预期);
  • messageId 一致性 :回复报文的 messageId 必须和读取指令完全一致,这是平台关联"指令-回复"的唯一标识,代码中通过 parse_read_command 提取、generate_reply_payload 复用,确保匹配。

4.3. 指令解析与回复

  • 指令解析 :只提取 messageIdproperties 两个核心字段(忽略 headers/timestamp 等冗余信息),聚焦业务核心;
  • 属性值模拟:温度值加±0.5℃随机波动,模拟真实物理设备的属性变化(区别于固定值,更贴近实际场景);
  • 回复发布校验 :发布后检查状态码(result[0]),0 表示成功,非 0 打印错误,便于快速定位发布失败问题。

4.4. 异常容错

  • 指令报文 JSON 解析失败时,打印错误并跳过回复,避免程序崩溃;
  • 遇到未知属性(如平台下发不存在的 pressure),仅告警不中断流程,保证核心功能正常;
  • 断开连接时打印具体原因(如"订阅越权""客户端ID重复"),快速定位服务器/配置问题。

5. 测试

从图中可以看出,属性上传的数据能一一对应。

OK,大功告成!!!!!!!!!

6. 结论

本文完成了 JetLinks 平台 MQTT 3.1.1 协议下设备属性读取指令的响应实现,精准监听平台下发的读取指令,按指定格式解析并回复属性值,保障了设备与平台的正常通信。代码兼顾连接稳定性、格式合规性与异常容错能力,可直接用于教学演示,也可作为物联网设备端 MQTT 通信开发的基础参考。

相关推荐
燃于AC之乐2 小时前
【Linux系统编程】Shell解释器完全实现:从命令解析、环境变量管理到内建命令的全面解析
linux·操作系统·命令行工具·进程控制·shell编程
AZ996ZA2 小时前
自学linux第十九天:Cron定时任务完全指南:从入门到排错
linux·运维·服务器
HIT_Weston2 小时前
117、【Ubuntu】【Hugo】首页板块配置:Branch Bundle
linux·运维·ubuntu
fiveym2 小时前
服务器硬件管控接口学习笔记:IPMI与Redfish深度解析+实操调研
服务器
Sapphire~2 小时前
Linux-14 ubuntu 安装 vscode
linux·vscode·ubuntu
HalvmånEver2 小时前
Linux:线程创建与终止下(线程六)
linux·运维·算法
BHXDML2 小时前
计算机网络实验:(三)设置虚拟局域网(VLAN)
网络·网络协议·计算机网络
夏旭泽2 小时前
计算机网络-网络层
服务器·网络·计算机网络
_周游2 小时前
Java8 API文档搜索引擎_优化构建索引速度
java·服务器·搜索引擎·intellij-idea