Files
2026-03-31 15:40:09 +08:00

328 lines
13 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using MetroFramework.Forms;
using MQTTnet;
using MQTTnet.Core;
using MQTTnet.Core.Client;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MqttClientWin
{
public partial class FmMqttClient : MetroForm
{
private MqttClient mqttClient = null;
public FmMqttClient()
{
InitializeComponent();
txtSubTopic.Text = "/Uplink";
txtSubService.Text = "mqtt.usot.top";
txtReceiveMessage.AppendText("Copyright © 2025 路怀帅. All rights reserved." + Environment.NewLine);
}
// <summary>
// 服务器连接成功
// </summary>
// <param name="sender"></param>
// <param name="e"></param>
private void MqttClient_Connected(object sender, EventArgs e)
{
Invoke((new Action(() =>
{
txtReceiveMessage.AppendText("已连接到MQTT服务器" + Environment.NewLine);
})));
}
// <summary>
// 断开服务器连接
// </summary>
// <param name="sender"></param>
// <param name="e"></param>
private void MqttClient_Disconnected(object sender, EventArgs e)
{
Invoke((new Action(() =>
{
txtReceiveMessage.AppendText("已断开MQTT连接" + Environment.NewLine);
})));
}
// <summary>
// 接收到消息
// </summary>
// <param name="sender"></param>
// <param name="e"></param>
private void MqttClient_ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e)
{
Invoke((Action)(() =>
{
// 原始消息输出到 txtReceiveMessage保持不变
string payloadStr = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
txtReceiveMessage.AppendText($">> {payloadStr}{Environment.NewLine}");
txtMessage.Clear();
// 结构化解析结果输出到 txtMessage
txtMessage.AppendText($"📩 来自主题 {e.ApplicationMessage.Topic} 的消息 [{DateTime.Now:HH:mm:ss}]{Environment.NewLine}");
try
{
using (var doc = JsonDocument.Parse(payloadStr))
{
var root = doc.RootElement;
// 提取和显示其他信息...
string deviceName = root.TryGetProperty("deviceName", out var deviceNameProp) ? deviceNameProp.GetString() : "N/A";
string devEUI = root.TryGetProperty("devEUI", out var devEUIProp) ? devEUIProp.GetString() : "N/A";
string gatewayTimeStr = root.TryGetProperty("gatewayTime", out var gatewayTimeProp) ? gatewayTimeProp.GetString() : "N/A";
string localTime = "N/A";
if (!string.IsNullOrEmpty(gatewayTimeStr) && gatewayTimeStr != "N/A")
{
if (DateTimeOffset.TryParse(gatewayTimeStr, out var dto))
{
localTime = dto.LocalDateTime.ToString("yyyy-MM-dd HH:mm:ss");
}
}
// 提取 rxInfo[0] 中的 rssi 和 loRaSNR
string rssi = "N/A";
string loRaSNR = "N/A";
if (root.TryGetProperty("rxInfo", out var rxInfoArray) && rxInfoArray.ValueKind == JsonValueKind.Array && rxInfoArray.GetArrayLength() > 0)
{
var gatewayInfo = rxInfoArray[0];
rssi = gatewayInfo.TryGetProperty("rssi", out var rssiProp) ? rssiProp.GetInt32().ToString() : "N/A";
loRaSNR = gatewayInfo.TryGetProperty("loRaSNR", out var snrProp) ? snrProp.GetDouble().ToString("F1") : "N/A";
}
// 显示提取的字段
txtMessage.AppendText($"🔧 设备名称: {deviceName}{Environment.NewLine}");
txtMessage.AppendText($"📡 设备EUI: {devEUI}{Environment.NewLine}");
txtMessage.AppendText($"🕒 网关时间: {localTime}{Environment.NewLine}");
txtMessage.AppendText($"📶 信号强度 (RSSI): {rssi} dBm{Environment.NewLine}");
txtMessage.AppendText($"⚡ 信噪比 (LoRaSNR): {loRaSNR} dB{Environment.NewLine}");
// 处理解码的 data 字段
if (root.TryGetProperty("data", out var dataElement))
{
string base64Data = dataElement.GetString();
if (!string.IsNullOrEmpty(base64Data))
{
try
{
byte[] decodedBytes = Convert.FromBase64String(base64Data);
string decodedJson = Encoding.UTF8.GetString(decodedBytes);
using (var innerDoc = JsonDocument.Parse(decodedJson))
{
var innerRoot = innerDoc.RootElement;
// 更新按钮状态
UpdateButtonState(innerRoot, "T", btn_t);
UpdateButtonState(innerRoot, "R", btn_r);
UpdateButtonState(innerRoot, "C", btn_c);
// 对于E字段由于其对应多个按钮且条件不同单独处理
if (innerRoot.TryGetProperty("E", out var eProp))
{
string eStr = eProp.GetString();
if (int.TryParse(eStr, out int eValue))
{
btn_s.BackColor = (eValue == 3) ? Color.Lime : Color.Gainsboro;
btn_q.BackColor = (eValue == 4) ? Color.Lime : Color.Gainsboro;
btn_e.BackColor = (eValue == 5) ? Color.Lime : Color.Gainsboro;
btn_m.BackColor = (eValue == 6) ? Color.Lime : Color.Gainsboro;
btn_p.BackColor = (eValue == 7) ? Color.Lime : Color.Gainsboro;
btn_n.BackColor = (eValue == 8) ? Color.Lime : Color.Gainsboro;
}
else
{
btn_s.BackColor = Color.Gainsboro;
btn_q.BackColor = Color.Gainsboro;
btn_e.BackColor = Color.Gainsboro;
btn_m.BackColor = Color.Gainsboro;
btn_p.BackColor = Color.Gainsboro;
btn_n.BackColor = Color.Gainsboro;
}
}
string prettyJson = innerRoot.ToString(); // 自动格式化
txtMessage.AppendText($"📦 Data解码:{prettyJson}{Environment.NewLine}");
}
}
catch (FormatException)
{
txtMessage.AppendText($"❌ Base64 解码失败: 数据格式错误{Environment.NewLine}");
}
catch (Exception ex)
{
txtMessage.AppendText($"❌ 解码异常: {ex.Message}{Environment.NewLine}");
}
}
else
{
txtMessage.AppendText($"📦 Base64 data: (空){Environment.NewLine}");
}
}
else
{
txtMessage.AppendText($"📦 Base64 data: (未找到字段){Environment.NewLine}");
}
}
}
catch (Exception ex)
{
txtMessage.AppendText($"❌ JSON 解析失败: {ex.Message}{Environment.NewLine}{Environment.NewLine}");
}
}));
}
// 通用按钮状态更新方法
private void UpdateButtonState(JsonElement root, string fieldName, Button button)
{
if (root.TryGetProperty(fieldName, out var fieldProp))
{
string valueStr = fieldProp.GetString(); // 先作为字符串获取
if (int.TryParse(valueStr, out int value))
{
bool isOn = (value == 1);
button.BackColor = isOn ? Color.Lime : Color.Gainsboro;
}
}
}
// <summary>
// 订阅消息
// </summary>
// <param name="sender"></param>
// <param name="e"></param>
private async void BtnSubscribe_ClickAsync(object sender, EventArgs e)
{
string broker = txtSubService.Text.Trim();
string topic = txtSubTopic.Text.Trim();
if (string.IsNullOrEmpty(broker))
{
MessageBox.Show("MQTT 服务器地址不能为空!");
return;
}
if (string.IsNullOrEmpty(topic))
{
MessageBox.Show("订阅主题不能为空!");
return;
}
// 断开旧连接(如果已连接)
if (mqttClient != null && mqttClient.IsConnected)
{
await mqttClient.DisconnectAsync();
}
// 初始化客户端(如果尚未创建)
if (mqttClient == null)
{
mqttClient = new MqttClientFactory().CreateMqttClient() as MqttClient;
mqttClient.ApplicationMessageReceived += MqttClient_ApplicationMessageReceived;
mqttClient.Connected += MqttClient_Connected;
mqttClient.Disconnected += MqttClient_Disconnected;
}
try
{
var options = new MqttClientTcpOptions
{
Server = broker,
ClientId = Guid.NewGuid().ToString().Substring(0, 5),
UserName = "",
Password = "", // 注意:某些版本需要 char[]
CleanSession = true,
// 可选设置端口默认1883
// Port = 1883
};
// 连接服务器
await mqttClient.ConnectAsync(options);
// 连接成功后订阅主题
var topicFilter = new TopicFilter(topic, MqttQualityOfServiceLevel.AtMostOnce);
await mqttClient.SubscribeAsync(topicFilter);
Invoke(new Action(() =>
{
txtReceiveMessage.AppendText($"[订阅] 已连接到 {broker} 并订阅主题: [{topic}]{Environment.NewLine}");
btnSubscribe.Enabled = false; // 防止重复点击
}));
}
catch (Exception ex)
{
Invoke(new Action(() =>
{
txtReceiveMessage.AppendText($"[错误] 连接或订阅失败: {ex.Message}{Environment.NewLine}");
MessageBox.Show($"连接失败: {ex.Message}");
}));
}
}
private void FmMqttClient_Load(object sender, EventArgs e)
{
}
private void txtPubTopic_TextChanged(object sender, EventArgs e)
{
}
private void txtSubService_TextChanged(object sender, EventArgs e)
{
}
private void label4_Click(object sender, EventArgs e)
{
}
private void label5_Click(object sender, EventArgs e)
{
}
private void txtReceiveMessage_TextChanged(object sender, EventArgs e)
{
}
private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
}
private void button2_Click(object sender, EventArgs e)
{
}
private void button2_Click_1(object sender, EventArgs e)
{
}
}
}