328 lines
13 KiB
C#
328 lines
13 KiB
C#
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)
|
||
{
|
||
|
||
}
|
||
}
|
||
} |