一、背景介绍

1、什么是ThingsBoard?

ThingsBoard是一个开源的物联网平台,它专门用于设备管理数据收集处理可视化。想象一下,你有很多物联网设备(比如智能家居设备、车辆传感器、工业机器等),这些设备会产生大量数据,ThingsBoard就是那个帮你统一管理这些设备,并让你能够查看和分析数据的"大脑"。

2、为什么要使用ThingsBoard?

  • 开源免费:不需要支付昂贵的许可费用
  • 易于扩展:支持从几个设备到数百万个设备的规模
  • 功能完整:提供设备管理、数据监控、告警、可视化等全套功能
  • 支持多种协议:MQTT、CoAP、HTTP等,适应不同设备需求

3、本文目的

本文将搭建ThingsBoard环境,注册物联网设备,并通过Python程序上报设备数据和位置信息,最后在Web界面上查看这些数据。

二、参考链接

三、效果展示

请添加图片描述

四、详细操作步骤

1、安装ThingsBoard

为什么选择Docker安装?
Docker可以让我们快速部署应用,避免复杂的依赖和环境配置问题,特别适合开发和测试环境。

mkdir -p ~/.mytb-data && sudo chown -R 799:799 ~/.mytb-data
mkdir -p ~/.mytb-logs && sudo chown -R 799:799 ~/.mytb-logs
docker run -d -p 9090:9090 -p 7070:7070 -p 1883:1883 \
	-p 5683-5688:5683-5688/udp \
	-v ~/.mytb-data:/data \
	-v ~/.mytb-logs:/var/log/thingsboard \
	--name mytb --restart always thingsboard/tb-postgres

端口说明:

  • 9090:Web管理界面端口
  • 1883:MQTT协议端口,设备通过此端口连接
  • 7070:HTTP API端口
  • 5683-5688:CoAP协议端口,用于低功耗设备

2、Web登录并配置设备

访问ThingsBoard管理界面:

# 在浏览器中访问(将<IP>替换为你的服务器IP)
http://<你的服务器IP>:9090

# 默认登录账号
用户名:tenant@thingsboard.org
密码:tenant

设备注册流程:

  1. 登录后进入"设备"菜单
  2. 点击"+"添加新设备
  3. 填写设备名称和描述
  4. 创建设备后,点击设备详情,复制"访问令牌"
    • 这个令牌相当于设备的身份证,用于验证设备身份

3、安装Python客户端SDK

什么是MQTT客户端?
MQTT是一种轻量级的物联网通信协议,我们的Python程序需要通过这个客户端库与ThingsBoard服务器通信。

pip3 install tb-mqtt-client

4、编写并运行设备客户端程序

程序功能详解:

这个Python程序主要完成以下任务:

cat > tb_client.py << 'EOF'
import sys
import os
import pyproj
sys.path.insert(0,"/opt/apollo/neo/python/cyber/python")
sys.path.insert(0,"/opt/apollo/neo/python")
import ctypes
import socket
from cyber_py3 import cyber
from modules.common_msgs.chassis_msgs import chassis_pb2
from modules.common_msgs.localization_msgs import localization_pb2
from modules.common_msgs.control_msgs import control_cmd_pb2
from tb_device_mqtt import TBDeviceMqttClient, TBPublishInfo
import argparse
import time
import numpy as np
import threading
import math

x_pi = 3.14159265358979324 * 3000.0 / 180.0
pi = 3.1415926535897932384626 # π
a = 6378245.0 # 长半轴
ee = 0.00669342162296594323 # 扁率

def transformlat(lng, lat):
    ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng))
    ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *math.sin(2.0 * lng * pi)) * 2.0 / 3.0
    ret += (20.0 * math.sin(lat * pi) + 40.0 *
    math.sin(lat / 3.0 * pi)) * 2.0 / 3.0
    ret += (160.0 * math.sin(lat / 12.0 * pi) + 320 *
    math.sin(lat * pi / 30.0)) * 2.0 / 3.0
    return ret

def transformlng(lng, lat):
    ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng))
    ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *math.sin(2.0 * lng * pi)) * 2.0 / 3.0
    ret += (20.0 * math.sin(lng * pi) + 40.0 *math.sin(lng / 3.0 * pi)) * 2.0 / 3.0
    ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 *math.sin(lng / 30.0 * pi)) * 2.0 / 3.0
    return ret

def wgs84togcj02(lng,lat):
    """
    WGS84转GCJ02(火星坐标系)
    :param lng:WGS84坐标系的经度
    :param lat:WGS84坐标系的纬度
    :return:列表
    """
    dlat = transformlat(lng - 105.0, lat - 35.0)
    dlng = transformlng(lng - 105.0, lat - 35.0)
    radlat = lat / 180.0 * pi
    magic = math.sin(radlat)
    magic = 1 - ee * magic * magic
    sqrtmagic = math.sqrt(magic)
    dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
    dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
    mglat = lat + dlat
    mglng = lng + dlng
    return [mglng, mglat]

class ApolloStatus(ctypes.Structure):
    _pack_ = 4  # 4字节对齐
    _fields_ = [
        ("speed_mps", ctypes.c_double),
        ("odometer_m", ctypes.c_double),
        ("throttle_percentage", ctypes.c_double),
        ("brake_percentage", ctypes.c_double),
        ("steering_percentage", ctypes.c_double),
        ("x", ctypes.c_double),
        ("y", ctypes.c_double),
        ("heading", ctypes.c_double),
        ("engine_started", ctypes.c_int),
        ("parking_brake", ctypes.c_int),
        ("low_beam_signal", ctypes.c_int),
        ("left_turn_signal", ctypes.c_int),
        ("right_turn_signal", ctypes.c_int),
        ("driving_mode", ctypes.c_int),
        ("error_code", ctypes.c_int),
        ("battery_soc_percentage", ctypes.c_int),
        ("horn", ctypes.c_int)
    ]

g_status = ApolloStatus()

def chassis_callback(msg):
    global g_status
    g_status.engine_started=msg.engine_started
    g_status.speed_mps=msg.speed_mps
    g_status.odometer_m=msg.odometer_m
    g_status.throttle_percentage=msg.throttle_percentage
    g_status.brake_percentage=msg.brake_percentage
    g_status.steering_percentage=msg.steering_percentage
    g_status.parking_brake=msg.parking_brake
    g_status.low_beam_signal=msg.low_beam_signal
    g_status.left_turn_signal=msg.left_turn_signal
    g_status.right_turn_signal=msg.right_turn_signal
    g_status.driving_mode=msg.driving_mode
    g_status.error_code=msg.error_code
    g_status.battery_soc_percentage=msg.battery_soc_percentage
    g_status.horn=msg.horn

def pos_callback(msg):
    global g_status
    x=msg.pose.position.x
    y=msg.pose.position.y
    heading=msg.pose.heading
    projector2 = pyproj.Proj(proj='utm', zone=50, ellps='WGS84')
    lon, lat = projector2(x, y, inverse=True)
    x,y=wgs84togcj02(lon, lat)
    g_status.x=x
    g_status.y=y
    g_status.heading=heading

client=None
def monitor_function():
    global g_status
    while True:
        global client
        telemetry = {"engine_started": g_status.engine_started,
                     "speed_mps": g_status.speed_mps,
                     "throttle_percentage": g_status.throttle_percentage,
                     "brake_percentage":g_status.brake_percentage,
                     "steering_percentage":g_status.steering_percentage,
                     "latitude":g_status.y,
                     "longitude":g_status.x}
        telemetry_with_ts = {"ts": int(round(time.time() * 1000)), "values": {"battery_soc_percentage": g_status.battery_soc_percentage}}
        client.send_telemetry(telemetry_with_ts)
        client.send_telemetry(telemetry)
        time.sleep(5)

def on_server_side_rpc_request(request_id, request_body):
    global client
    print(request_id, request_body)
    if request_body["method"] == "getValue":
        client.send_rpc_reply(request_id, {"value": True})
    elif request_body["method"] == "setValue":
        value=request_body['params']
        print(value)
        client.send_rpc_reply(request_id, {"value": True})

def main():
    parser = argparse.ArgumentParser(description='网络延迟测试客户端')
    parser.add_argument('--host', default='localhost', help='服务器地址 (默认: localhost)')    
    parser.add_argument('--token', default='', help='')
    
    args = parser.parse_args()
    
    cyber.init()
    node = cyber.Node("tb_client")
    node.create_reader("/apollo/canbus/chassis",
                        chassis_pb2.Chassis,
                        chassis_callback)
    node.create_reader("/apollo/localization/pose",
                        localization_pb2.LocalizationEstimate,
                        pos_callback)
    global client
    client = TBDeviceMqttClient(args.host, username=args.token)
    client.set_server_side_rpc_request_handler(on_server_side_rpc_request)
    client.connect()

    thread = threading.Thread(target=monitor_function)
    thread.start()
    node.spin()
    cyber.shutdown()

if __name__ == "__main__":
    main()
EOF
python3 tb_client.py --host <服务器IP> --token=<获取的令牌>

代码说明:

  1. 订阅Apollo消息: 获取车辆信息
  2. 设备连接:使用令牌连接到ThingsBoard
  3. 数据上报:定期发送设备状态数据
  4. RPC处理:响应服务器的远程控制命令
  5. 多线程:使用独立线程处理数据上报

五、数据可视化配置

  1. 进入ThingsBoard的仪表板菜单

    • 点击左侧菜单的"仪表板"
    • 点击"+"创建新仪表板
  2. 添加数据可视化组件

    • 点击"编辑仪表板"
    • 添加各种部件:
      • 数字/图表:显示速度、电量等数据
      • 地图:显示设备位置
      • 开关:远程控制设备
  3. 配置数据源

    • 选择对应的设备和遥测数据
    • 设置刷新间隔
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐