462 lines
15 KiB
C#
462 lines
15 KiB
C#
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; }
|
||
}
|
||
}
|