端口数据读取与转发程序

This commit is contained in:
copper 2025-06-16 17:02:50 +08:00
commit 47c339f1b3
7 changed files with 941 additions and 0 deletions

62
.gitignore vendored Normal file
View File

@ -0,0 +1,62 @@
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
# Created by https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,dotnetcore
# Edit at https://www.toptal.com/developers/gitignore?templates=windows,visualstudiocode,dotnetcore
### DotnetCore ###
# .NET Core build folders
bin/
obj/
# Common node modules locations
/node_modules
/wwwroot/node_modules
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,dotnetcore
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)

354
Form1.Designer.cs generated Normal file
View File

@ -0,0 +1,354 @@
namespace sscom_sender;
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.cmbPort1 = new System.Windows.Forms.ComboBox();
this.cmbBaud1 = new System.Windows.Forms.ComboBox();
this.btnConnect1 = new System.Windows.Forms.Button();
this.lblPort1 = new System.Windows.Forms.Label();
this.lblBaud1 = new System.Windows.Forms.Label();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.cmbPort2 = new System.Windows.Forms.ComboBox();
this.cmbBaud2 = new System.Windows.Forms.ComboBox();
this.btnConnect2 = new System.Windows.Forms.Button();
this.lblPort2 = new System.Windows.Forms.Label();
this.lblBaud2 = new System.Windows.Forms.Label();
this.groupBox3 = new System.Windows.Forms.GroupBox();
this.txtUrl = new System.Windows.Forms.TextBox();
this.lblUrl = new System.Windows.Forms.Label();
this.btnStartUpload = new System.Windows.Forms.Button();
this.btnStopUpload = new System.Windows.Forms.Button();
this.groupBox4 = new System.Windows.Forms.GroupBox();
this.txtLog = new System.Windows.Forms.TextBox();
this.btnClearLog = new System.Windows.Forms.Button();
this.btnSaveLog = new System.Windows.Forms.Button();
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel();
this.groupBox1.SuspendLayout();
this.groupBox2.SuspendLayout();
this.groupBox3.SuspendLayout();
this.groupBox4.SuspendLayout();
this.statusStrip1.SuspendLayout();
this.SuspendLayout();
//
// groupBox1
//
this.groupBox1.Controls.Add(this.lblBaud1);
this.groupBox1.Controls.Add(this.lblPort1);
this.groupBox1.Controls.Add(this.btnConnect1);
this.groupBox1.Controls.Add(this.cmbBaud1);
this.groupBox1.Controls.Add(this.cmbPort1);
this.groupBox1.Location = new System.Drawing.Point(12, 12);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(200, 120);
this.groupBox1.TabIndex = 0;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "串口1";
//
// cmbPort1
//
this.cmbPort1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbPort1.FormattingEnabled = true;
this.cmbPort1.Location = new System.Drawing.Point(60, 25);
this.cmbPort1.Name = "cmbPort1";
this.cmbPort1.Size = new System.Drawing.Size(121, 23);
this.cmbPort1.TabIndex = 0;
//
// cmbBaud1
//
this.cmbBaud1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbBaud1.FormattingEnabled = true;
this.cmbBaud1.Items.AddRange(new object[] {
"9600",
"19200",
"38400",
"57600",
"115200"});
this.cmbBaud1.Location = new System.Drawing.Point(60, 54);
this.cmbBaud1.Name = "cmbBaud1";
this.cmbBaud1.Size = new System.Drawing.Size(121, 23);
this.cmbBaud1.TabIndex = 1;
//
// btnConnect1
//
this.btnConnect1.Location = new System.Drawing.Point(60, 83);
this.btnConnect1.Name = "btnConnect1";
this.btnConnect1.Size = new System.Drawing.Size(75, 23);
this.btnConnect1.TabIndex = 2;
this.btnConnect1.Text = "连接";
this.btnConnect1.UseVisualStyleBackColor = true;
this.btnConnect1.Click += new System.EventHandler(this.btnConnect1_Click);
//
// lblPort1
//
this.lblPort1.AutoSize = true;
this.lblPort1.Location = new System.Drawing.Point(15, 28);
this.lblPort1.Name = "lblPort1";
this.lblPort1.Size = new System.Drawing.Size(32, 15);
this.lblPort1.TabIndex = 3;
this.lblPort1.Text = "端口";
//
// lblBaud1
//
this.lblBaud1.AutoSize = true;
this.lblBaud1.Location = new System.Drawing.Point(15, 57);
this.lblBaud1.Name = "lblBaud1";
this.lblBaud1.Size = new System.Drawing.Size(44, 15);
this.lblBaud1.TabIndex = 4;
this.lblBaud1.Text = "波特率";
//
// groupBox2
//
this.groupBox2.Controls.Add(this.lblBaud2);
this.groupBox2.Controls.Add(this.lblPort2);
this.groupBox2.Controls.Add(this.btnConnect2);
this.groupBox2.Controls.Add(this.cmbBaud2);
this.groupBox2.Controls.Add(this.cmbPort2);
this.groupBox2.Location = new System.Drawing.Point(230, 12);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(200, 120);
this.groupBox2.TabIndex = 1;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "串口2";
//
// cmbPort2
//
this.cmbPort2.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbPort2.FormattingEnabled = true;
this.cmbPort2.Location = new System.Drawing.Point(60, 25);
this.cmbPort2.Name = "cmbPort2";
this.cmbPort2.Size = new System.Drawing.Size(121, 23);
this.cmbPort2.TabIndex = 0;
//
// cmbBaud2
//
this.cmbBaud2.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbBaud2.FormattingEnabled = true;
this.cmbBaud2.Items.AddRange(new object[] {
"9600",
"19200",
"38400",
"57600",
"115200"});
this.cmbBaud2.Location = new System.Drawing.Point(60, 54);
this.cmbBaud2.Name = "cmbBaud2";
this.cmbBaud2.Size = new System.Drawing.Size(121, 23);
this.cmbBaud2.TabIndex = 1;
//
// btnConnect2
//
this.btnConnect2.Location = new System.Drawing.Point(60, 83);
this.btnConnect2.Name = "btnConnect2";
this.btnConnect2.Size = new System.Drawing.Size(75, 23);
this.btnConnect2.TabIndex = 2;
this.btnConnect2.Text = "连接";
this.btnConnect2.UseVisualStyleBackColor = true;
this.btnConnect2.Click += new System.EventHandler(this.btnConnect2_Click);
//
// lblPort2
//
this.lblPort2.AutoSize = true;
this.lblPort2.Location = new System.Drawing.Point(15, 28);
this.lblPort2.Name = "lblPort2";
this.lblPort2.Size = new System.Drawing.Size(32, 15);
this.lblPort2.TabIndex = 3;
this.lblPort2.Text = "端口";
//
// lblBaud2
//
this.lblBaud2.AutoSize = true;
this.lblBaud2.Location = new System.Drawing.Point(15, 57);
this.lblBaud2.Name = "lblBaud2";
this.lblBaud2.Size = new System.Drawing.Size(44, 15);
this.lblBaud2.TabIndex = 4;
this.lblBaud2.Text = "波特率";
//
// groupBox3
//
this.groupBox3.Controls.Add(this.btnStopUpload);
this.groupBox3.Controls.Add(this.btnStartUpload);
this.groupBox3.Controls.Add(this.lblUrl);
this.groupBox3.Controls.Add(this.txtUrl);
this.groupBox3.Location = new System.Drawing.Point(450, 12);
this.groupBox3.Name = "groupBox3";
this.groupBox3.Size = new System.Drawing.Size(320, 120);
this.groupBox3.TabIndex = 2;
this.groupBox3.TabStop = false;
this.groupBox3.Text = "数据上传";
//
// txtUrl
//
this.txtUrl.Location = new System.Drawing.Point(15, 40);
this.txtUrl.Name = "txtUrl";
this.txtUrl.Size = new System.Drawing.Size(290, 23);
this.txtUrl.TabIndex = 0;
this.txtUrl.Text = "http://localhost:8080/api/data";
//
// lblUrl
//
this.lblUrl.AutoSize = true;
this.lblUrl.Location = new System.Drawing.Point(15, 22);
this.lblUrl.Name = "lblUrl";
this.lblUrl.Size = new System.Drawing.Size(56, 15);
this.lblUrl.TabIndex = 1;
this.lblUrl.Text = "上传地址";
//
// btnStartUpload
//
this.btnStartUpload.Location = new System.Drawing.Point(15, 80);
this.btnStartUpload.Name = "btnStartUpload";
this.btnStartUpload.Size = new System.Drawing.Size(75, 23);
this.btnStartUpload.TabIndex = 2;
this.btnStartUpload.Text = "开始上传";
this.btnStartUpload.UseVisualStyleBackColor = true;
this.btnStartUpload.Click += new System.EventHandler(this.btnStartUpload_Click);
//
// btnStopUpload
//
this.btnStopUpload.Location = new System.Drawing.Point(110, 80);
this.btnStopUpload.Name = "btnStopUpload";
this.btnStopUpload.Size = new System.Drawing.Size(75, 23);
this.btnStopUpload.TabIndex = 3;
this.btnStopUpload.Text = "停止上传";
this.btnStopUpload.UseVisualStyleBackColor = true;
this.btnStopUpload.Click += new System.EventHandler(this.btnStopUpload_Click);
//
// groupBox4
//
this.groupBox4.Controls.Add(this.btnSaveLog);
this.groupBox4.Controls.Add(this.btnClearLog);
this.groupBox4.Controls.Add(this.txtLog);
this.groupBox4.Location = new System.Drawing.Point(12, 150);
this.groupBox4.Name = "groupBox4";
this.groupBox4.Size = new System.Drawing.Size(758, 270);
this.groupBox4.TabIndex = 3;
this.groupBox4.TabStop = false;
this.groupBox4.Text = "数据日志";
//
// txtLog
//
this.txtLog.Location = new System.Drawing.Point(15, 22);
this.txtLog.Multiline = true;
this.txtLog.Name = "txtLog";
this.txtLog.ReadOnly = true;
this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.txtLog.Size = new System.Drawing.Size(728, 210);
this.txtLog.TabIndex = 0;
//
// btnClearLog
//
this.btnClearLog.Location = new System.Drawing.Point(15, 238);
this.btnClearLog.Name = "btnClearLog";
this.btnClearLog.Size = new System.Drawing.Size(75, 23);
this.btnClearLog.TabIndex = 1;
this.btnClearLog.Text = "清空日志";
this.btnClearLog.UseVisualStyleBackColor = true;
this.btnClearLog.Click += new System.EventHandler(this.btnClearLog_Click);
//
// btnSaveLog
//
this.btnSaveLog.Location = new System.Drawing.Point(110, 238);
this.btnSaveLog.Name = "btnSaveLog";
this.btnSaveLog.Size = new System.Drawing.Size(75, 23);
this.btnSaveLog.TabIndex = 2;
this.btnSaveLog.Text = "保存日志";
this.btnSaveLog.UseVisualStyleBackColor = true;
this.btnSaveLog.Click += new System.EventHandler(this.btnSaveLog_Click);
//
// statusStrip1
//
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.toolStripStatusLabel1});
this.statusStrip1.Location = new System.Drawing.Point(0, 428);
this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Size = new System.Drawing.Size(784, 22);
this.statusStrip1.TabIndex = 4;
this.statusStrip1.Text = "statusStrip1";
//
// toolStripStatusLabel1
//
this.toolStripStatusLabel1.Name = "toolStripStatusLabel1";
this.toolStripStatusLabel1.Size = new System.Drawing.Size(32, 17);
this.toolStripStatusLabel1.Text = "就绪";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(784, 450);
this.Controls.Add(this.statusStrip1);
this.Controls.Add(this.groupBox4);
this.Controls.Add(this.groupBox3);
this.Controls.Add(this.groupBox2);
this.Controls.Add(this.groupBox1);
this.Name = "Form1";
this.Text = "串口数据采集与上传系统";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1_FormClosing);
this.Load += new System.EventHandler(this.Form1_Load);
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.groupBox2.ResumeLayout(false);
this.groupBox2.PerformLayout();
this.groupBox3.ResumeLayout(false);
this.groupBox3.PerformLayout();
this.groupBox4.ResumeLayout(false);
this.groupBox4.PerformLayout();
this.statusStrip1.ResumeLayout(false);
this.statusStrip1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.ComboBox cmbPort1;
private System.Windows.Forms.ComboBox cmbBaud1;
private System.Windows.Forms.Button btnConnect1;
private System.Windows.Forms.Label lblPort1;
private System.Windows.Forms.Label lblBaud1;
private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.ComboBox cmbPort2;
private System.Windows.Forms.ComboBox cmbBaud2;
private System.Windows.Forms.Button btnConnect2;
private System.Windows.Forms.Label lblPort2;
private System.Windows.Forms.Label lblBaud2;
private System.Windows.Forms.GroupBox groupBox3;
private System.Windows.Forms.TextBox txtUrl;
private System.Windows.Forms.Label lblUrl;
private System.Windows.Forms.Button btnStartUpload;
private System.Windows.Forms.Button btnStopUpload;
private System.Windows.Forms.GroupBox groupBox4;
private System.Windows.Forms.TextBox txtLog;
private System.Windows.Forms.Button btnClearLog;
private System.Windows.Forms.Button btnSaveLog;
private System.Windows.Forms.StatusStrip statusStrip1;
private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1;
}

461
Form1.cs Normal file
View File

@ -0,0 +1,461 @@
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; }
}
}

16
Program.cs Normal file
View File

@ -0,0 +1,16 @@
namespace sscom_sender;
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
}
}

16
sscom-sender.csproj Normal file
View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<RootNamespace>sscom_sender</RootNamespace>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.IO.Ports" Version="8.0.0" />
</ItemGroup>
</Project>

8
sscom-sender.csproj.user Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Update="Form1.cs">
<SubType>Form</SubType>
</Compile>
</ItemGroup>
</Project>

24
sscom-sender.sln Normal file
View File

@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sscom-sender", "sscom-sender.csproj", "{F03C6C53-8989-99E6-D747-EC03449AD20F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F03C6C53-8989-99E6-D747-EC03449AD20F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F03C6C53-8989-99E6-D747-EC03449AD20F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F03C6C53-8989-99E6-D747-EC03449AD20F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F03C6C53-8989-99E6-D747-EC03449AD20F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {707867E8-3EF5-4E83-A851-9053BE677857}
EndGlobalSection
EndGlobal