commit 47c339f1b3da5e3272d2b77e11c7372900a16d58 Author: copper Date: Mon Jun 16 17:02:50 2025 +0800 端口数据读取与转发程序 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b361c46 --- /dev/null +++ b/.gitignore @@ -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) + diff --git a/Form1.Designer.cs b/Form1.Designer.cs new file mode 100644 index 0000000..1a1e333 --- /dev/null +++ b/Form1.Designer.cs @@ -0,0 +1,354 @@ +namespace sscom_sender; + +partial class Form1 +{ + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; +} diff --git a/Form1.cs b/Form1.cs new file mode 100644 index 0000000..b147745 --- /dev/null +++ b/Form1.cs @@ -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 dataQueue = new ConcurrentQueue(); + + // 线程控制 + 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 logQueue = new ConcurrentQueue(); + + 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(); + + // 批量获取日志 + 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; } + } +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..6fd076c --- /dev/null +++ b/Program.cs @@ -0,0 +1,16 @@ +namespace sscom_sender; + +static class Program +{ + /// + /// The main entry point for the application. + /// + [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()); + } +} \ No newline at end of file diff --git a/sscom-sender.csproj b/sscom-sender.csproj new file mode 100644 index 0000000..1b91352 --- /dev/null +++ b/sscom-sender.csproj @@ -0,0 +1,16 @@ + + + + WinExe + net9.0-windows + sscom_sender + enable + true + enable + + + + + + + \ No newline at end of file diff --git a/sscom-sender.csproj.user b/sscom-sender.csproj.user new file mode 100644 index 0000000..f61322e --- /dev/null +++ b/sscom-sender.csproj.user @@ -0,0 +1,8 @@ + + + + + Form + + + diff --git a/sscom-sender.sln b/sscom-sender.sln new file mode 100644 index 0000000..90e7ab7 --- /dev/null +++ b/sscom-sender.sln @@ -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