通过mqtt使用webhook转发消息实现远程查看单片机日志

通过mqtt使用webhook转发消息实现远程查看单片机日志

背景

我做的一个开机棒放在办公室,我在家里想远程唤醒开机,结果没开唤醒成功,mqtt也没回复唤醒结果,就想远程看一下开机棒的状态,开机棒又是用esp32做的,当然没法像ssh这种远程,就想可以把日志或者自身的状态发送给服务器,在服务器上查看信息。(最后发现是同事把我的开机棒连接的插座断电了o(╥﹏╥)o,像这种后续还要加一个掉电检测)

需要资源

只需一个公网服务器即可,当然你如果你都是在局域网下也可以,不过这样也就没有远程查看的需求了。

公网服务器搭建mqtt服务器,然后配置webhook转发,最后再写一个脚本监听webhook的转发达到存取日志的目的。

mqtt服务器

搭建

搭建mqtt服务器没什么好说的,记得搭建好之后写一个开机自启的服务,这样就能使用systemctl去控制了

shell 复制代码
/lib/systemd/system/emqx.service
[Unit]
Description=emqxautostart
After=network.target

[Service]
Type=forking
 
Environment=HOME=/root/app/emqx				 # 换成自己的emqx路径
ExecStart=/root/app/emqx/bin/emqx start		 # 换成自己的emqx路径
#ExecReload=/root/app/emqx/bin/emqxt restart # 换成自己的emqx路径
ExecStop=/root/app/emqx/bin/emqx stop		 # 换成自己的emqx路径
PrivateTmp=true

[Install]
WantedBy=multi-user.target

配置webhook

http://服务器地址:端口号/url

规则:

监听服务

验证了js和py两种版本的,实现效果完全一样,看自己的喜好

py版本

python 复制代码
import os
import json
from datetime import datetime
from flask import Flask, request
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

def ensure_log_directory(date_str):
    """确保日志目录存在"""
    script_dir = os.path.dirname(os.path.abspath(__file__))
    log_dir = os.path.join(script_dir, 'logPy', date_str[:6])  # 使用年月作为子目录名
    if not os.path.exists(log_dir):
        os.makedirs(log_dir, exist_ok=True)
    return log_dir

@app.route('/py_webhook', methods=['POST'])
def py_webhook():
    # 获取请求体数据
    if request.is_json:
        received_data = request.get_json()
    else:
        received_data = json.loads(request.data.decode('utf-8'))
    
    # 提取时间戳和有效载荷
    timestamp_ms = received_data.get('timestamp')
    payload = received_data.get('payload')
    
    if timestamp_ms:
        # 将毫秒时间戳转换为datetime对象
        timestamp = datetime.fromtimestamp(timestamp_ms / 1000.0)
    else:
        # 如果没有提供时间戳,使用当前时间
        timestamp = datetime.now()
    log_time = timestamp.strftime('%Y-%m-%d %H:%M:%S')
        
    
    # 使用年月格式作为目录名(如 "202601")
    month_str = timestamp.strftime('%Y%m')
    
    # 使用年月日格式作为文件名(如 "20260121.log")
    date_str = timestamp.strftime('%Y%m%d')
    
    # 确保日志目录存在
    log_dir = ensure_log_directory(month_str)
    log_file_path = os.path.join(log_dir, f'{date_str}.log')
    
    # 创建日志条目
    if isinstance(payload, str):
        log_entry = f'[{log_time}]  {payload}\n'
    else:
        log_entry = f'[{log_time}]  {json.dumps(payload)}\n'
    
    # 写入日志文件
    try:
        with open(log_file_path, 'a', encoding='utf-8') as log_file:
            log_file.write(log_entry)
        print(f'日志已写入: {log_file_path}',flush=True)
    except Exception as e:
        print(f'写入日志文件失败: {e}',flush=True)
    
    print(f'Received webhook payload: {payload}',flush=True)
    print(f'Received webhook timeStamp: {log_time}',flush=True)
    
    # 返回响应
    response_data = {'status': 'success', 'message': 'Webhook received successfully'}
    return json.dumps(response_data), 200, {'Content-Type': 'application/json'}

if __name__ == '__main__':
    # 确保主日志目录存在
    script_dir = os.path.dirname(os.path.abspath(__file__))
    main_log_dir = os.path.join(script_dir, 'logPy')
    if not os.path.exists(main_log_dir):
        os.makedirs(main_log_dir)
    
    app.run(host='0.0.0.0', port=3001, debug=False) # 使用localhost不行

js版本

javascript 复制代码
const express = require('express');
const mysql = require('mysql');
const fs = require('fs');
const path = require('path');
const bodyParser = require('body-parser');
const app = express();

// 使用 raw body parser 来捕获原始请求体 (既能处理json,又能文本)
app.use(express.raw({ type: () => true, limit: '10mb' }));
// 中间件:将原始 body 转换为适当的格式
app.use((req, res, next) => {
    // 尝试解析为 JSON
    try {
        req.body = JSON.parse(req.body.toString());
    } catch (e) {
        // 如果不是 JSON,保持原始字符串
        console.log("e : " + e + " " + req.body)
        req.body = req.body.toString();
    }

    next();
});

// 创建日志目录的函数
function ensureLogDirectory(dateStr) {
    const logDir = path.join(__dirname, 'logJs', dateStr);
    if (!fs.existsSync(logDir)) {
        fs.mkdirSync(logDir, { recursive: true });
    }
    return logDir;
}

// 处理Webhook请求的路由处理程序
app.post('/js_webhook', (req, res) => {
    const receivedData = req.body; // 获取Webhook请求正文中的数据
    // 提取时间戳和有效载荷
    const timestampMs = receivedData.timestamp;
    const payload = receivedData.payload;
    let timeStamp = 0;
    if (timestampMs) {
        // 使用接收到的时间戳
        timeStamp = new Date(timestampMs);
    }
    else {
        timeStamp = new Date();
        console.log("timestamp is null! body : " + receivedData);
    }
    // 将时间戳转换为日志格式 (yyyy-MM-dd HH:mm:ss)
    const logTime = timeStamp.getFullYear() + '-' +
        String(timeStamp.getMonth() + 1).padStart(2, '0') + '-' +
        String(timeStamp.getDate()).padStart(2, '0') + ' ' +
        String(timeStamp.getHours()).padStart(2, '0') + ':' +
        String(timeStamp.getMinutes()).padStart(2, '0') + ':' +
        String(timeStamp.getSeconds()).padStart(2, '0');
    // 使用年月格式作为目录名(如 "202601")
    const monthStr = timeStamp.getFullYear() +
        String(timeStamp.getMonth() + 1).padStart(2, '0');

    // 使用年月日格式作为文件名(如 "20260121.log")
    const dateStr = timeStamp.getFullYear() +
        String(timeStamp.getMonth() + 1).padStart(2, '0') +
        String(timeStamp.getDate()).padStart(2, '0');

    // 确保日志目录存在
    const logDir = ensureLogDirectory(monthStr);
    const logFilePath = path.join(logDir, `${dateStr}.log`); // 文件名格式为 YYYYMMDD.log

    // 创建日志条目
    const logEntry = `[${logTime}]  ${JSON.stringify(payload)}\n`;

    // 写入日志文件
    fs.appendFile(logFilePath, logEntry, (err) => {
        if (err) {
            console.error('写入日志文件失败:', err);
        } else {
            console.log(`日志已写入: ${logFilePath}`);
        }
    });

    console.log('Received webhook payload:', payload);
    console.log('Received webhook timeStamp:' + logTime + " " + timestampMs);
    res.status(200).json({ status: 'success', message: 'Webhook received successfully' });

});
// 启动服务器监听指定端口(例如:3000)
app.listen(3000, () => {
    console.log('Server started on port 3000');
});

开机自启

可以跟emqx那种服务也行,也可以配置rc.local脚本也行,看自己喜好,我这里提供了rc.local方式,rc.local存放到/etc/rc.local位置

shell 复制代码
# !!!都换成自己的路径!!!
# 我用的是nvm的node,系统自带的版本太低
nohup /root/.nvm/versions/node/v24.13.0/bin/node /root/sh/emqx_webhook/main.js > /root/sh/emqx_webhook/outputJs.log 2>&1 &
nohup python3 /root/sh/emqx_webhook/main.py > /root/sh/emqx_webhook/outputPy.log 2>&1 &

效果

可以看脚本运行的效果outputJs.log、outputPy.log,mqtt转发的日志按照日期文件夹存放到相应的文件夹

注意

都是血的教训

  1. 配置webhook时注意不要写成http://服务器地址/url:端口号
  2. 接收wenhook的脚本不运行,你在emqx的后台看webhook的状态一定是未连接的
  3. py脚本存放的路径一定要是绝对路径
相关推荐
Porco.w4 小时前
STM32之ESP8266
stm32·单片机·嵌入式硬件
蓝桥_吹雪6 小时前
HAL库深入了解--STM32与GPIO
单片机·嵌入式硬件
嗯嗯=6 小时前
STM32单片机学习篇5
stm32·单片机·学习
不能跑的代码不是好代码6 小时前
STM32:LED共阴/共阳连接与GPIO控制逻辑的关系,如何实现电平转换
stm32·单片机·嵌入式硬件
qq_25814297-npl8 小时前
HEX数据00,显示为ASC码,怎么是是\0
单片机
不做无法实现的梦~8 小时前
使用ros2跑mid360的fastlio2建图
git·单片机·嵌入式硬件·gitcode
Joshua-a8 小时前
正点原子DS100示波器测DC电源纹波方法
单片机·嵌入式硬件
2301_772204289 小时前
ARM——时钟系统
arm开发·单片机·嵌入式硬件
恶魔泡泡糖10 小时前
51单片机直流电机
单片机·嵌入式硬件·51单片机