14 ThingsBoard实战:从零搭建设备配置+设备,完成MQTT温湿度上行/目标温度下行测试(对比JetLinks)

之前研究了社区版的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的"产品")

设备配置是设备的模板,可统一管理同类型设备的参数、规则:

  1. 左侧菜单→Device Profiles→Add new device profile;
  2. 填写名称(如"温控器模板"),类型选择"Default",其余默认;
  3. 保存后,设备配置创建完成(无需定义物模型,后续直接接收JSON数据即可)。

2. 创建设备实体

  1. 左侧菜单→Devices→Add new device;
  2. 填写设备名称(如"温控器-001"),选择第一步创建的设备配置;
  3. 保存后,点击设备→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秒打印上报日志,平台端数据时间、数值完全匹配;

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

五、常见问题&注意事项

  1. ClientID重复 :多设备测试时需保证CLIENT_ID唯一,否则会导致MQTT连接被踢;
  2. QoS设置:建议上下行均使用QoS=1,避免消息丢失(ThingsBoard默认支持QoS 0/1);
  3. 认证失败 :检查设备Token是否正确(-u参数),ThingsBoard MQTT认证仅需Token作为用户名,密码留空;
  4. 主题错误 :上报遥测用v1/devices/me/telemetry,接收属性用v1/devices/me/attributes,不可混淆;
  5. 重连机制 :代码中auto_reconnect=True仅保证TCP重连,需配合reconnect_delay_set控制重连间隔。

六、总结

  1. ThingsBoard无需预定义物模型、无"产品"概念,相比JetLinks更轻量化,适合快速接入设备;
  2. MQTT主题是固定格式(v1/devices/me/xxx),设备Token作为认证用户名,无需复杂的签名机制;
  3. 代码层面,采用"定时器上报+主线程监听"的模式,兼顾稳定性和灵活性,可直接用于生产环境;
  4. 租户是ThingsBoard的核心权限层级,实战中避免用"客户"账号操作(权限不足)。

本文从基础配置到命令行、代码测试,完整覆盖了ThingsBoard的MQTT上下行通信,对比JetLinks的差异,帮你快速掌握ThingsBoard的核心使用方式。后续可进一步探索规则链、仪表盘等功能,实现温湿度可视化、目标温度自动控制等高级场景。

相关推荐
ssswywywht1 小时前
python练习
开发语言·python
PD我是你的真爱粉1 小时前
RabbitMQRPC与死信队列
后端·python·中间件
知识即是力量ol1 小时前
口语八股:MySQL 核心原理系列(二):事务与锁篇
java·数据库·mysql·事务·八股·原理·
喵手1 小时前
Python爬虫实战:医院科室排班智能采集系统 - 从零构建合规且高效的医疗信息爬虫(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·医院科室排版智能采集系统·采集医疗信息·采集医疗信息sqlite存储
dashizhi20152 小时前
如何禁止外部电脑接入内网、防止外来设备连接内部wifi?
网络·智能路由器
!沧海@一粟!2 小时前
Linux-配置虚拟IP实例
linux·网络
凉、介2 小时前
关于家用路由器的一些知识
网络·笔记·学习·智能路由器
济6172 小时前
I.MX6U 开发板网络环境搭建----(电脑 WiFi 上网,开发板和电脑直连)--虚拟机双网口实现-- Ubuntu20.04
linux·网络·电脑
执笔论英雄2 小时前
【大模型推理】 通过TokenWeave 学习chunked prefill 的缺点。
服务器·网络·学习