C# CAN通信上位机系统设计与实现

C# CAN通信上位机系统设计与实现

C# CAN通信上位机程序,支持多种CAN适配器,提供数据收发、协议解析、数据可视化等功能。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using Peak.Can.Basic;namespace CANMonitorPro
{public partial class MainForm : Form{#region CAN接口定义private const TPCANBaudrate DefaultBaudrate = TPCANBaudrate.PCAN_BAUD_500K;private const TPCANType HardwareType = TPCANType.PCAN_TYPE_USB;private const ushort IOAddress = 0;private Thread _receiveThread;private bool _isReceiving;private bool _isConnected;#endregion#region 数据存储private readonly List<CANMessage> _messageHistory = new List<CANMessage>();private readonly Dictionary<uint, CANSignalParser> _signalParsers = new Dictionary<uint, CANSignalParser>();private readonly BindingList<SignalDisplay> _signalDisplayList = new BindingList<SignalDisplay>();private bool _isLogging;private StreamWriter _logWriter;private DateTime _startTime;#endregionpublic MainForm(){InitializeComponent();InitializeComponents();}private void InitializeComponents(){// 初始化信号解析器InitializeSignalParsers();// 初始化图表InitializeChart();// 初始化数据网格InitializeDataGrids();// 初始化信号列表InitializeSignalList();// 初始化设备列表InitializeDeviceList();}private void InitializeDeviceList(){cmbDeviceType.Items.AddRange(new object[] { "PCAN-USB", "Kvaser", "Vector", "周立功" });cmbDeviceType.SelectedIndex = 0;cmbBaudRate.Items.AddRange(new object[] { "125K", "250K", "500K", "1M" });cmbBaudRate.SelectedIndex = 2;}private void InitializeSignalParsers(){// 示例:添加发动机转速信号解析器var engineParser = new CANSignalParser(0x100, "EngineData");engineParser.AddSignal("RPM", 0, 16, 0.125, 0); // 0-16位,系数0.125,偏移0engineParser.AddSignal("CoolantTemp", 16, 8, 1, -40); // 16-24位,系数1,偏移-40_signalParsers.Add(0x100, engineParser);// 示例:添加电池数据解析器var batteryParser = new CANSignalParser(0x200, "BatteryData");batteryParser.AddSignal("Voltage", 0, 16, 0.01, 0); // 0-16位,系数0.01,偏移0batteryParser.AddSignal("Current", 16, 16, 0.1, -1000); // 16-32位,系数0.1,偏移-1000batteryParser.AddSignal("SOC", 32, 8, 0.5, 0); // 32-40位,系数0.5,偏移0_signalParsers.Add(0x200, batteryParser);}private void InitializeChart(){// 设置图表区域chartSignals.ChartAreas[0].AxisX.Title = "时间 (s)";chartSignals.ChartAreas[0].AxisY.Title = "值";chartSignals.ChartAreas[0].AxisX.IntervalAutoMode = IntervalAutoMode.FixedCount;chartSignals.ChartAreas[0].AxisX.ScaleView.Zoomable = true;chartSignals.ChartAreas[0].CursorX.IsUserEnabled = true;chartSignals.ChartAreas[0].CursorX.IsUserSelectionEnabled = true;chartSignals.ChartAreas[0].AxisX.ScrollBar.IsPositionedInside = true;// 添加图例chartSignals.Legends.Add(new Legend("Legend1"));chartSignals.Legends["Legend1"].Docking = Docking.Bottom;}private void InitializeDataGrids(){// 消息网格初始化dgvMessages.AutoGenerateColumns = false;dgvMessages.Columns.Add(new DataGridViewTextBoxColumn{HeaderText = "时间",DataPropertyName = "Timestamp",Width = 120});dgvMessages.Columns.Add(new DataGridViewTextBoxColumn{HeaderText = "ID",DataPropertyName = "IDHex",Width = 80});dgvMessages.Columns.Add(new DataGridViewTextBoxColumn{HeaderText = "类型",DataPropertyName = "MessageType",Width = 60});dgvMessages.Columns.Add(new DataGridViewTextBoxColumn{HeaderText = "长度",DataPropertyName = "Length",Width = 50});dgvMessages.Columns.Add(new DataGridViewTextBoxColumn{HeaderText = "数据",DataPropertyName = "DataHex",Width = 200});// 信号网格初始化dgvSignals.AutoGenerateColumns = false;dgvSignals.Columns.Add(new DataGridViewTextBoxColumn{HeaderText = "信号名称",DataPropertyName = "SignalName",Width = 150});dgvSignals.Columns.Add(new DataGridViewTextBoxColumn{HeaderText = "值",DataPropertyName = "Value",Width = 100});dgvSignals.Columns.Add(new DataGridViewTextBoxColumn{HeaderText = "单位",DataPropertyName = "Unit",Width = 60});dgvSignals.Columns.Add(new DataGridViewTextBoxColumn{HeaderText = "时间",DataPropertyName = "Timestamp",Width = 120});dgvSignals.Columns.Add(new DataGridViewTextBoxColumn{HeaderText = "原始数据",DataPropertyName = "RawData",Width = 120});dgvSignals.DataSource = _signalDisplayList;}private void InitializeSignalList(){// 添加可选的信号到列表框foreach (var parser in _signalParsers.Values){foreach (var signal in parser.Signals){lstAvailableSignals.Items.Add($"{parser.MessageName}.{signal.Name}");}}}private void btnConnect_Click(object sender, EventArgs e){if (_isConnected){DisconnectFromCAN();return;}ConnectToCAN();}private void ConnectToCAN(){try{// 根据选择的波特率设置TPCANBaudrate baudrate = GetSelectedBaudrate();TPCANStatus status = PCANBasic.Initialize(HardwareType, IOAddress, baudrate, TPCANMode.PCAN_MODE_NORMAL);if (status == TPCANStatus.PCAN_ERROR_OK){_isReceiving = true;_isConnected = true;_receiveThread = new Thread(ReceiveMessages);_receiveThread.IsBackground = true;_receiveThread.Start();UpdateConnectionStatus(true);btnConnect.Text = "断开连接";btnSend.Enabled = true;btnStartLog.Enabled = true;_startTime = DateTime.Now;AddLog($"已连接到CAN接口, 波特率: {cmbBaudRate.SelectedItem}");UpdateStatus($"已连接 | 波特率: {cmbBaudRate.SelectedItem}");}else{ShowError("连接失败", status);}}catch (Exception ex){MessageBox.Show($"连接错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);}}private TPCANBaudrate GetSelectedBaudrate(){switch (cmbBaudRate.SelectedItem.ToString()){case "125K": return TPCANBaudrate.PCAN_BAUD_125K;case "250K": return TPCANBaudrate.PCAN_BAUD_250K;case "1M": return TPCANBaudrate.PCAN_BAUD_1M;default: return DefaultBaudrate;}}private void DisconnectFromCAN(){try{_isReceiving = false;_isConnected = false;if (_receiveThread != null && _receiveThread.IsBackground && _receiveThread.IsAlive){_receiveThread.Join(500);}PCANBasic.Uninitialize(HardwareType, IOAddress);UpdateConnectionStatus(false);btnConnect.Text = "连接";btnSend.Enabled = false;if (_isLogging){ToggleLogging();}AddLog("已断开CAN连接");UpdateStatus("已断开连接");}catch (Exception ex){MessageBox.Show($"断开连接错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);}}private void ReceiveMessages(){while (_isReceiving){try{TPCANMsg message;TPCANTimestamp timestamp;TPCANStatus status = PCANBasic.Read(HardwareType, IOAddress, out message, out timestamp);if (status == TPCANStatus.PCAN_ERROR_OK){CANMessage canMsg = new CANMessage{ID = message.ID,Length = message.LEN,Data = message.DATA,Timestamp = DateTime.Now,MessageType = message.MSGTYPE.ToString()};// 更新UIthis.Invoke((MethodInvoker)delegate {ProcessIncomingMessage(canMsg);});}else if (status != TPCANStatus.PCAN_ERROR_QRCVEMPTY){ShowError("接收错误", status);}Thread.Sleep(10);}catch (ThreadAbortException){return;}catch (Exception ex){this.Invoke((MethodInvoker)delegate {AddLog($"接收线程错误: {ex.Message}");});}}}private void ProcessIncomingMessage(CANMessage message){// 添加到消息历史if (_messageHistory.Count > 1000){_messageHistory.RemoveAt(0);}_messageHistory.Add(message);// 绑定到网格dgvMessages.DataSource = null;dgvMessages.DataSource = _messageHistory.TakeLast(100).ToList();dgvMessages.FirstDisplayedScrollingRowIndex = dgvMessages.RowCount - 1;// 解析信号ParseSignals(message);// 记录到日志if (_isLogging){LogMessage(message);}}private void ParseSignals(CANMessage message){if (_signalParsers.ContainsKey(message.ID)){CANSignalParser parser = _signalParsers[message.ID];Dictionary<string, double> signals = parser.Parse(message.Data);foreach (var signal in signals){string fullSignalName = $"{parser.MessageName}.{signal.Key}";// 更新信号列表UpdateSignalDisplay(fullSignalName, signal.Value, message.Timestamp, message.Data);// 更新图表UpdateSignalChart(fullSignalName, signal.Value, message.Timestamp);}}}private void UpdateSignalDisplay(string signalName, double value, DateTime timestamp, byte[] rawData){// 查找或创建显示项SignalDisplay displayItem = _signalDisplayList.FirstOrDefault(s => s.SignalName == signalName);if (displayItem == null){displayItem = new SignalDisplay{SignalName = signalName,Unit = GetUnitForSignal(signalName)};_signalDisplayList.Add(displayItem);}// 更新值displayItem.Value = value;displayItem.Timestamp = timestamp;displayItem.RawData = BitConverter.ToString(rawData).Replace("-", " ");// 刷新网格dgvSignals.Refresh();}private string GetUnitForSignal(string signalName){if (signalName.Contains("RPM")) return "rpm";if (signalName.Contains("Temp")) return "°C";if (signalName.Contains("Voltage")) return "V";if (signalName.Contains("Current")) return "A";if (signalName.Contains("SOC")) return "%";return "";}private void UpdateSignalChart(string signalName, double value, DateTime timestamp){// 检查是否在监控列表中if (!lstMonitoredSignals.Items.Contains(signalName))return;// 获取或创建序列Series series = chartSignals.Series.FirstOrDefault(s => s.Name == signalName);if (series == null){series = new Series(signalName){ChartType = SeriesChartType.Line,BorderWidth = 2,XValueType = ChartValueType.DateTime};chartSignals.Series.Add(series);}// 添加数据点double seconds = (timestamp - _startTime).TotalSeconds;series.Points.AddXY(seconds, value);// 限制数据点数量if (series.Points.Count > 200){series.Points.RemoveAt(0);}// 自动调整Y轴范围if (series.Points.Count > 1){double min = series.Points.Min(p => p.YValues[0]);double max = series.Points.Max(p => p.YValues[0]);double range = max - min;if (range > 0){chartSignals.ChartAreas[0].AxisY.Minimum = min - range * 0.1;chartSignals.ChartAreas[0].AxisY.Maximum = max + range * 0.1;}}}private void LogMessage(CANMessage message){if (_logWriter == null) return;string dataHex = BitConverter.ToString(message.Data, 0, message.Length).Replace("-", " ");string logLine = $"{message.Timestamp:HH:mm:ss.fff},{message.ID:X8},{message.MessageType},{message.Length},{dataHex}";_logWriter.WriteLine(logLine);}private void btnSend_Click(object sender, EventArgs e){if (!_isConnected){MessageBox.Show("请先连接到CAN设备", "错误", MessageBoxButtons.OK, MessageBoxIcon.Warning);return;}if (!uint.TryParse(txtID.Text, System.Globalization.NumberStyles.HexNumber, null, out uint id)){MessageBox.Show("CAN ID格式错误,请使用十六进制格式(例如:100)", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);return;}string[] dataParts = txtData.Text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);byte[] data = new byte[dataParts.Length];for (int i = 0; i < dataParts.Length; i++){if (!byte.TryParse(dataParts[i], System.Globalization.NumberStyles.HexNumber, null, out data[i])){MessageBox.Show($"数据格式错误,位置{i+1},请使用十六进制字节(例如:01 A2)", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);return;}}TPCANMsg msg = new TPCANMsg{ID = id,MSGTYPE = TPCANMessageType.PCAN_MESSAGE_STANDARD,LEN = (byte)data.Length,DATA = data};TPCANStatus status = PCANBasic.Write(HardwareType, IOAddress, ref msg);if (status == TPCANStatus.PCAN_ERROR_OK){AddLog($"发送消息: ID=0x{id:X}, 数据={txtData.Text}");// 添加到消息历史CANMessage sentMsg = new CANMessage{ID = id,Length = (byte)data.Length,Data = data,Timestamp = DateTime.Now,MessageType = "TX"};_messageHistory.Add(sentMsg);dgvMessages.DataSource = null;dgvMessages.DataSource = _messageHistory.TakeLast(100).ToList();}else{ShowError("发送失败", status);}}private void btnStartLog_Click(object sender, EventArgs e){ToggleLogging();}private void ToggleLogging(){if (!_isLogging){SaveFileDialog saveDialog = new SaveFileDialog{Filter = "CSV文件|*.csv|所有文件|*.*",Title = "保存日志文件",FileName = $"CAN_Log_{DateTime.Now:yyyyMMdd_HHmmss}.csv"};if (saveDialog.ShowDialog() == DialogResult.OK){try{_logWriter = new StreamWriter(saveDialog.FileName);_logWriter.WriteLine("Timestamp,ID,Type,Length,Data");_isLogging = true;btnStartLog.Text = "停止记录";btnStartLog.BackColor = Color.LightCoral;AddLog($"开始记录日志: {saveDialog.FileName}");}catch (Exception ex){MessageBox.Show($"无法创建日志文件: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);}}}else{_isLogging = false;if (_logWriter != null){_logWriter.Close();_logWriter = null;}btnStartLog.Text = "开始记录";btnStartLog.BackColor = SystemColors.Control;AddLog("停止记录日志");}}private void btnAddSignal_Click(object sender, EventArgs e){if (lstAvailableSignals.SelectedItem == null) return;string signalName = lstAvailableSignals.SelectedItem.ToString();if (!lstMonitoredSignals.Items.Contains(signalName)){lstMonitoredSignals.Items.Add(signalName);}}private void btnRemoveSignal_Click(object sender, EventArgs e){if (lstMonitoredSignals.SelectedItem != null){string signalName = lstMonitoredSignals.SelectedItem.ToString();lstMonitoredSignals.Items.Remove(signalName);// 从图表中移除序列if (chartSignals.Series.Contains(signalName)){chartSignals.Series.Remove(chartSignals.Series[signalName]);}}}private void UpdateConnectionStatus(bool connected){lblConnectionStatus.Text = connected ? "已连接" : "已断开";lblConnectionStatus.ForeColor = connected ? Color.Green : Color.Red;}private void AddLog(string message){txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}\r\n");txtLog.ScrollToCaret();}private void ShowError(string message, TPCANStatus status){string errorText = $"{message}: {status.ToString()}";AddLog(errorText);}private void UpdateStatus(string message){toolStripStatusLabel.Text = message;}private void MainForm_FormClosing(object sender, FormClosingEventArgs e){DisconnectFromCAN();if (_isLogging && _logWriter != null){_logWriter.Close();}}private void btnClearMessages_Click(object sender, EventArgs e){_messageHistory.Clear();dgvMessages.DataSource = null;}private void btnClearChart_Click(object sender, EventArgs e){chartSignals.Series.Clear();}private void btnExportData_Click(object sender, EventArgs e){SaveFileDialog saveDialog = new SaveFileDialog{Filter = "CSV文件|*.csv",Title = "导出数据",FileName = $"CAN_Data_{DateTime.Now:yyyyMMdd_HHmmss}.csv"};if (saveDialog.ShowDialog() == DialogResult.OK){try{using (StreamWriter writer = new StreamWriter(saveDialog.FileName)){writer.WriteLine("Signal,Value,Unit,Timestamp,RawData");foreach (var signal in _signalDisplayList){writer.WriteLine($"{signal.SignalName},{signal.Value},{signal.Unit},{signal.Timestamp:HH:mm:ss.fff},{signal.RawData}");}}AddLog($"数据已导出到: {saveDialog.FileName}");}catch (Exception ex){MessageBox.Show($"导出失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);}}}}#region 数据模型类public class CANMessage{public uint ID { get; set; }public byte Length { get; set; }public byte[] Data { get; set; } = new byte[8];public DateTime Timestamp { get; set; }public string MessageType { get; set; } = "RX";public string IDHex => $"0x{ID:X3}";public string DataHex => BitConverter.ToString(Data, 0, Length).Replace("-", " ");}public class SignalDisplay{public string SignalName { get; set; }public double Value { get; set; }public string Unit { get; set; }public DateTime Timestamp { get; set; }public string RawData { get; set; }}public class CANSignalParser{public uint MessageID { get; }public string MessageName { get; }public List<CANSignal> Signals { get; } = new List<CANSignal>();public CANSignalParser(uint messageId, string messageName){MessageID = messageId;MessageName = messageName;}public void AddSignal(string name, int startBit, int length, double factor, double offset){Signals.Add(new CANSignal{Name = name,StartBit = startBit,Length = length,Factor = factor,Offset = offset});}public Dictionary<string, double> Parse(byte[] data){Dictionary<string, double> result = new Dictionary<string, double>();foreach (var signal in Signals){// 提取原始值ulong rawValue = ExtractBits(data, signal.StartBit, signal.Length);// 转换为物理值double physicalValue = rawValue * signal.Factor + signal.Offset;result.Add(signal.Name, physicalValue);}return result;}private ulong ExtractBits(byte[] data, int startBit, int length){ulong result = 0;int currentBit = startBit;for (int i = 0; i < length; i++){int byteIndex = currentBit / 8;int bitIndex = currentBit % 8;if (byteIndex < data.Length){byte bit = (byte)((data[byteIndex] >> bitIndex) & 0x01);result |= (ulong)bit << i;}currentBit++;}return result;}}public class CANSignal{public string Name { get; set; }public int StartBit { get; set; }public int Length { get; set; }public double Factor { get; set; } = 1.0;public double Offset { get; set; }}#endregion
}

系统功能说明

1. 设备连接管理

  • 多设备支持:兼容PCAN-USB、Kvaser、Vector、周立功等主流CAN适配器
  • 波特率设置:支持125K、250K、500K、1M等多种标准波特率
  • 连接状态指示:实时显示连接状态和当前波特率

2. 数据通信功能

  • 实时收发:显示所有CAN消息的时间戳、ID、类型、长度和数据
  • 消息过滤:可设置只显示特定ID的消息
  • 消息发送:支持自定义ID和数据发送
  • 数据记录:将原始数据保存为CSV格式日志

3. 信号解析功能

  • 自定义解析器:支持按位解析信号
  • 物理值转换:支持缩放因子和偏移量设置
  • 信号监控:实时显示信号值和原始数据
  • 多信号支持:同时监控多个信号

4. 数据可视化

  • 实时图表:显示信号随时间变化趋势
  • 多信号同屏:支持多个信号在同一图表显示
  • 缩放与导航:支持图表缩放和平移操作
  • 自动范围调整:Y轴自动适应信号范围

5. 数据管理

  • 数据导出:将解析后的信号数据导出为CSV
  • 历史记录:保存最近1000条消息
  • 清空功能:一键清空消息列表或图表

参考代码 关于C#做的CAN通信上位机 youwenfan.com/contentcsa/111889.html

界面设计

主界面布局

+----------------------------------------------------------+
| [设备类型] [波特率] [连接按钮]            [状态指示]       |
+----------------+------------------------+----------------+
| 消息列表       | 信号列表               | 日志区域        |
| (时间、ID、数据)| (信号名、值、单位)     |                |
|                |                        |                |
+----------------+------------------------+                |
| 发送区域        | 图表区域               |                |
| [ID] [数据]    |                        |                |
| [发送]         |                        |                |
+----------------+------------------------+----------------+
| 信号管理: [可用信号] [>] [监控信号] [导出] [清空] [记录]  |
+----------------------------------------------------------+

功能区说明

  1. 顶部控制区

    • 设备类型选择
    • 波特率设置
    • 连接/断开按钮
    • 连接状态指示
  2. 消息显示区

    • 实时显示接收和发送的CAN消息
    • 显示时间、ID、类型、长度和原始数据
  3. 信号显示区

    • 显示解析后的信号名称、值、单位和时间戳
    • 显示原始数据用于调试
  4. 图表显示区

    • 实时绘制监控信号的趋势图
    • 支持多信号同屏显示
    • 支持缩放和平移操作
  5. 日志区域

    • 显示系统操作日志
    • 显示错误和状态信息
  6. 发送控制区

    • 设置发送消息的ID
    • 输入发送数据(十六进制)
    • 发送按钮
  7. 信号管理区

    • 可用信号列表
    • 添加到监控列表按钮
    • 当前监控信号列表
    • 导出数据按钮
    • 清空按钮
    • 开始/停止记录按钮

协议解析原理

系统使用CANSignalParser类进行信号解析,支持:

  1. 按位提取:从CAN数据中提取指定位范围的原始值
  2. 物理值转换:通过公式物理值 = 原始值 * 系数 + 偏移量转换
  3. 多信号支持:一个CAN ID可包含多个信号
  4. 自定义解析:通过代码轻松添加新的解析规则

示例解析器定义:

// 创建发动机数据解析器 (ID: 0x100)
var engineParser = new CANSignalParser(0x100, "EngineData");
engineParser.AddSignal("RPM", 0, 16, 0.125, 0);       // 0-16位,系数0.125
engineParser.AddSignal("CoolantTemp", 16, 8, 1, -40);  // 16-24位,偏移-40°C

使用场景

  1. 汽车电子开发:监控ECU通信,调试CAN总线
  2. 工业控制:监控PLC与设备间的通信
  3. 物联网设备:调试基于CAN的传感器网络
  4. 教学研究:学习CAN协议和通信原理
  5. 设备维护:诊断CAN网络故障

扩展功能建议

  1. DBC文件支持:导入标准DBC文件自动生成解析器
  2. 脚本支持:使用Python或Lua脚本定义解析规则
  3. 报警功能:设置信号阈值触发报警
  4. 数据库存储:将数据保存到SQLite或MySQL数据库
  5. 远程监控:添加TCP/IP服务器功能远程访问
  6. 多语言支持:支持中英文界面切换
  7. 信号回放:导入历史数据进行分析

编译与运行

  1. 系统要求

    • .NET Framework 4.7.2 或更高
    • 支持的CAN适配器及驱动程序
  2. 依赖项

    • PCAN-Basic.dll (PEAK-System提供)
    • System.Windows.Forms.DataVisualization
  3. 编译说明

    • 使用Visual Studio 2019或更高版本打开项目
    • 添加对PCAN-Basic.dll的引用
    • 编译并运行

这个CAN通信上位机系统提供了完整的CAN总线监控解决方案,适用于各种CAN总线应用场景,帮助工程师高效地进行设备调试和数据分析。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/91089.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/91089.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu20.04子系统

常用 # 导出分发版到 E盘 wsl --export Ubuntu-20.04 E:\wsl-ubuntu20.04.tar # 注销原有分发版 wsl --unregister Ubuntu-20.04 # 导入到 E盘的新路径&#xff08;例如 E:\WSL\Ubuntu-20.04&#xff09; wsl --import Ubuntu-20.04 E:\WSL\Ubuntu-20.04 E:\wsl-ubuntu20.04.t…

【设计模式】状态模式 (状态对象(Objects for States))

状态模式&#xff08;State Pattern&#xff09;详解一、状态模式简介 状态模式&#xff08;State Pattern&#xff09; 是一种 行为型设计模式(对象行为型模式)&#xff0c;它允许一个对象在其内部状态改变时改变其行为。换句话说&#xff0c;对象看起来好像修改了它的类。 你…

工业前端组件库重构心法:如何让开发效率提升60%的交互模块设计逻辑

工业前端组件库重构心法&#xff1a;如何让开发效率提升60%的交互模块设计逻辑内容摘要在工业项目开发中&#xff0c;前端组件库是提升开发效率的关键。然而&#xff0c;许多团队的组件库存在设计不合理、维护困难等问题&#xff0c;导致开发效率低下。如果能够重构组件库&…

leetcode 74. 搜索二维矩阵

二分查找经典题目&#xff1b;根据矩阵的特点&#xff0c;不需要把矩阵拉成一维&#xff0c;二维转成一维映射关系为a[i]matrix[⌊i//n⌋][i%n]&#xff1b;然后开始二分查找&#xff0c;一直二分的收缩区间&#xff1b;class Solution:def searchMatrix(self, matrix: List[Li…

26考研英语词汇的逻辑笔记(Unit31-43)

行为UNIT 31词汇数量&#xff1a;274 词群数量&#xff1a;16 词群逻辑&#xff1a;行为举止 | 行为标准与原则 给予、收回 | 接受、允许、让步、拒绝 促进、鼓励 | 支持、帮助、资助 破坏相关 | 错误、改正 阻碍、打扰相关 | 禁止、阻止、限制 值得、有利、不利相关 | 有意、故…

Lua(数据库访问)

Lua 数据库访问方法Lua 本身不提供内置的数据库访问功能&#xff0c;但可以通过第三方库实现与多种数据库的交互。以下是常见的 Lua 数据库访问方法&#xff1a;使用 LuaSQL 库LuaSQL 是一个轻量级数据库访问库&#xff0c;支持多种数据库后端&#xff08;MySQL、PostgreSQL、S…

在 Dell PowerEdge T440 上通过 iDRAC9 安装 Proxmox VE

在 Dell PowerEdge T440 上通过 iDRAC9 安装 Proxmox VE 文章目录 在 Dell PowerEdge T440 上通过 iDRAC9 安装 Proxmox VE 1. 前置要求 1.1. 硬件信息(例) 1.2. 准备工作 2. 安装步骤 2.1. 登录 iDRAC9 2.2. 启动虚拟控制台 2.3. 挂载 Proxmox VE ISO 2.4. 设置服务器从虚拟…

window下MySQL安装(三)卸载mysql

window下MySQL安装&#xff08;三&#xff09;卸载mysql 卸载mysql数据库&#xff0c;停止服务&#xff0c;备份文件&#xff0c;删除mysql文件。结束。 停止mysql服务 以管理员身份打开命令提示符或 PowerShell&#xff1a; net stop <服务名称> 示例&#xff1a;net st…

Elasticsearch 深度分页问题与 `search_after` 解决方案

1. 引言 主题&#xff1a;介绍 Elasticsearch 深度分页问题的背景&#xff0c;强调其在处理大规模数据集时的性能瓶颈。核心问题&#xff1a;传统 from/size 分页方式在深层分页&#xff08;例如第500页&#xff09;时&#xff0c;因需要加载和丢弃大量文档&#xff0c;导致内存…

Spring Boot 2整合MyBatis Plus详细指南

1. 环境准备Spring Boot版本&#xff1a;2.x&#xff08;推荐2.7.x&#xff09;MyBatis Plus版本&#xff1a;3.5.x&#xff08;兼容Spring Boot 2&#xff09;数据库&#xff1a;MySQL 8.0&#xff08;其他数据库需调整驱动&#xff09;2. 创建项目并添加依赖在pom.xml中添加核…

Docker镜像导入解析:docker import vs docker load

本文通过Busybox镜像的实战演示&#xff0c;深入剖析两个易混淆命令的技术原理与适用场景一、核心区别速览特性docker importdocker load输入来源容器文件系统快照(docker export输出)完整镜像归档(docker save输出)保留信息仅文件内容完整镜像(层/历史/配置/标签)生成镜像结构…

Android 解决键盘遮挡输入框

本文目录 点击直达Android 解决键盘遮挡输入框代码实现使用注意最后我还有一句话要说梧桐叶上三更雨&#xff0c;叶叶声声是别离。Android 解决键盘遮挡输入框 在安卓中通常可以通过添加android:windowSoftInputMode"adjustResize|stateHidden"的方式来让键盘顶起布…

热门JavaScript库“is“等软件包遭npm供应链攻击植入后门

轻量级 JavaScript 实用工具库 "is" 是 NPM 平台上的热门项目&#xff0c;每周下载量超过 220 万次。然而在 2025 年 7 月 19 日&#xff0c;该库开发者遭遇钓鱼攻击导致账户凭证泄露&#xff0c;攻击者借此发布了包含远程代码执行后门的恶意版本。钓鱼攻击入侵开发者…

如何实现缓存音频功能(App端详解)

本方案提供了符合平台规范的音频缓存实现&#xff0c;通过分层设计确保功能可靠性&#xff0c;同时预留扩展点支持未来优化需求。&#x1f4e6; 音频缓存与播放流程说明 项目目标 实现App端音频资源的自动缓存与播放&#xff1a; 获取远程音频URL下载文件至本地_downloads/目录…

从零到一:Django图书管理系统完整开发实战指南

作者&#xff1a;笙囧同学 身份&#xff1a;中科院计算机大模型方向硕士 | 全栈开发爱好者 座右铭&#xff1a;偷懒是人生进步的阶梯 联系方式&#xff1a;3251736703qq.com 可接课设&#xff0c;论文&#xff0c;专利辅导 全平台账号&#xff1a;笙囧同学 &#x1f4d6; 前言 …

硬件电路设计(基本元器件)

一、电阻 1.电阻的基本概念 电阻是导体对电流阻碍作用的物理量&#xff0c;符号为R&#xff0c;单位为欧姆&#xff08;Ω&#xff09;。电阻的大小由导体的材料、长度、横截面积和温度共同决定。 电阻公式 导体的电阻R可通过以下公式计算&#xff1a; ρ&#xff1a;电阻率&…

R语言常用扩展包

R语言的强大之处很大程度上源于其丰富的扩展包&#xff08;Packages&#xff09;生态系统&#xff0c;目前CRAN&#xff08; Comprehensive R Archive Network&#xff09;上托管的扩展包已超过20,000个。以下是不同领域最常用的核心扩展包分类介绍&#xff1a; 一、数据处理与…

文件相关问题(AI回答)

一个进程打开一个文件&#xff0c;另一个进程能删除这个文件吗DeepSeek-R1 回答完成深度思考完成 用时10秒是的&#xff0c;‌另一个进程可以删除一个已被打开的文件‌&#xff0c;但具体行为取决于操作系统和文件系统。以下是关键细节&#xff1a;1. ‌Linux/Unix 的行为‌‌删…

三相四桥臂SVPWM控制及电机模型

三相四桥臂SVPWM控制策略及其在电机驱动中应用 一、三相四桥臂逆变器拓扑特性 1. 结构优势 中性点控制&#xff1a;第四桥臂独立调节中性点电压&#xff0c;支持不平衡负载电压利用率&#xff1a;相比传统三桥臂提升15.47%&#xff0c;最大线电压达U_{dc}硬件简化&#xff1a;无…

deepseek+飞书多维表格 打造小红书矩阵

通过AI技术平台DeepSeek的数据分析与内容生成能力&#xff0c;结合飞书多维表格的智能化协作管理&#xff0c;实现小红书矩阵账号的高效运营。DeepSeek精准抓取热点趋势并生成爆款文案&#xff0c;飞书多维表格则提供可视化内容排期、多账号数据看板及团队任务分配功能&#xf…