我想用ESP32 做一個MQTT Viwer的網頁,以下是相關資料及要求
#WI-FI網路的配置
ssid = 'Bili-Net'
password = '0932388283'
# MQTT 配置
mqtt_server = '192.168.0.172'
mqtt_port = 11883
mqtt_topic = 'myTopic'
client_id = 'MQTT-message_client'
連線時請print連線的ip
如果遇到網路中斷請試著再連線
網頁最上方顯示"MQTT Viewer"
預設陣列來記錄每個發佈站點在顯示DIV的行數(橫)編號及Hostname,及該行最後的更新時間,及所接收的Jason資料,
我想可以用JAVASCRIPT用來更新資料有更新的行,是不要整個網頁更新,會產生閃爍
取得訂閱的資料先比對Hostname 再將資料更新於對的行,如果是新的Hostname 即在最下方新增一行
如果接收的HostName總數超過16個,就顯示超過接收的最大值,並將沒有在陣列中名稱的資料丟棄不處理
每行的顯示順序可參考
data = json.loads(msg.decode('utf-8'))
message = "<div class='message'>"
message += "{} ".format(data.get('hostname', ''))
message += "{} ".format(data.get('ip', ''))
message += "{} ".format(data.get('timestamp', ''))
for key, value in data.items():
if key not in ['hostname', 'ip', 'timestamp'] and value:
message += "{}:{} ".format(key, value)
message += "</div>"
broadcast(message)
except Exception as e:
print("Failed to parse JSON data:", e)
如果行超過1小時沒有消息更新,則該行改變字體顏色以RED字表示,如果再次取得新資料在將字更新為黑色
並將GPIO2的LED做為ESP32運行的指示燈,亮1秒息3秒
好的,以下是基於您的要求編寫的完整ESP32程式,使用MicroPython實現MQTT Viewer的網頁。這個程式將:
連接到指定的Wi-Fi網絡。
連接到MQTT服務器並訂閱特定的主題。
在ESP32上運行一個Web服務器,提供一個動態更新的網頁,顯示接收到的MQTT訊息。
使用JavaScript在前端更新數據,避免整頁刷新導致的閃爍。
控制GPIO2的LED作為運行指示燈,亮1秒,熄3秒。
如果某個Hostname超過1小時未更新,將該行的字體顏色變為紅色。
Wi-Fi連接
使用network模組連接到指定的Wi-Fi網絡。
如果連接失敗,會持續嘗試直到連接成功。
連接成功後,打印ESP32的IP地址。
MQTT連接
使用umqtt.robust模組連接到MQTT服務器。
設置回調函數mqtt_callback,處理接收到的MQTT訊息。
訂閱指定的主題myTopic。
MQTT消息處理
在mqtt_callback中,解析接收到的JSON數據。
根據hostname來更新或新增主機的數據。
如果hostname已存在於hostnames字典中,更新其數據和最後更新時間。
如果是新的hostname且未超過最大限制,新增到hostnames字典中。
如果超過16個hostname,將不再處理新的主機數據。
LED指示燈
使用Timer和Pin模組控制GPIO2的LED。
每隔4秒執行一次led_breath函數,實現亮1秒、熄3秒的效果。
Web服務器
使用uasyncio模組建立一個異步的Web服務器。
網頁包含一個JavaScript程式,每5秒向ESP32請求一次數據,並更新頁面內容。
數據以JSON格式傳輸,包含每個主機的訊息和字體顏色。
前端網頁
顯示標題"MQTT Viewer"。
使用<div id="messages"></div>來顯示所有的訊息。
根據接收到的數據,動態生成<div>元素,並設置相應的字體顏色。
字體顏色變化
在/data的請求處理中,計算每個主機最後更新的時間差。
如果超過1小時(3600秒)未更新,將color設置為'red',否則為'black'。
異常處理
如果MQTT連接失敗,將嘗試重新連接。
使用try-except來捕獲可能的異常,防止程式崩潰。
请按照以下步骤测试修改后的代码:
上传新的 main.py
将修改后的代码上传到 ESP32。
重启 ESP32
通过硬件复位或软件重启来重启设备。
监控串口输出
观察串口监视器,应该会看到类似以下输出:
vbnet
複製程式碼
Connecting to network...
Network connected: 192.168.0.188
Connected to 192.168.0.172 MQTT broker, subscribed to myTopic topic
Web server started on port 80
访问网页
在与 ESP32 位于同一网络的设备上,打开浏览器,输入 ESP32 的 IP 地址(例如,http://192.168.0.188/)。
应该会看到标题为 “MQTT Viewer” 的网页。
发布 MQTT 消息
确保有设备向 myTopic 主题发布包含 hostname 字段的 JSON 格式的消息。
查看数据更新
网页会每 5 秒自动获取最新数据并更新。
应该会看到来自不同 Hostname 的消息按行显示。
仍然无法访问网页
确认您的浏览器设备与 ESP32 在同一网络中。
检查是否有防火墙或网络设置阻止了访问。
ESP32 内存不足
如果 ESP32 内存不足,可能会导致代码无法正常运行。
您可以尝试在代码中添加 import gc,并在适当的位置调用 gc.collect() 来释放内存。
无法接收 MQTT 消息
确认 MQTT 服务器的地址和端口配置正确。
确认其他设备正在向正确的主题发布消息。
通过上述修改,您的程序应当能够正常运行,并满足所有需求。
很高兴听到程序现在可以正常运行。为了在每一行前加入所在的行号,我们需要在网页显示的消息前添加对应的行号。
以下是修改后的代码,在每个消息前添加了行号。
python
複製程式碼
# 2024/10/10-12:00
import network
import time
import json
from machine import Pin, Timer
from umqtt.robust import MQTTClient
import uasyncio as asyncio
# Wi-Fi 配置
ssid = 'Bili-Net'
password = '0932388283'
# MQTT 配置
mqtt_server = '192.168.0.172'
mqtt_port = 11883
mqtt_topic = 'myTopic'
client_id = 'MQTT-message_client'
# 全局变量
hostnames = {} # 记录每个 Hostname 的数据,包括行号、最后更新时间和接收的数据
max_hosts = 16 # 最大 Hostname 数量
line_number = 0 # 当前行号
led = Pin(2, Pin.OUT) # GPIO2 作为 LED 指示灯
# 连接 Wi-Fi
def connect_wifi():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('Connecting to network...')
wlan.connect(ssid, password)
while not wlan.isconnected():
time.sleep(1)
ip_address = wlan.ifconfig()[0]
print('Network connected:', ip_address)
return ip_address
# MQTT 客户端设置
def mqtt_connect():
client = MQTTClient(client_id, mqtt_server, port=mqtt_port)
client.set_callback(mqtt_callback)
client.connect()
client.subscribe(mqtt_topic)
print('Connected to %s MQTT broker, subscribed to %s topic' % (mqtt_server, mqtt_topic))
return client
# MQTT 消息回调函数
def mqtt_callback(topic, msg):
global line_number
try:
data = json.loads(msg.decode('utf-8'))
hostname = data.get('hostname', '')
current_time = time.time()
if hostname in hostnames:
# 更新现有的 Hostname 数据
hostnames[hostname]['data'] = data
hostnames[hostname]['last_update'] = current_time
elif len(hostnames) < max_hosts:
# 新增 Hostname 数据
hostnames[hostname] = {
'line': line_number + 1, # 行号从1开始
'data': data,
'last_update': current_time
}
line_number += 1
else:
print("Maximum number of hostnames reached.")
except Exception as e:
print("Failed to parse JSON data:", e)
# LED 呼吸灯控制
def led_breath(t):
led.on()
time.sleep(1)
led.off()
# 启动 LED 指示灯
def start_led():
timer = Timer(-1)
timer.init(period=4000, mode=Timer.PERIODIC, callback=led_breath)
# 网页内容
def web_page():
html = """
<!DOCTYPE html>
<html>
<head>
<title>MQTT Viewer</title>
<style>
.red {color: red;}
.black {color: black;}
</style>
<script>
setInterval(function() {
fetch('/data').then(function(response) {
return response.json();
}).then(function(data) {
var container = document.getElementById('messages');
container.innerHTML = '';
data.forEach(function(msg) {
var div = document.createElement('div');
div.className = msg.color;
div.innerHTML = msg.message;
container.appendChild(div);
});
});
}, 5000);
</script>
</head>
<body>
<h1>MQTT Viewer</h1>
<div id="messages"></div>
</body>
</html>
"""
return html
# 处理 HTTP 请求
async def serve_client(reader, writer):
try:
request_line = await reader.readline()
# 跳过请求头
while await reader.readline() != b'\r\n':
pass
request = request_line.decode()
if 'GET /data' in request:
# 返回 JSON 数据
messages = []
current_time = time.time()
for hostname in hostnames:
data = hostnames[hostname]['data']
last_update = hostnames[hostname]['last_update']
line_num = hostnames[hostname]['line']
time_diff = current_time - last_update
color = 'red' if time_diff > 3600 else 'black'
message = "{}. ".format(line_num) # 添加行号
message += "{} ".format(data.get('hostname', ''))
message += "{} ".format(data.get('ip', ''))
message += "{} ".format(data.get('timestamp', ''))
for key, value in data.items():
if key not in ['hostname', 'ip', 'timestamp'] and value:
message += "{}:{} ".format(key, value)
messages.append({'message': message, 'color': color})
response = json.dumps(messages)
writer.write('HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n')
writer.write(response.encode())
else:
# 返回网页
response = web_page()
writer.write('HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n')
writer.write(response.encode())
await writer.drain()
await writer.aclose()
except Exception as e:
print('Error serving client:', e)
await writer.aclose()
# 启动 Web 服务器
async def start_server():
server = await asyncio.start_server(serve_client, '0.0.0.0', 80)
print('Web server started on port 80')
return server
# MQTT 消息处理任务
async def mqtt_task(client):
while True:
client.check_msg()
await asyncio.sleep_ms(100)
# 主程序
def main():
ip = connect_wifi()
start_led()
try:
client = mqtt_connect()
except OSError as e:
print('Failed to connect to MQTT broker. Reconnecting...')
time.sleep(5)
machine.reset()
loop = asyncio.get_event_loop()
# 启动 Web 服务器
server_coro = start_server()
server = loop.run_until_complete(server_coro)
# 启动 MQTT 任务
loop.create_task(mqtt_task(client))
try:
loop.run_forever()
except KeyboardInterrupt:
print('Stopping server...')
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
if __name__ == '__main__':
main()
在 Hostname 数据中存储行号
在 mqtt_callback 函数中,当新增一个 Hostname 时,给它分配一个行号 line,并将行号从 1 开始计数:
python
複製程式碼
hostnames[hostname] = {
'line': line_number + 1, # 行号从1开始
'data': data,
'last_update': current_time
}
在消息中添加行号
在 serve_client 函数中,构建每个消息时,添加对应的行号:
python
複製程式碼
line_num = hostnames[hostname]['line']
message = "{}. ".format(line_num) # 添加行号
message += "{} ".format(data.get('hostname', ''))
# 继续构建消息...
行号从 1 开始
为了使行号更直观,从 1 开始计数,而不是从 0。
请按照以下步骤测试修改后的代码:
上传新的 main.py
将修改后的代码上传到 ESP32。
重启 ESP32
通过硬件复位或软件重启来重启设备。
发布 MQTT 消息
确保有设备向 myTopic 主题发布包含 hostname 字段的 JSON 格式的消息。
查看网页
在浏览器中访问 ESP32 的 IP 地址。
现在,您应该在每一行的前面看到对应的行号。
行号的分配
行号是在新增 Hostname 时分配的,之后即使 Hostname 的数据更新,其行号也保持不变。
行号的顺序
行号的顺序取决于收到 Hostname 的顺序,新的 Hostname 会添加在最后一行。
最大行数
最大 Hostname 数量仍然是 16,如果超过这个数量,新收到的 Hostname 将被忽略。