Ubuntu安装Mosquitto,做一个自己的物联网服务器

Ubuntu安装Mosquitto服务器并导出运行日志,web查看运行日志并导出为Excel(未写),本文需要同一局域网环境,公网访问需要使用公网IP或穿透

注意:Linux下创建新文件请在使用前确认权限

          所有操作请注意路径、用户名、密码等

目录

一,准备工作

       1 一台运行Ubuntu的主机,主机完成基础配置,ssh等(本文使用树莓派5)

       2 一台其他支持mqtt收发的设备用来测试

二,安装

1 确保操作系统是最新的

2 安装Mosquitto

3 测试(可跳过)

4 配置防火墙(默认1883端口)

5 设置Mosquitto开机启动

6 设置Mosquitto配置文件(重点)

        6.1打开配置文件

        6.2修改文件如下

7 创建密码文件

8 重启服务使配置文件生效

三、使用

1 确认正常监听端口

2 确认连接状态(windows powershell)

3 连接至Linux服务器(本文使用Windows MQTTX)

4 接收测试

5 发送测试

6 接收数据并导出文件

6.1 导出为JSON

6.2 导出为txt

四、运行演示

五、固定Linux设备ip

六、ESP32连接等注意事项

七、Python解析json并从网页导出日志

1 安装所需python库

2 选择一个目录创建并写入前后端代码

app.py

index.html

3 运行并访问


一,准备工作

       1 一台运行Ubuntu的主机,主机完成基础配置,ssh等(本文使用树莓派5)

       2 一台其他支持mqtt收发的设备用来测试

二,安装

1 确保操作系统是最新的

sudo apt update

sudo apt upgrade -y

2 安装Mosquitto

sudo apt install mosquitto mosquitto-clients -y

        安装完成后查看运行状态会显示runing

sudo systemctl status mosquitto

                

3 测试(可跳过)

        分别运行以下指令测试Mosquitto服务器

mosquitto_pub -h localhost -t "test/topic" -m "Hello, MQTT"
mosquitto_sub -h localhost -t "test/topic"

4 配置防火墙(默认1883端口)

sudo ufw allow 1883

5 设置Mosquitto开机启动

sudo systemctl enable mosquitto

6 设置Mosquitto配置文件(重点)

        6.1打开配置文件
sudo nano /etc/mosquitto/mosquitto.conf
        6.2修改文件如下

allow_anonymous false                          设置为禁止匿名连接(部分库匿名连接容易崩溃)

password_file /etc/mosquitto/pwfile        选择密码文件(等会创建)

bind_address 0.0.0.0                              不限制设备

# Place your local configuration in /etc/mosquitto/conf.d/
#
# A full description of the configuration file is at
# /usr/share/doc/mosquitto/examples/mosquitto.conf.example

#pid_file /run/mosquitto/mosquitto.pid

persistence true
persistence_location /var/lib/mosquitto/

log_dest file /var/log/mosquitto/mosquitto.log

include_dir /etc/mosquitto/conf.d

allow_anonymous false

password_file /etc/mosquitto/pwfile

bind_address 0.0.0.0

7 创建密码文件

系统会提示输入密码,your_username就是用户名

创建密码文件
sudo mosquitto_passwd -c /etc/mosquitto/pwfile your_username
设置文件权限
sudo chown mosquitto:mosquitto /etc/mosquitto/pwfile
sudo chmod 600 /etc/mosquitto/pwfile

8 重启服务使配置文件生效

sudo systemctl restart mosquitto

三、使用

1 确认正常监听端口

安装 net-tools 包
sudo apt install net-tools -y
运行 netstat 命令
sudo netstat -tuln | grep 1883

2 确认连接状态(windows powershell)

Test-NetConnection -ComputerName 192.168.66.161 -Port 1883

3 连接至Linux服务器(本文使用Windows MQTTX)

服务器地址填写Linux设备的IP,用户名和密码为先前设置,弹出已连接则正常

4 接收测试

mosquitto_sub 订阅 -p 端口 -t "订阅话题" -u "用户名" -P "密码"

mosquitto_sub -p 1883 -t "test/topic" -u "your_username" -P "password"

话题与命令行一致,命令行中可以看到订阅的消息

5 发送测试

mosquitto_pub 发送 -m "内容"

mosquitto_pub -p 1883 -t "test/winrec" -m "HELLO" -u "your
_username" -P "password"

6 接收数据并导出文件

6.1 导出为JSON

路径等请自行更换并设置好权限

mosquitto_sub -p 1883 -t "collect/updata" -u "pi5" -P "password" | while read -r msg; do echo "{\"timestamp\": \"$(date +'%Y-%m-%dT%H:%M:%S')\", \"message\": \"$msg\"}" >> "/home/pi5/mqtt/data/$(date +'%y_%m_%d').json"; done
文件夹创建参考
sudo mkdir -p /home/mqtt/data
sudo chmod -R 777 /home/mqtt/data
6.2 导出为txt
mosquitto_sub -p 1883 -t "test/topic" -u "your_username" -P "password" >> "/home/mqtt/data/$(date +'%y_%m_%d').txt"

四、运行演示

运行导出为JSON命令并发送一段数据

sudo nano /home/mqtt/data/25_02_22.json 查看运行结果

五、固定Linux设备ip

依次运行将Xiaomi 13网络下的设备IP固定为192.168.66.66
sudo nmcli con mod "Xiaomi 13" ipv4.addresses 192.168.66.66/24
sudo nmcli con mod "Xiaomi 13" ipv4.gateway 192.168.66.1
sudo nmcli con mod "Xiaomi 13" ipv4.dns "8.8.8.8"
sudo nmcli con mod "Xiaomi 13" ipv4.method manual
设置完成后重连WiFi
sudo nmcli con up "Xiaomi 13"

六、ESP32连接等注意事项

使用PubSubClient库时需要设置参数,否则esp32会崩溃,以下参数测试可行,例程如下

//PubSubClient ESP32-Linux
//PubSubClient.h 中修改MQTT_MAX_PACKET_SIZE 512 MQTT_KEEPALIVE 180
#include <WiFi.h>
#include <PubSubClient.h>
#include "mqttserver.h"

// 平台配置
const char* mqttServer = "192.168.66.66";
const int mqttPort = 1883;
const char* mqttClientId = "ESP32_1";
const char* mqttUsername = "your_username";
const char* mqttPassword = "password";

// Topic配置
String topic_control = "test/dowload";  // 属性设置Topic
String topic_property_post = "test/update";  // 属性上报Topic

WiFiClient espClient; // 创建一个WiFi客户端实例,用于与WiFi网络连接
PubSubClient client(espClient); // 创建一个PubSubClient实例,将WiFi客户端传递给它以进行MQTT通信

// 物联网参数
float depth_water;
float humi, temp;

void mqtt_init(void) {
  // 设置MQTT服务器地址和端口(告诉客户端在哪里找到MQTT代理)
  client.setServer(mqttServer, mqttPort);
  // 设置MQTT消息回调函数(用于处理接收到的MQTT消息。当客户端接收到消息时,这个函数会被自动调用)
  client.setCallback(callback);
}

//MQTT状态判断,断连则重连,连接正常则上报数据
void mqtt_server(void) {
  // 检查MQTT连接状态
  if (!client.connected()) {
    connectMQTT(); // 如果未连接,则尝试连接MQTT服务器
  }
  client.loop(); // 处理MQTT消息(MQTT客户端的核心循环,处理所有待处理的消息和回调。如果没有调用这个函数,客户端将无法接收消息。)
  reportDeviceStatus(); // 报告设备状态
}

// 处理收到的控制命令
void callback(char* topic, byte* payload, unsigned int length) {
  // 将payload转换为字符串
  String message;
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
  }

  Serial.print("Message received: ");
  Serial.println(message);
}

// MQTT服务器连接
void connectMQTT(void) {
  // 只要未连接到MQTT服务器,就持续尝试连接
  while (!client.connected()) {
    Serial.println("Reconnecting to MQTT..."); // 输出重新连接的信息
    // 尝试连接到MQTT服务器(使用提供的客户端ID、用户名和密码)
    if (client.connect(mqttClientId, mqttUsername, mqttPassword)) {
      Serial.println("Reconnected to MQTT server"); // 输出连接成功的信息
      client.subscribe(topic_control.c_str());  // 重新订阅控制命令主题
    } else {
      // 如果连接失败,输出错误状态
      Serial.print("MQTT reconnection failed, state: ");
      Serial.println(client.state());
      delay(2000); // 等待2秒再尝试重新连接
    }
  }
}

// 上传数据
void reportDeviceStatus(void) {
  // 构造JSON格式的负载
  String payload = "{\"depth_water\":" + String(depth_water) + ",\"humi\":" + String(humi) +",\"temp\":" + String(temp) +"}";

  // 通过MQTT发布负载
  if (client.publish(topic_property_post.c_str(), payload.c_str())) {
    Serial.println("Device status reported successfully"); // 输出成功信息
  } else {
    Serial.println("Failed to report device status"); // 输出失败信息
  }
}

七、Python解析json并从网页导出日志

1 安装所需python库

sudo apt install python3-pip python3-flask

sudo apt install python3-pandas python3-openpyxl

2 选择一个目录创建并写入前后端代码

cd /home/pi5

touch app.py
touch index.html

app.py
from flask import Flask, request, jsonify, send_from_directory
import os
import json
import re
import pandas as pd
from datetime import datetime

app = Flask(__name__)

# 设置存储 JSON 文件的目录
DATA_DIR = '/home/pi5/mqtt/data/'

# 获取所有日期的文件名
def get_json_files():
    files = [f for f in os.listdir(DATA_DIR) if f.endswith('.json')]
    return sorted(files)

# 解析指定日期的 JSON 文件
def parse_json_file(file_name):
    try:
        data_list = []
        with open(os.path.join(DATA_DIR, file_name), 'r') as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                try:
                    # 尝试直接解析行数据
                    data = json.loads(line)
                except json.JSONDecodeError:
                    # 使用正则表达式修复 message 字段的格式问题
                    corrected_line = re.sub(
                        r'"message":\s*"(\{.*?\})"',
                        r'"message": \1',
                        line,
                        flags=re.DOTALL
                    )
                    try:
                        data = json.loads(corrected_line)
                    except json.JSONDecodeError as e:
                        print(f"Error decoding corrected line in {file_name}: {e}")
                        continue
                # 检查 message 字段是否为字符串,尝试进一步解析
                if 'message' in data and isinstance(data['message'], str):
                    try:
                        data['message'] = json.loads(data['message'])
                    except json.JSONDecodeError:
                        print(f"Failed to parse message field in {file_name}")
                data_list.append(data)
        return data_list
    except Exception as e:
        print(f"Error reading {file_name}: {e}")
        return None

# 查询指定日期范围的数据
@app.route('/query', methods=['GET'])
def query_data():
    start_date = request.args.get('start_date')  # 获取查询的开始日期
    end_date = request.args.get('end_date')      # 获取查询的结束日期

    # 解析日期格式 (yyyy-mm-dd)
    try:
        start_date = datetime.strptime(start_date, '%Y-%m-%d')
        end_date = datetime.strptime(end_date, '%Y-%m-%d')
    except ValueError:
        return jsonify({'error': 'Invalid date format. Please use yyyy-mm-dd.'}), 400

    result = []

    # 遍历所有文件,根据时间范围过滤文件
    for file_name in get_json_files():
        # 提取文件日期(假设文件名是类似 "25_02_22.json")
        file_date_str = file_name.split('.')[0]
        try:
            file_date = datetime.strptime(file_date_str, '%y_%m_%d')
        except ValueError:
            print(f"Skipping invalid filename: {file_name}")
            continue

        # 如果文件日期在指定的时间范围内,解析并返回数据
        if start_date <= file_date <= end_date:
            parsed_data = parse_json_file(file_name)
            if parsed_data:
                for entry in parsed_data:
                    # 提取 timestamp 字段并仅保留时间部分
                    timestamp = entry.get("timestamp", "")
                    time_part = timestamp.split('T')[1] if 'T' in timestamp else ""

                    # 提取 message 字段,并格式化为 "key: value | key: value"
                    message = entry.get("message", {})
                    message_str = " | ".join([f"{key}: {value}" for key, value in message.items()])

                    # 将时间和格式化后的 message 拼接
                    result.append({
                        'date': file_date.strftime('%Y-%m-%d'),
                        'data': f"{time_part} {message_str}"  # 拼接时间和消息内容
                    })

    return jsonify(result)



# 导出数据为 Excel
@app.route('/export_excel', methods=['GET'])
def export_excel():
    start_date = request.args.get('start_date')  # 获取查询的开始日期
    end_date = request.args.get('end_date')      # 获取查询的结束日期

    # 解析日期格式 (yyyy-mm-dd)
    try:
        start_date = datetime.strptime(start_date, '%Y-%m-%d')
        end_date = datetime.strptime(end_date, '%Y-%m-%d')
    except ValueError:
        return jsonify({'error': 'Invalid date format. Please use yyyy-mm-dd.'}), 400

    result = []

    # 遍历所有文件,根据时间范围过滤文件
    for file_name in get_json_files():
        # 提取文件日期(假设文件名是类似 "25_02_22.json")
        file_date_str = file_name.split('.')[0]
        try:
            file_date = datetime.strptime(file_date_str, '%y_%m_%d')
        except ValueError:
            print(f"Skipping invalid filename: {file_name}")
            continue

        # 如果文件日期在指定的时间范围内,解析并返回数据
        if start_date <= file_date <= end_date:
            parsed_data = parse_json_file(file_name)
            if parsed_data:
                for entry in parsed_data:
                    result.append({
                        'date': file_date.strftime('%Y-%m-%d'),
                        'data': entry
                    })

    # 将查询结果转换为 pandas DataFrame
    df_list = []
    for item in result:
        date_str = item['date']
        data = item['data']
        # 检查 'message' 是否为字符串,如果是,则解析它为字典
        if isinstance(data.get("message", ""), str):
            try:
                data['message'] = json.loads(data['message'])  # 解析为字典
            except json.JSONDecodeError:
                print(f"Failed to parse message field in {item['date']}")

        # 提取 'timestamp' 并只保留 'T' 后的部分,改为 'time'
        timestamp = data.get("timestamp", "")
        time_part = timestamp.split('T')[1] if 'T' in timestamp else ""

        # 提取字段,并确保获取有效的数据
        df_list.append({
            'date': date_str,
            'time': time_part,  # 只保留 'T' 后的部分
            'humi1': data.get("message", {}).get("humi1", ""),
            'temp2': data.get("message", {}).get("temp2", ""),
            #'temp': data.get("message", {}).get("temp", ""),
        })

    # 将数据保存到 Excel 文件
    df = pd.DataFrame(df_list)
    file_path = "data.xlsx"  # 使用相对路径
    df.to_excel(file_path, index=False, engine='openpyxl')

    # 使用 send_from_directory 发送文件,确保文件位于当前工作目录
    return send_from_directory(os.getcwd(), file_path, as_attachment=True)


# 返回静态文件(index.html)
@app.route('/')
def serve_index():
    return send_from_directory(os.getcwd(), 'index.html')

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MQTT Data Query</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        .container {
            max-width: 600px;
            margin: auto;
        }
        input, button {
            padding: 8px;
            margin: 5px;
        }
        .result {
            margin-top: 20px;
        }
        table {
            width: 100%;
            border-collapse: collapse;
        }
        table, th, td {
            border: 1px solid #ddd;
        }
        th, td {
            padding: 8px;
            text-align: left;
        }
    </style>
</head>
<body>

    <div class="container">
        <h1>Data Query</h1>
        
        <label for="start_date">Start Date:</label>
        <input type="date" id="start_date" required>
        
        <label for="end_date">End Date:</label>
        <input type="date" id="end_date" required>
        <br>
        <button onclick="fetchData()">Query Data</button>
        <button onclick="exportExcel()">Export to Excel</button>

        <div class="result">
            <h3>Results</h3>
            <div id="data-table"></div>
        </div>
    </div>

    <script>
        async function fetchData() {
            const start_date = document.getElementById('start_date').value;
            const end_date = document.getElementById('end_date').value;
            
            if (!start_date || !end_date) {
                alert("Please select both start and end dates.");
                return;
            }
            
            const response = await fetch(`/query?start_date=${start_date}&end_date=${end_date}`);
            const data = await response.json();

            let tableHTML = '<table><tr><th>Date</th><th>Data</th></tr>';

            data.forEach(item => {
                tableHTML += `<tr><td>${item.date}</td><td>${JSON.stringify(item.data)}</td></tr>`;
            });

            tableHTML += '</table>';
            document.getElementById('data-table').innerHTML = tableHTML;
        }

        function exportExcel() {
            const start_date = document.getElementById('start_date').value;
            const end_date = document.getElementById('end_date').value;

            if (!start_date || !end_date) {
                alert("Please select both start and end dates.");
                return;
            }

            window.location.href = `/export_excel?start_date=${start_date}&end_date=${end_date}`;
        }
    </script>

</body>
</html>

3 运行并访问

运行

python app.py

访问

设备IP:5000

### 如何搭建用于物联网的云服务器 构建一个完整的物联网(IoT)云服务器涉及多个技术领域,包括硬件选型、软件开发以及网络配置等方面。以下是关于如何搭建物联网服务器的关键点: #### 1. **基础架构设计** 在设计物联网服务器的基础架构时,需考虑以下几个方面: - 数据采集层:通过传感器或其他设备收集数据并上传到云端。 - 网络传输层:利用 Wi-Fi、蜂窝网络或 LoRaWAN 等协议实现设备与云端之间的通信。 - 应用处理层:负责数据分析、存储和可视化等功能。 当前许多大型企业已退出公共 IoT 平台市场[^1],因此中小企业可能需要转向私有化部署解决方案来满足其特定需求。 #### 2. **选择合适的硬件平台** 对于小型项目来说,可以选用树莓派(Raspberry Pi)作为边缘计算节点;而对于更大规模的应用场景,则建议采用更强大的服务器级处理器配合专用网卡以提高性能表现。 #### 3. **软件环境准备** 操作系统的选择至关重要,在Linux发行版中Ubuntu Server因其良好的社区支持而被广泛推荐给开发者们使用。此外还需要安装必要的中间件和服务程序比如MQTT Broker(Mosquitto),数据库管理系统(MySQL/PostgreSQL),Web框架(Flask/Django)[^2]. #### 4. **安全措施实施** 由于涉及到敏感信息传递,所以必须重视整个系统的安全性防护工作: - 使用SSL/TLS加密保护HTTP请求. - 对所有API接口设置身份验证机制. - 定期更新补丁修复漏洞. #### 示例代码片段展示简单的 MQTT broker 配置过程如下: ```bash sudo apt-get update sudo apt-get install mosquitto mosquitto-clients -y ``` 接着可以通过命令测试发布订阅功能: ```bash mosquitto_sub -h localhost -t test/topic mosquitto_pub -h localhost -t test/topic -m "Hello World" ``` 以上仅提供了一个基本概念性的指导方向,实际操作过程中还需依据具体业务情况出相应调整优化.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值