ssinfo-sender/Form1.cs
2025-06-16 17:02:50 +08:00

462 lines
15 KiB
C#
Raw 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 System.Collections.Concurrent;
using System.IO.Ports;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
namespace sscom_sender
{
public partial class Form1 : Form
{
// 串口对象
private SerialPort? serialPort1;
private SerialPort? serialPort2;
// 数据缓冲区
private StringBuilder buffer1 = new StringBuilder();
private StringBuilder buffer2 = new StringBuilder();
// 数据队列
private ConcurrentQueue<DataPacket> dataQueue = new ConcurrentQueue<DataPacket>();
// 线程控制
private CancellationTokenSource? uploadCancellationTokenSource;
private CancellationTokenSource? logCancellationTokenSource;
private Task? uploadTask;
private Task? logTask;
// HTTP客户端
private readonly HttpClient httpClient = new HttpClient();
// 日志文件路径
private readonly string logFilePath = Path.Combine(Application.StartupPath, "data_log.txt");
// 线程安全的日志队列
private ConcurrentQueue<string> logQueue = new ConcurrentQueue<string>();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// 初始化串口列表
RefreshPortList();
// 设置默认波特率
cmbBaud1.SelectedIndex = 4; // 115200
cmbBaud2.SelectedIndex = 4; // 115200
// 启动日志线程
StartLogThread();
UpdateStatus("系统已启动");
}
private void RefreshPortList()
{
string[] ports = SerialPort.GetPortNames();
cmbPort1.Items.Clear();
cmbPort2.Items.Clear();
foreach (string port in ports)
{
cmbPort1.Items.Add(port);
cmbPort2.Items.Add(port);
}
if (ports.Length > 0)
{
cmbPort1.SelectedIndex = 0;
if (ports.Length > 1)
cmbPort2.SelectedIndex = 1;
else
cmbPort2.SelectedIndex = 0;
}
}
private void btnConnect1_Click(object sender, EventArgs e)
{
if (serialPort1?.IsOpen == true)
{
// 断开连接
serialPort1.Close();
btnConnect1.Text = "连接";
UpdateStatus("串口1已断开");
}
else
{
// 建立连接
try
{
serialPort1 = new SerialPort(
cmbPort1.Text,
int.Parse(cmbBaud1.Text),
Parity.None,
8,
StopBits.One);
serialPort1.DataReceived += SerialPort1_DataReceived;
serialPort1.Open();
btnConnect1.Text = "断开";
UpdateStatus($"串口1已连接: {cmbPort1.Text}");
}
catch (Exception ex)
{
MessageBox.Show($"串口1连接失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private void btnConnect2_Click(object sender, EventArgs e)
{
if (serialPort2?.IsOpen == true)
{
// 断开连接
serialPort2.Close();
btnConnect2.Text = "连接";
UpdateStatus("串口2已断开");
}
else
{
// 建立连接
try
{
serialPort2 = new SerialPort(
cmbPort2.Text,
int.Parse(cmbBaud2.Text),
Parity.None,
8,
StopBits.One);
serialPort2.DataReceived += SerialPort2_DataReceived;
serialPort2.Open();
btnConnect2.Text = "断开";
UpdateStatus($"串口2已连接: {cmbPort2.Text}");
}
catch (Exception ex)
{
MessageBox.Show($"串口2连接失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private void SerialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (serialPort1?.IsOpen == true)
{
string data = serialPort1.ReadExisting();
ProcessSerialData(data, 1, buffer1);
}
}
private void SerialPort2_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (serialPort2?.IsOpen == true)
{
string data = serialPort2.ReadExisting();
ProcessSerialData(data, 2, buffer2);
}
}
private void ProcessSerialData(string data, int portNumber, StringBuilder buffer)
{
// 将数据添加到缓冲区
buffer.Append(data);
// 查找完整的数据包(以换行符结束)
string bufferContent = buffer.ToString();
string[] lines = bufferContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
if (lines.Length > 0)
{
// 处理完整的行
for (int i = 0; i < lines.Length - 1; i++)
{
ProcessCompleteDataLine(lines[i], portNumber);
}
// 检查最后一行是否完整
if (bufferContent.EndsWith("\r") || bufferContent.EndsWith("\n"))
{
ProcessCompleteDataLine(lines[lines.Length - 1], portNumber);
buffer.Clear();
}
else
{
// 保留不完整的最后一行
buffer.Clear();
buffer.Append(lines[lines.Length - 1]);
}
}
}
private void ProcessCompleteDataLine(string line, int portNumber)
{
if (string.IsNullOrWhiteSpace(line)) return;
var dataPacket = new DataPacket
{
PortNumber = portNumber,
RawData = line,
Timestamp = DateTime.Now
};
// 如果是串口1且数据格式匹配BESTPOS提取经纬度
if (portNumber == 1 && line.Contains("BESTPOSA"))
{
var coordinates = ExtractCoordinates(line);
if (coordinates != null)
{
dataPacket.Latitude = coordinates.Value.Latitude;
dataPacket.Longitude = coordinates.Value.Longitude;
}
}
// 添加到队列
dataQueue.Enqueue(dataPacket);
// 添加到日志显示
string logMessage = $"[{dataPacket.Timestamp:yyyy-MM-dd HH:mm:ss}] 串口{portNumber}: {line}";
if (dataPacket.Latitude.HasValue && dataPacket.Longitude.HasValue)
{
logMessage += $" [经度: {dataPacket.Longitude:F8}, 纬度: {dataPacket.Latitude:F8}]";
}
AddLogMessage(logMessage);
}
private (double Latitude, double Longitude)? ExtractCoordinates(string data)
{
try
{
// BESTPOS数据格式解析
// #BESTPOSA,COM1,14394,98.0,UNKNOWN,1,1699.000,1700908,2,18;INSUFFICIENT_OBS,NONE,纬度,经度,高度,undulation,datum,lat_std,lon_std,hgt_std,stn_id,diff_age,sol_age,#obs,#L1,#L2,reserved,ext_sol_stat,galileo_beidou_sig_mask,gps_glonass_sig_mask*checksum
string[] parts = data.Split(',');
if (parts.Length >= 14)
{
if (double.TryParse(parts[11], out double latitude) &&
double.TryParse(parts[12], out double longitude))
{
return (latitude, longitude);
}
}
}
catch (Exception ex)
{
AddLogMessage($"坐标解析错误: {ex.Message}");
}
return null;
}
private void btnStartUpload_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtUrl.Text))
{
MessageBox.Show("请输入上传地址", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
uploadCancellationTokenSource = new CancellationTokenSource();
uploadTask = Task.Run(() => UploadWorker(uploadCancellationTokenSource.Token));
btnStartUpload.Enabled = false;
btnStopUpload.Enabled = true;
UpdateStatus("数据上传已启动");
}
private void btnStopUpload_Click(object sender, EventArgs e)
{
uploadCancellationTokenSource?.Cancel();
btnStartUpload.Enabled = true;
btnStopUpload.Enabled = false;
UpdateStatus("数据上传已停止");
}
private async Task UploadWorker(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
if (dataQueue.TryDequeue(out DataPacket? packet))
{
await UploadData(packet);
}
else
{
await Task.Delay(100, cancellationToken); // 等待100ms
}
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
Invoke(() => AddLogMessage($"上传错误: {ex.Message}"));
await Task.Delay(5000, cancellationToken); // 错误后等待5秒
}
}
}
private async Task UploadData(DataPacket packet)
{
try
{
var jsonData = JsonSerializer.Serialize(packet);
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(txtUrl.Text, content);
if (response.IsSuccessStatusCode)
{
Invoke(() => AddLogMessage($"数据上传成功: 串口{packet.PortNumber}"));
}
else
{
Invoke(() => AddLogMessage($"数据上传失败: {response.StatusCode} - 串口{packet.PortNumber}"));
}
}
catch (Exception ex)
{
Invoke(() => AddLogMessage($"上传异常: {ex.Message}"));
}
}
private void StartLogThread()
{
logCancellationTokenSource = new CancellationTokenSource();
logTask = Task.Run(() => LogWorker(logCancellationTokenSource.Token));
}
private async Task LogWorker(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
var logsToWrite = new List<string>();
// 批量获取日志
while (logQueue.TryDequeue(out string? logMessage))
{
logsToWrite.Add(logMessage);
if (logsToWrite.Count >= 10) break; // 批量写入最多10条
}
if (logsToWrite.Count > 0)
{
await File.AppendAllLinesAsync(logFilePath, logsToWrite, cancellationToken);
}
else
{
await Task.Delay(1000, cancellationToken); // 等待1秒
}
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
// 日志写入错误,避免无限循环
Console.WriteLine($"日志写入错误: {ex.Message}");
await Task.Delay(5000, cancellationToken);
}
}
}
private void AddLogMessage(string message)
{
// 添加到界面显示
if (InvokeRequired)
{
Invoke(() => AddLogMessage(message));
return;
}
txtLog.AppendText(message + Environment.NewLine);
txtLog.SelectionStart = txtLog.Text.Length;
txtLog.ScrollToCaret();
// 添加到日志文件队列
logQueue.Enqueue(message);
// 限制界面显示的行数
if (txtLog.Lines.Length > 1000)
{
var lines = txtLog.Lines.Skip(500).ToArray();
txtLog.Text = string.Join(Environment.NewLine, lines);
}
}
private void UpdateStatus(string message)
{
if (InvokeRequired)
{
Invoke(() => UpdateStatus(message));
return;
}
toolStripStatusLabel1.Text = message;
AddLogMessage($"[系统] {message}");
}
private void btnClearLog_Click(object sender, EventArgs e)
{
txtLog.Clear();
}
private void btnSaveLog_Click(object sender, EventArgs e)
{
try
{
using (var saveDialog = new SaveFileDialog())
{
saveDialog.Filter = "文本文件|*.txt|所有文件|*.*";
saveDialog.FileName = $"log_{DateTime.Now:yyyyMMdd_HHmmss}.txt";
if (saveDialog.ShowDialog() == DialogResult.OK)
{
File.WriteAllText(saveDialog.FileName, txtLog.Text);
MessageBox.Show("日志保存成功", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
catch (Exception ex)
{
MessageBox.Show($"保存失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
// 停止所有线程
uploadCancellationTokenSource?.Cancel();
logCancellationTokenSource?.Cancel();
// 关闭串口
serialPort1?.Close();
serialPort2?.Close();
// 释放资源
httpClient.Dispose();
}
}
// 数据包类
public class DataPacket
{
public int PortNumber { get; set; }
public string RawData { get; set; } = string.Empty;
public DateTime Timestamp { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }
}
}