ESP32部署开源物联网平台ThingsBoard

1. 前言
之前使用阿里云物联网平台搭建的电能表温表IOT采集并前端展示的项目,由于阿里云IOT Studio体验版到期了,前端展示被关闭了。继续使用要收取一笔高昂费用,我自然消费不起,那么现在我的目标就是使用开源物联网平台来代替阿里云物联网,这意味着你要自己部署物联网MQTT消息服务器,不过这个也不难,搞一台linux机器就行,可以是自己购买公网服务器,也可以家里的内网x86小主机。好吧,开始开干!


PS: 阿里云物联网前端应用无法展示
2. 部署MQTT服务器
2.1 目前有什么选择
EMQX自带Web后台,Mosquitto没有Web后台
EMQX多节点集群是需要购买商业许可证的,但是单节点部署是免费的,高性能MQTT服务器
ThingsBoard是自带mqtt服务器,完善的可视化以及设备管理,内置低配MQTT服务器
2.2 部署EMQX
version: '3.8'
services:
emqx:
image: emqx/emqx:latest
container_name: emqx
restart: unless-stopped
hostname: emqx-node
mem_limit: 1024M
environment:
- EMQX_NODE_NAME=emqx@emqx-node
- EMQX_LOG__FILE__DEFAULT__ENABLE=true
# 禁用遥测,可选
- EMQX_TELEMETRY__ENABLE=false
- TZ=Asia/Shanghai
volumes:
# 数据持久化
- ./data:/opt/emqx/data
- ./log:/opt/emqx/log
ports:
# MQTT 标准端口
- "1883:1883"
# WebSocket 端口
#- "8083:8083"
# WebSocket 安全端口
#- "8084:8084"
# MQTT over TLS
#- "8883:8883"
# Dashboard Web 管理后台
- "18083:18083"
healthcheck:
test: ["CMD", "/opt/emqx/bin/emqx", "ctl", "status"]
interval: 10s
timeout: 10s
retries: 5


PS: 登录EMQX之后的界面
2.3 EMQX与阿里云物联网区别
阿里云物联网平台解决了什么问题:一站式解决方案,提供了从设备连接到应用开发的全套服务,除了消息中转,还集成了设备管理、物模型、规则引擎、数据存储、安全认证等功能,并连接阿里云庞大的生态服务。开箱即用,需要付费。
EMQX解决了什么问题:MQTT消息服务器,是一个高性能、开源的 MQTT 消息服务器。核心功能是完成接收和分发来自设备的消息。但它本身不提供设备管理、数据存储或应用开发等一体化能力。但是可以与外部数据库、可视化平台等系统的完美集成。
使用标准的mqtt协议连接emqx: 修改MQTT服务器地址和端口
使用开源数据可视化工具做前端展示:ThingsBoard ,是开源物联网平台,为IoT方案提供设备管理、数据采集、处理与可视化,内置轻量级
2.4 小结
因为目前家里机器性能以及资源有限,内存条才4G, 如果要跑EMQX作为高性能MQTT Broker,加上ThingsBoard作为可视化应用服务,根本不可能,容器服务会把最后的系统内存都占满,系统开机就宕机。最后选择ThingsBoard部署这个项目,这个已经完全够用,因为设备连接数很少,用不上高性能MQTT Broker,低配的内置MQTT Broker也可以用。当前日后如果真的需要EMQX,那么编写规则引擎让接入Grafana可视化工具,这就非常完美。不废话,下一步部署ThingsBoard!
3. 部署ThingsBoard
3.1 部署方式
不采用微服务方式部署,不需要分别部署多个组件,常用单体部署方式更加省内存,所有核心功能都打包在同一个Java虚拟机进程中运行
使用thingsboard/tb-postgres:latest镜像,内置PostgreSQL数据库
默认账号:
Systen Administrator: [email protected] / sysadmin
Tenant Administrator: [email protected]/ tenant
Customer User: [email protected]/ customer
租户管理员可以创建设备,创建仪表板
系统管理员可以创建租户,客户用户只能查看
部署编排:
version: '3.8'
services:
thingsboard:
image: thingsboard/tb-postgres:latest
container_name: thingsboard
restart: unless-stopped
mem_limit: 1536M # 限制最大内存 1GB+512M+512M 小于它会宕机
ports:
- "18080:9090" # Web UI 端口
- "11883:1883" # MQTT 设备接入端口
environment:
TB_QUEUE_TYPE: in-memory # 使用内存队列,降低资源占用,指定消息队列类型
volumes:
- ./tb-data:/data
- ./tb-logs:/var/log/thingsboard
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9090/api/noauth/health"]
interval: 30s
timeout: 10s
retries: 3
3.2 容器权限问题
解决容器权限的问题:
日志报错如下:
initdb: error: could not create directory "/data/db": Permission denied
授予操作文件权限:
docker run --rm thingsboard/tb-postgres cat /etc/passwd #查看容器所有用户
#输出: thingsboard:x:799:799:Thingsboard application,,,:/home/thingsboard:/usr/sbin/nologin
#ps: 用户名:密码占位符:用户ID(UID):组ID(GID):用户描述:家目录:登录Shell
#目前确定了UID是799
#授予权限
sudo chown -R 799:799 ./tb-data
sudo chown -R 799:799 ./tb-logs
日志显示如下:
Installation finished successfully!
Starting ThingsBoard ...
表示安装成功,服务启动了,经过测试1024M内存会导致访问被Killed ,增加到1024+256M+256M 可以运行
3.3 创建设备

登录租户,创建设备连接参数如下:

使用树莓派完成连通性测试:

之前阿里云的MQTT链接参数:
client ID 是系统自动拼接的
user name 是系统自动拼接的
password 是自动随机生成的

PS: 之前阿里云MQTT的连接参数
在thingsboard中,client ID、 user name 、 password 这些MQTT基本连接参数可以自定义或者自动生成
3.4 数据类型
thingsboard中的数据类型:
属性(Attributes):用于描述设备相对静态的特征。比如设备固件版本、运行模式等。在平台里只保留最新的一个值。
遥测(Telemetry):用于上报随时间连续变化的测量数据,比如温度、湿度、电压、电流等。每次上报都会在数据库里生成一条带有时间戳的新记录,方便后续做历史趋势分析。
主题:
v1/devices/me/telemetry(用于遥测)
v1/devices/me/attributes(用于属性)
v1/devices/me/attributes/response/+(<request_id>)(订阅主题)
平台收到上报的数据:

关键代码:
/* thingsboard mqtt */
#define TB_BROKER "192.168.1.10"
#define TB_PORT 11883 // Thingsboard MQTT 非加密端口,使用加密请改为 8883并配置证书
#define TB_CLIENT_ID "xnn7fo32lwpzgz56ks7h"
#define TB_USERNAME "sbu0bic9smatdufeh5st"
#define TB_PASSWORD "p8fg43v4f5rk545vaxxd"
#define TB_TOPIC_TELEMETRY "v1/devices/me/telemetry"
// Thingsboard MQTT 连接参数,不需要生成,已经硬编码
snprintf(clientId, sizeof(clientId), TB_CLIENT_ID);
snprintf(username, sizeof(username), TB_USERNAME);
snprintf(password, sizeof(password), TB_PASSWORD);
// 配置 MQTT 客户端,生成 host
// snprintf(ali_broker, sizeof(ali_broker), "%s.iot-as-mqtt.%s.aliyuncs.com",
// MQTT_CRE_PRODUCT_KEY, MQTT_CRE_REGION_ID);
ESP_LOGI(TAG, "broker: %s", TB_BROKER);
ESP_LOGI(TAG, "port: %d", TB_PORT);
ESP_LOGI(TAG, "clientId: %s", clientId);
ESP_LOGI(TAG, "username: %s", username);
ESP_LOGI(TAG, "password: %s", password); // 注意:生产环境不要打印密码
// 配置MQTT客户端
esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.uri = NULL, // 不使用uri方式,而是分别设置hostname和port
.broker.address.hostname = TB_BROKER, // Thingsboard MQTT接入地址
.broker.address.port = TB_PORT, // 非加密端口,如需加密请使用8883并配置证书
.broker.address.transport = MQTT_TRANSPORT_OVER_TCP,
.credentials.client_id = clientId,
.credentials.username = username,
.credentials.authentication.password = password,
.session.keepalive = 60, // 心跳间隔60秒
};
/**
* @brief 从 Modbus 数据队列读取数据,并调用处理函数发布到 MQTT
*
* @param client
* @param modbus_queue_handle
*/
static void mqtt_update_modbus_sensor_data(esp_mqtt_client_handle_t client, QueueHandle_t modbus_queue_handle)
{
modbus_data_msg_t msg;
// 循环接收所有可用消息,直到队列空
while (xQueueReceive(modbus_queue_handle, &msg, 0) == pdTRUE) {
if (msg.id == DEVICE_ENERGY) {
ESP_LOGI(TAG, "Received Modbus data for Energy Meter: current=%.2f, power=%.2f, energy=%.2f",
msg.current, msg.power, msg.energy);
} else if (msg.id == DEVICE_SHT30) {
ESP_LOGI(TAG, "Received Modbus data for SHT30 Sensor: temp=%.2f, hum=%.2f",
msg.temp_f, msg.hum_f);
} else {
ESP_LOGI(TAG, "Received Modbus data for Unknown Device ID %d", msg.id);
continue; // 跳过未知设备
}
// 每次接收后立即上报最新数据
mqtt_publish_modbus_data_msg(client, &msg, TB_TOPIC_TELEMETRY);
}
}
/**
* @brief 处理 Modbus 传感器数据,并发布到 MQTT 上报 Topic
*
* @param client
* @param msg
* @param topic
*/
static void mqtt_publish_modbus_data_msg(esp_mqtt_client_handle_t client, modbus_data_msg_t *msg, const char* topic)
{
static uint16_t msg_id = 0;
if( topic == NULL || strlen(topic) == 0 ) {
ESP_LOGE(TAG, "Invalid topic for publishing modbus sensor data");
return;
}
cJSON *root = cJSON_CreateObject();
if(msg->id == DEVICE_ENERGY){
cJSON_AddNumberToObject(root, "current", ((int)(msg->current * 100)) / 100.0f); // 保留两位小数
cJSON_AddNumberToObject(root, "power", ((int)(msg->power * 100)) / 100.0f); // 保留两位小数
cJSON_AddNumberToObject(root, "energy", ((int)(msg->energy * 100)) / 100.0f); // 保留两位小数
cJSON_AddNumberToObject(root, "energy_online", msg->is_online);
}else if(msg->id == DEVICE_SHT30){
// 保留两位小数
cJSON_AddNumberToObject(root, "temp", ((int)(msg->temp_f * 100)) / 100.0f);
cJSON_AddNumberToObject(root, "hum", ((int)(msg->hum_f * 100)) / 100.0f);
cJSON_AddNumberToObject(root, "temp_online", msg->is_online);
}
char *json_str = cJSON_PrintUnformatted(root);
esp_mqtt_client_publish(client, topic, json_str, 0, 1, 0);
cJSON_free(json_str);
cJSON_Delete(root);
}
3.5 数据展示
前端展示:

前端展示地址:
https://tbui.hainiu1220.cn/dashboard/7cd30bb0-3760-11f1-961c-ef9f1b85e573?publicId=e3d97ad0-36b4-11f1-961c-ef9f1b85e573
4. 最后
4.1 代码
#克隆指定分支上的tag, -b指定动态静态引用
#克隆 b-thingsboard-v0.1 标签代码
git clone -b b-thingsboard-v0.1 https://github.com/haiou1220/esp32c3-mini-mqtt.git
4.2总结
目前使用ThingsBoard解决了,阿里云物联网平台在展示数据时候的收费问题。借助ThingsBoard可以完全放弃商用的阿里云物联网的解决方案,虽然阿里云物联网MQTT消息服务器是有免费额度的,但是其中连接设备数也是有一定的限制的,所以想要免费使用,还是得花时间部署开源物联网平台。
目前连接到ThingsBoard 局域网mqtt服务器是使用mqtt协议连接,之后考虑使用纯服务端方式的 商用cpolar / 自建frp 。这样在户外4G模块也能连接局域网的mqtt服务器了。
文章评论