之前研究了社区版的jetlinks,发现多用户户情况下,设备产品都是可见的,对于商业应用,上课学生很多的情况下,感觉有点麻烦,因此研究下ThingsBoard。
作为物联网开发者,JetLinks和ThingsBoard都是常用的物联网平台,但两者的设计逻辑差异显著:JetLinks需先创建产品、定义物模型才能创建设备,而ThingsBoard无需预定义物模型(直接解析JSON报文),也无"产品"概念,取而代之的是"设备配置"(可理解为JetLinks的产品模板)。本文以"温控器(温湿度上报+目标温度下发)"场景为例,从零演示ThingsBoard MQTT命令行测试、Python代码测试全流程,对比JetLinks差异,帮你快速上手ThingsBoard。
一、前置准备
ThingsBoard核心概念(对比JetLinks)
| 概念 | ThingsBoard | JetLinks |
|---|---|---|
| 设备模板 | 设备配置(Device Profile) | 产品(Product) |
| 数据解析 | 自动解析JSON,无需预定义 | 需先定义物模型(属性/测点) |
| 权限层级 | 租户(Tenant)>客户(Customer)>设备 | 平台>产品>设备 |
| MQTT主题规范 | v1/devices/me/xxx |
/{productId}/{deviceId}/xxx |
注意:ThingsBoard中"租户"权限最高(可编辑规则链、创建设备配置),"客户"权限极有限(仅查看设备),实战中建议以租户身份操作。
二、ThingsBoard基础配置(创建设备配置+设备)
1. 创建设备配置(Device Profile,对应JetLinks的"产品")
设备配置是设备的模板,可统一管理同类型设备的参数、规则:
- 左侧菜单→Device Profiles→Add new device profile;
- 填写名称(如"温控器模板"),类型选择"Default",其余默认;
- 保存后,设备配置创建完成(无需定义物模型,后续直接接收JSON数据即可)。
2. 创建设备实体
- 左侧菜单→Devices→Add new device;
- 填写设备名称(如"温控器-001"),选择第一步创建的设备配置;
- 保存后,点击设备→Credentials→查看设备Token(本文中为
ydrcrveluuehyt1li4dy),该Token作为MQTT连接的用户名。
三、命令行测试(mosquitto)
通过mosquitto_sub/mosquitto_pub验证MQTT上下行通信,模拟设备侧行为。
1. 设备监听平台下发的目标温度(下行)
执行以下命令,监听平台下发的属性(对应JetLinks的"下行指令"):
bash
mosquitto_sub -d -q 1 -h 192.168.111.53 -p 2883 -t v1/devices/me/attributes -u "ydrcrveluuehyt1li4dy"
-d:调试模式,打印交互过程;-q 1:QoS=1,确保消息至少送达一次;-t:订阅主题(ThingsBoard设备接收属性的固定主题);-u:设备Token(认证用)。
测试效果
平台端发送目标温度报文(如{"temperature_dest":16}),设备端控制台输出如下:
text
root@zhongrui:~# mosquitto_sub -d -q 1 -h 192.168.111.53 -p 2883 -t v1/devices/me/attributes -u "ydrcrveluuehyt1li4dy"
Client mosq-7UsLQ9o4GyiOGwbg8k sending CONNECT
Client mosq-7UsLQ9o4GyiOGwbg8k received CONNACK (0)
Client mosq-7UsLQ9o4GyiOGwbg8k sending SUBSCRIBE (Mid: 1, Topic: v1/devices/me/attributes, QoS: 1, Options: 0x00)
Client mosq-7UsLQ9o4GyiOGwbg8k received SUBACK
Subscribed (mid: 1): 1
Client mosq-7UsLQ9o4GyiOGwbg8k received PUBLISH (d0, q1, r0, m1, 'v1/devices/me/attributes', ... (23 bytes))
Client mosq-7UsLQ9o4GyiOGwbg8k sending PUBACK (m1, rc0)
{"temperature_dest":16}
Client mosq-7UsLQ9o4GyiOGwbg8k received PUBLISH (d0, q1, r0, m2, 'v1/devices/me/attributes', ... (23 bytes))
Client mosq-7UsLQ9o4GyiOGwbg8k sending PUBACK (m2, rc0)
{"temperature_dest":17}
Client mosq-7UsLQ9o4GyiOGwbg8k sending PINGREQ
Client mosq-7UsLQ9o4GyiOGwbg8k received PINGRESP
Client mosq-7UsLQ9o4GyiOGwbg8k received PUBLISH (d0, q1, r0, m3, 'v1/devices/me/attributes', ... (23 bytes))
Client mosq-7UsLQ9o4GyiOGwbg8k sending PUBACK (m3, rc0)
{"temperature_dest":18}
Client mosq-7UsLQ9o4GyiOGwbg8k sending PINGREQ
Client mosq-7UsLQ9o4GyiOGwbg8k received PINGRESP


2. 设备上报温湿度(上行)
执行以下命令,向平台上报温湿度数据:
bash
mosquitto_pub -d -q 1 -h 192.168.111.53 -p 2883 -t v1/devices/me/telemetry -u "ydrcrveluuehyt1li4dy" -m '{"temperature":25.3,"humidity":64}'
-m:上报的JSON报文(无需预定义,ThingsBoard自动解析);-t:上报遥测数据的固定主题。
测试效果
text
root@zhongrui:~# mosquitto_pub -d -q 1 -h 192.168.111.53 -p 2883 -t v1/devices/me/telemetry -u "ydrcrveluuehyt1li4dy" -m '{"temperature":25.3,"humidity":64}'
Client mosq-N7ErubfQbBFdNMyhGv sending CONNECT
Client mosq-N7ErubfQbBFdNMyhGv received CONNACK (0)
Client mosq-N7ErubfQbBFdNMyhGv sending PUBLISH (d0, q1, r0, m1, 'v1/devices/me/telemetry', ... (34 bytes))
Client mosq-N7ErubfQbBFdNMyhGv received PUBACK (Mid: 1, RC:0)
Client mosq-N7ErubfQbBFdNMyhGv sending DISCONNECT
root@zhongrui:~#

四、Python代码测试
命令行仅用于验证,实战中需用代码实现"定时上报+实时监听",以下代码无需修改即可运行,具备自动重连、优雅退出、数据波动模拟等生产级特性。
python
import json
import logging
import time
import random
import threading
from paho.mqtt import client as mqtt_client
from paho.mqtt.client import MQTTv311
# ===================== 核心配置 ======================
MQTT_BROKER = "192.168.111.53"
MQTT_PORT = 2883
MQTT_USERNAME = "ydrcrveluuehyt1li4dy"
MQTT_PASSWORD = ""
CLIENT_ID = "python-mqtt-timer-client" # 固定ClientID,避免冲突
# 主题配置
TELEMETRY_PUB_TOPIC = "v1/devices/me/telemetry"
ATTRIBUTES_SUB_TOPIC = "v1/devices/me/attributes"
# ===== 模拟数据&定时器配置 =====
REPORT_INTERVAL = 5 # 上报间隔(秒)
TEMP_BASE = 25.3
HUMI_BASE = 64.0
TEMP_FLUCTUATION = 0.8
HUMI_FLUCTUATION = 1.5
TEMP_MIN, TEMP_MAX = 20.0, 35.0
HUMI_MIN, HUMI_MAX = 30.0, 85.0
# 全局定时器对象(用于控制上报启停)
report_timer = None
# 日志配置
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
def connect_mqtt() -> mqtt_client.Client:
"""建立MQTT连接"""
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})")
# 连接成功后:1. 订阅属性 2. 启动第一次上报定时器
subscribe_attributes(client)
start_report_timer(client)
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))
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_simulated_data():
"""生成模拟温湿度数据"""
temp = TEMP_BASE + random.uniform(-TEMP_FLUCTUATION, TEMP_FLUCTUATION)
temp = round(max(TEMP_MIN, min(temp, TEMP_MAX)), 1)
humi = HUMI_BASE + random.uniform(-HUMI_FLUCTUATION, HUMI_FLUCTUATION)
humi = round(max(HUMI_MIN, min(humi, HUMI_MAX)), 1)
return {"temperature": temp, "humidity": humi}
def publish_telemetry(client: mqtt_client.Client):
"""单次上报数据,上报完成后重新启动定时器"""
global report_timer
if client.is_connected():
# 生成模拟数据并上报
telemetry_data = generate_simulated_data()
payload = json.dumps(telemetry_data, ensure_ascii=False)
result = client.publish(TELEMETRY_PUB_TOPIC, payload, qos=1)
status = result[0]
if status == 0:
logger.info(f"✅ 上报数据成功:")
logger.info(f" 📌 上报Topic: {TELEMETRY_PUB_TOPIC}")
logger.info(f" 📝 上报数据: {json.dumps(telemetry_data, ensure_ascii=False, indent=2)}")
else:
logger.error(f"❌ 上报数据失败,状态码:{status}")
else:
logger.warning("⚠️ MQTT未连接,跳过本次上报")
# 重新启动定时器(实现循环上报)
start_report_timer(client)
def start_report_timer(client: mqtt_client.Client):
"""启动上报定时器(核心:递归实现循环)"""
global report_timer
# 取消原有定时器(避免重复)
if report_timer is not None and report_timer.is_alive():
report_timer.cancel()
# 创建新的定时器:REPORT_INTERVAL秒后执行publish_telemetry
report_timer = threading.Timer(REPORT_INTERVAL, publish_telemetry, args=[client])
report_timer.daemon = True # 守护线程:主线程退出时定时器自动结束
report_timer.start()
logger.debug(f"⏰ 已设置下次上报定时器({REPORT_INTERVAL}秒后)")
def on_attributes_message(client, userdata, msg):
"""订阅属性消息的回调"""
topic = msg.topic
payload = msg.payload.decode("utf-8", errors="ignore")
logger.info(f"\n📩 收到属性消息:")
logger.info(f" 📌 消息Topic: {topic}")
try:
payload_json = json.loads(payload)
logger.info(
f" 📝 消息内容: {json.dumps(payload_json, ensure_ascii=False, indent=2)}"
)
except json.JSONDecodeError:
logger.info(f" 📝 消息内容: {payload}")
def subscribe_attributes(client: mqtt_client.Client):
"""订阅属性主题"""
client.subscribe(ATTRIBUTES_SUB_TOPIC, qos=1) # QoS=1匹配你的命令
client.on_message = on_attributes_message
logger.info(f"📌 已订阅属性主题:{ATTRIBUTES_SUB_TOPIC},等待消息...")
def run():
"""主函数:启动MQTT连接+订阅,定时器负责上报"""
logger.info("🚀 启动MQTT服务(定时器版:5秒上报+实时监听)")
client = connect_mqtt()
try:
# 主线程阻塞运行MQTT客户端(监听订阅消息)
client.loop_forever()
except KeyboardInterrupt:
# 优雅退出:取消定时器+断开连接
global report_timer
if report_timer is not None:
report_timer.cancel()
logger.info("\n🛑 程序退出,断开MQTT连接...")
client.loop_stop()
client.disconnect()
if __name__ == "__main__":
try:
run()
except Exception as e:
logger.error(f"❌ 程序异常:{str(e)}", exc_info=True)
代码核心特性说明
- 协议适配:采用MQTT 3.1.1,兼容ThingsBoard的MQTT服务;
- 定时上报 :基于
threading.Timer实现5秒一次温湿度上报,替代while True+sleep,避免阻塞监听逻辑; - 数据模拟:温湿度在基准值附近波动,且限制上下限(避免异常值);
- 自动重连:网络波动后按"2s→4s→...→10s"渐进式重连,避免服务器限流;
- 实时监听 :主线程阻塞监听平台下发的目标温度,和
mosquitto_sub效果一致; - 优雅退出 :捕获
Ctrl+C,自动取消定时器、断开MQTT连接。
测试效果
-
上行(温湿度上报):控制台每5秒打印上报日志,平台端数据时间、数值完全匹配;

-
下行(目标温度下发):平台发送的指令实时打印,时间、数值完全匹配;

五、常见问题&注意事项
- ClientID重复 :多设备测试时需保证
CLIENT_ID唯一,否则会导致MQTT连接被踢; - QoS设置:建议上下行均使用QoS=1,避免消息丢失(ThingsBoard默认支持QoS 0/1);
- 认证失败 :检查设备Token是否正确(
-u参数),ThingsBoard MQTT认证仅需Token作为用户名,密码留空; - 主题错误 :上报遥测用
v1/devices/me/telemetry,接收属性用v1/devices/me/attributes,不可混淆; - 重连机制 :代码中
auto_reconnect=True仅保证TCP重连,需配合reconnect_delay_set控制重连间隔。
六、总结
- ThingsBoard无需预定义物模型、无"产品"概念,相比JetLinks更轻量化,适合快速接入设备;
- MQTT主题是固定格式(
v1/devices/me/xxx),设备Token作为认证用户名,无需复杂的签名机制; - 代码层面,采用"定时器上报+主线程监听"的模式,兼顾稳定性和灵活性,可直接用于生产环境;
- 租户是ThingsBoard的核心权限层级,实战中避免用"客户"账号操作(权限不足)。
本文从基础配置到命令行、代码测试,完整覆盖了ThingsBoard的MQTT上下行通信,对比JetLinks的差异,帮你快速掌握ThingsBoard的核心使用方式。后续可进一步探索规则链、仪表盘等功能,实现温湿度可视化、目标温度自动控制等高级场景。