1. 前言
JetLinks作为开源的IoT物联网平台,提供了完善的设备接入、物模型管理、功能调用等核心能力,其中MQTT协议是设备与平台直连的主流方式。本次测试以继电器设备为核心测试载体,继电器具备明确的"通/断"二元状态,且状态变更可直观验证,能精准覆盖"平台下发功能指令-设备解析指令-执行操作-反馈结果"全流程,是测试MQTT直连设备功能调用完整性、准确性的理想场景。
本次实践聚焦JetLinks平台与MQTT直连设备的功能调用交互逻辑,核心验证以下目标:
- 验证JetLinks平台物模型定义与设备端参数解析的匹配性;
- 打通"平台下发功能调用指令→设备接收解析→执行操作→回复执行结果"的闭环;
- 标准化MQTT主题格式、报文结构,为其他类型设备(如传感器、控制器)的功能调用提供可复用的参考范式。
2. 创建产品、设备

mqtt秘钥和客户端的秘钥生成,本人已经修改过对接认证方式,参考这篇文档https://blog.csdn.net/weixin_43951955/article/details/157621331?spm=1001.2014.3001.5501
重点是物模型的创建

此处务必要一一对应,功能的输入参数,就是输入到设备的控制参数,如本次继电器的"status"参数,需与物模型中定义的参数名、数据类型完全一致,否则设备端解析会出现参数缺失或类型不匹配问题。
3. mqtt主题及报文
3.1 事件上报主题
设备端除接收平台的功能调用指令外,也可通过事件上报主题主动推送设备状态、异常信息等数据,常见主题格式为/{productId}/{deviceId}/event/{eventId},本次聚焦功能调用,事件上报逻辑可参考JetLinks官方文档扩展实现。
3.2 功能主题
-
平台下发功能调用指令
mqtt主题
从主题的格式就可以看出来,该主题是监听所有的功能调用的,因此需要在回调函数做不同功能的区分
text/{productId:产品ID}/{deviceId:设备ID}/function/invoke报文内容
json{ "headers": { "deviceName": "继电器-测试", "productName": "继电器-类", "productId": "2018348352173023232", "_uid": "fJwi830cTZDlvTUXmWrCuy9QersSPNM9", "async": false, "traceparent": "00-bb2626ad382614b8eaf1c9d1ddbf798e-14d237caace59b9e-01" }, "messageId": "2018625963334967296", "deviceId": "2018564501706469376", "timestamp": 1770112908572, "functionId": "control_relay_switch", "inputs": [ { "name": "status", "value": true } ], "messageType": "INVOKE_FUNCTION", "replyType": "INVOKE_FUNCTION_REPLY" }报文中
messageId为平台生成的唯一消息标识,设备回复时需原样返回,用于平台关联指令与回复;functionId需与物模型中定义的功能标识符完全一致,是设备端区分不同功能的核心依据;inputs数组为功能调用的入参,参数名和值需严格匹配物模型定义。 -
设备响应平台下发的功能调用指令
mqtt主题
text/{productId:产品ID}/{deviceId:设备ID}/function/invoke/reply报文内容
json{ "messageId": "2018625963334967296", "output": true, "success": true }功能的响应报文和属性的读取响应报文结构基本一致 ,核心需包含
messageId(关联原指令)、success(执行结果)、output(功能执行后的输出值),平台通过success判断指令是否执行成功,output用于展示功能执行后的设备状态,格式需简洁且符合平台解析要求。
4. 代码
python
import json
import logging
import time
from paho.mqtt import client as mqtt_client
from paho.mqtt.client import MQTTv311
# ===================== 配置项 ======================
MQTT_BROKER = "192.168.120.176"
MQTT_PORT = 1883
MQTT_USERNAME = "admin"
MQTT_PASSWORD = "admin"
CLIENT_ID = "2019050361171279872"
# 设备基础信息
PRODUCT_ID = "2019049642691198976"
DEVICE_ID = "2019050361171279872"
# ========== 官方指定的功能调用Topic ==========
FUNCTION_INVOKE_TOPIC = f"/{PRODUCT_ID}/{DEVICE_ID}/function/invoke"
FUNCTION_REPLY_TOPIC = f"/{PRODUCT_ID}/{DEVICE_ID}/function/invoke/reply"
# ========== 继电器设备状态 ==========
DEVICE_STATE = {
"relay_switch_status": False, # false=断开,true=闭合
"relay_switch_count": 0, # 累计通断次数
"last_switch_time": None, # 最后切换时间
}
# 日志配置
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 parse_official_function_invoke(payload: str) -> tuple:
"""解析官方格式的功能调用指令"""
try:
payload_data = json.loads(payload)
# 提取官方定义的核心字段
message_id = payload_data.get("messageId") # 消息ID(回复时需原样返回)
function_id = payload_data.get("functionId") # 功能标识(control_relay_switch)
inputs = payload_data.get(
"inputs", []
) # 官方参数格式:数组[{"name":"xxx","value":"xxx"}]
# 将inputs数组转换为字典(便于使用)
params = {}
for item in inputs:
param_name = item.get("name")
param_value = item.get("value")
if param_name:
params[param_name] = param_value
return message_id, function_id, params
except json.JSONDecodeError:
logger.error(f"❌ 官方格式指令解析失败:{payload}")
return None, None, {}
def handle_control_relay_switch_official(params: dict) -> dict:
"""处理继电器开关控制(适配官方参数格式)"""
# 1. 校验参数(官方inputs中name为status)
if "status" not in params:
return {
"success": False,
"current_status": DEVICE_STATE["relay_switch_status"],
"msg": "缺少必填参数:status",
}
target_status = params["status"]
if not isinstance(target_status, bool):
return {
"success": False,
"current_status": DEVICE_STATE["relay_switch_status"],
"msg": "参数错误:status必须是布尔值",
}
# 2. 更新设备状态
DEVICE_STATE["relay_switch_status"] = target_status
DEVICE_STATE["relay_switch_count"] += 1
DEVICE_STATE["last_switch_time"] = time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime()
)
# 3. 返回执行结果(用于构造回复)
logger.info(
f"🔌 继电器状态更新:{'闭合' if target_status else '断开'} | 累计次数:{DEVICE_STATE['relay_switch_count']}"
)
return {
"success": True,
"current_status": target_status,
"msg": f"继电器已{'闭合' if target_status else '断开'}",
}
def generate_official_reply_payload(message_id: str, handle_result: dict) -> str:
"""构造匹配平台格式的回复报文"""
# 严格匹配平台返回格式:messageId + output(布尔值) + success(布尔值)
reply_payload = json.dumps(
{
"messageId": message_id, # 与下发指令的messageId完全一致
"output": handle_result.get(
"current_status", False
), # 直接返回布尔值(开关状态)
"success": handle_result.get("success", False), # 顶级success字段
},
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 == FUNCTION_INVOKE_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, function_id, params = parse_official_function_invoke(payload)
if not message_id or not function_id:
logger.error("❌ 指令解析失败,跳过回复")
return
# 2. 处理control_relay_switch功能
if function_id == "control_relay_switch":
handle_result = handle_control_relay_switch_official(params)
else:
handle_result = {
"success": False,
"current_status": DEVICE_STATE["relay_switch_status"],
"msg": f"不支持的功能:{function_id}",
}
# 3. 构造匹配平台格式的回复报文
reply_payload = generate_official_reply_payload(message_id, handle_result)
# 4. 发布回复
publish_result = client.publish(FUNCTION_REPLY_TOPIC, reply_payload, qos=0)
if publish_result[0] == 0:
logger.info(f"✅ 回复成功(匹配平台格式):")
logger.info(f" 📌 Topic: {FUNCTION_REPLY_TOPIC}")
logger.info(
f" 📝 报文: {json.dumps(json.loads(reply_payload), ensure_ascii=False, indent=2)}"
)
else:
logger.error(f"❌ 回复发布失败,状态码:{publish_result[0]}")
def subscribe_official_topic(client: mqtt_client.Client):
"""订阅官方功能调用Topic"""
client.subscribe(FUNCTION_INVOKE_TOPIC, qos=0)
client.on_message = on_message
logger.info(f"📌 已订阅官方功能调用Topic:{FUNCTION_INVOKE_TOPIC}")
logger.info(
f"📌 继电器初始状态:{'断开' if not DEVICE_STATE['relay_switch_status'] else '闭合'}"
)
logger.info(f"📌 等待官方格式控制指令...")
def run():
"""启动适配平台格式的继电器控制服务"""
logger.info("🚀 启动JetLinks 继电器MQTT服务(匹配平台返回格式)")
client = connect_mqtt()
subscribe_official_topic(client)
client.loop_forever()
if __name__ == "__main__":
try:
run()
except KeyboardInterrupt:
logger.info("\n🛑 退出继电器MQTT服务")
except Exception as e:
logger.error(f"❌ 程序异常:{str(e)}", exc_info=True)
代码中实现了MQTT自动重连、指令解析容错、参数校验等生产级特性,可直接复用至其他MQTT直连设备;核心逻辑分为"连接MQTT服务器→订阅功能调用主题→解析平台指令→执行设备操作→回复执行结果"五部分,各函数职责单一,便于扩展和维护。
5. 测试
测试正常结果应该是从平台下发功能测试指令,设备接受到指令之后先回复一个返回报文的主题,接着执行指令,以下为测试结果


结论:
- 通过调试界面分别下发
是和否的控制,分别对应开启和关闭 - 当设备收到报文之后,解析出正确的结果,提取控制指令,打印结果
- 使用回复主题的报文,返回一条消息给服务器
- 如果服务器正确接受到报文,就不会显示超时的弹窗
测试过程中需确保MQTT Broker地址、设备认证信息、物模型参数与代码配置一致;若平台出现"指令超时"提示,需排查设备是否订阅正确主题、回复报文是否包含正确的messageId、网络是否通畅等问题。
6. 结论
本次基于JetLinks平台完成了MQTT直连继电器设备的功能调用全流程验证,核心结论如下:
- 交互逻辑闭环验证通过:JetLinks平台下发的功能调用指令可被设备端正确解析,设备执行操作后返回的响应报文能被平台识别,实现了"平台指令下发-设备执行-结果反馈"的完整闭环,无超时、解析失败等异常。
- 物模型与代码适配关键 :物模型中定义的功能标识符(
functionId)、参数名(status)需与设备端代码严格一致,是功能调用成功的核心前提;参数类型(如布尔值)的匹配性直接影响指令执行结果。 - 代码具备通用复用性 :本次编写的MQTT客户端代码(含连接管理、指令解析、响应构造)可适配各类MQTT直连设备的功能调用场景,仅需修改
DEVICE_STATE、功能处理函数(如handle_control_relay_switch_official)即可快速适配传感器、执行器等不同类型设备。 - 异常处理保障稳定性:代码中加入的参数校验、JSON解析容错、MQTT自动重连等机制,有效提升了设备端的鲁棒性,可应对网络波动、指令格式异常等常见场景,符合物联网设备长期稳定运行的要求。
综上,JetLinks平台的MQTT功能调用机制具备良好的兼容性和易用性,本次实践的配置方式、代码逻辑可作为IoT设备接入JetLinks平台的标准化参考方案。