电脑蓝牙OTA对接BLE蓝牙升级程序C#

一、开发前景

        最近一直想实现一款在电脑上就能用的OTA的蓝牙升级程序,网上的都是用串口去实现,太不方便了,经过一段时间的开发,终于能够直接调用Windows电脑自带的蓝牙接口去实现,开发用具用的VS2019,.Net Framework4.8。

        我采用的是Ymodem发送方式,UI做得很简陋,只实现了功能,另外受蓝牙MTU设置和波特率等参数配置的限制,发送速度较慢,只有128字节,需要自行摸索如何改良传输速度

二、效果展示

三、代码展示

接下来开始展示代码吧:

 1、Form1.Designer.cs


namespace TCM_OTA_APP
{


    partial class Form1
    {
        /// <summary>
        /// 必需的设计器变量。
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// 清理所有正在使用的资源。
        /// </summary>
        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows 窗体设计器生成的代码

        /// <summary>
        /// 设计器支持所需的方法 - 不要修改
        /// 使用代码编辑器修改此方法的内容。
        /// </summary>
        private void InitializeComponent()
        {
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
            this.btnSearch = new System.Windows.Forms.Button();
            this.btnConnect = new System.Windows.Forms.Button();
            this.listboxBleDevice = new System.Windows.Forms.ListBox();
            this.tableLayoutPanelTop = new System.Windows.Forms.TableLayoutPanel();
            this.btnOpteron = new System.Windows.Forms.Button();
            this.btnLoader = new System.Windows.Forms.Button();
            this.btnSend = new System.Windows.Forms.Button();
            this.linkLabelEdit1 = new ReaLTaiizor.Controls.LinkLabelEdit();
            this.richTextBox1 = new System.Windows.Forms.RichTextBox();
            this.tableLayoutPanelTop.SuspendLayout();
            this.SuspendLayout();
            // 
            // btnSearch
            // 
            this.btnSearch.Dock = System.Windows.Forms.DockStyle.Fill;
            this.btnSearch.Location = new System.Drawing.Point(3, 4);
            this.btnSearch.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
            this.btnSearch.Name = "btnSearch";
            this.btnSearch.Size = new System.Drawing.Size(113, 58);
            this.btnSearch.TabIndex = 0;
            this.btnSearch.Text = "搜索";
            this.btnSearch.UseVisualStyleBackColor = true;
            this.btnSearch.Click += new System.EventHandler(this.btnSearch_Click);
            // 
            // btnConnect
            // 
            this.btnConnect.Dock = System.Windows.Forms.DockStyle.Fill;
            this.btnConnect.Location = new System.Drawing.Point(122, 4);
            this.btnConnect.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
            this.btnConnect.Name = "btnConnect";
            this.btnConnect.Size = new System.Drawing.Size(122, 58);
            this.btnConnect.TabIndex = 1;
            this.btnConnect.Text = "匹配";
            this.btnConnect.UseVisualStyleBackColor = true;
            this.btnConnect.Click += new System.EventHandler(this.btnConnect_Click);
            // 
            // listboxBleDevice
            // 
            this.listboxBleDevice.Dock = System.Windows.Forms.DockStyle.Top;
            this.listboxBleDevice.FormattingEnabled = true;
            this.listboxBleDevice.ItemHeight = 18;
            this.listboxBleDevice.Location = new System.Drawing.Point(0, 199);
            this.listboxBleDevice.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
            this.listboxBleDevice.Name = "listboxBleDevice";
            this.listboxBleDevice.Size = new System.Drawing.Size(1473, 220);
            this.listboxBleDevice.TabIndex = 1;
            // 
            // tableLayoutPanelTop
            // 
            this.tableLayoutPanelTop.ColumnCount = 4;
            this.tableLayoutPanelTop.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 119F));
            this.tableLayoutPanelTop.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
            this.tableLayoutPanelTop.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 118F));
            this.tableLayoutPanelTop.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 1108F));
            this.tableLayoutPanelTop.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F));
            this.tableLayoutPanelTop.Controls.Add(this.btnOpteron, 0, 3);
            this.tableLayoutPanelTop.Controls.Add(this.btnLoader, 0, 3);
            this.tableLayoutPanelTop.Controls.Add(this.btnSend, 0, 3);
            this.tableLayoutPanelTop.Controls.Add(this.btnSearch, 0, 0);
            this.tableLayoutPanelTop.Controls.Add(this.btnConnect, 1, 0);
            this.tableLayoutPanelTop.Controls.Add(this.linkLabelEdit1, 3, 3);
            this.tableLayoutPanelTop.Dock = System.Windows.Forms.DockStyle.Top;
            this.tableLayoutPanelTop.Location = new System.Drawing.Point(0, 0);
            this.tableLayoutPanelTop.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
            this.tableLayoutPanelTop.Name = "tableLayoutPanelTop";
            this.tableLayoutPanelTop.RowCount = 4;
            this.tableLayoutPanelTop.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 66F));
            this.tableLayoutPanelTop.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 32F));
            this.tableLayoutPanelTop.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 29F));
            this.tableLayoutPanelTop.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 47F));
            this.tableLayoutPanelTop.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F));
            this.tableLayoutPanelTop.Size = new System.Drawing.Size(1473, 199);
            this.tableLayoutPanelTop.TabIndex = 0;
            // 
            // btnOpteron
            // 
            this.btnOpteron.Dock = System.Windows.Forms.DockStyle.Fill;
            this.btnOpteron.Location = new System.Drawing.Point(122, 131);
            this.btnOpteron.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
            this.btnOpteron.Name = "btnOpteron";
            this.btnOpteron.Size = new System.Drawing.Size(122, 64);
            this.btnOpteron.TabIndex = 16;
            this.btnOpteron.Text = "连接蓝牙";
            this.btnOpteron.UseVisualStyleBackColor = true;
            this.btnOpteron.Click += new System.EventHandler(this.btnOpteron_Click);
            // 
            // btnLoader
            // 
            this.btnLoader.Dock = System.Windows.Forms.DockStyle.Fill;
            this.btnLoader.Location = new System.Drawing.Point(250, 131);
            this.btnLoader.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
            this.btnLoader.Name = "btnLoader";
            this.btnLoader.Size = new System.Drawing.Size(112, 64);
            this.btnLoader.TabIndex = 15;
            this.btnLoader.Text = "选取文件";
            this.btnLoader.UseVisualStyleBackColor = true;
            this.btnLoader.Click += new System.EventHandler(this.btnLoader_Click);
            // 
            // btnSend
            // 
            this.btnSend.Dock = System.Windows.Forms.DockStyle.Fill;
            this.btnSend.Location = new System.Drawing.Point(3, 131);
            this.btnSend.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
            this.btnSend.Name = "btnSend";
            this.btnSend.Size = new System.Drawing.Size(113, 64);
            this.btnSend.TabIndex = 13;
            this.btnSend.Text = "发送";
            this.btnSend.UseVisualStyleBackColor = true;
            this.btnSend.Click += new System.EventHandler(this.btnSend_Click);
            // 
            // linkLabelEdit1
            // 
            this.linkLabelEdit1.ActiveLinkColor = System.Drawing.Color.FromArgb(((int)(((byte)(153)))), ((int)(((byte)(34)))), ((int)(((byte)(34)))));
            this.linkLabelEdit1.BackColor = System.Drawing.Color.Transparent;
            this.linkLabelEdit1.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F);
            this.linkLabelEdit1.LinkBehavior = System.Windows.Forms.LinkBehavior.NeverUnderline;
            this.linkLabelEdit1.LinkColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(34)))), ((int)(((byte)(37)))));
            this.linkLabelEdit1.Location = new System.Drawing.Point(368, 127);
            this.linkLabelEdit1.Name = "linkLabelEdit1";
            this.linkLabelEdit1.Size = new System.Drawing.Size(896, 38);
            this.linkLabelEdit1.TabIndex = 17;
            this.linkLabelEdit1.TabStop = true;
            this.linkLabelEdit1.Text = "请选择bin文件";
            this.linkLabelEdit1.TextAlign = System.Drawing.ContentAlignment.BottomLeft;
            this.linkLabelEdit1.VisitedLinkColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(34)))), ((int)(((byte)(37)))));
            // 
            // richTextBox1
            // 
            this.richTextBox1.Location = new System.Drawing.Point(3, 433);
            this.richTextBox1.Name = "richTextBox1";
            this.richTextBox1.ReadOnly = true;
            this.richTextBox1.Size = new System.Drawing.Size(1470, 400);
            this.richTextBox1.TabIndex = 2;
            this.richTextBox1.Text = "";
           
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(1473, 830);
            this.Controls.Add(this.richTextBox1);
            this.Controls.Add(this.listboxBleDevice);
            this.Controls.Add(this.tableLayoutPanelTop);
            this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
            this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
            this.Name = "Form1";
            this.Text = "OTA升级";
            this.tableLayoutPanelTop.ResumeLayout(false);
            this.ResumeLayout(false);

        }

        #endregion

        // 组件声明
        private System.Windows.Forms.Button btnSearch;
        private System.Windows.Forms.Button btnConnect;
        private System.Windows.Forms.ListBox listboxBleDevice;
        private System.Windows.Forms.TableLayoutPanel tableLayoutPanelTop;
        private System.Windows.Forms.RichTextBox richTextBox1;
        private System.Windows.Forms.Button btnSend;
        private ReaLTaiizor.Controls.LinkLabelEdit linkLabelEdit1;
        private System.Windows.Forms.Button btnOpteron;
        private System.Windows.Forms.Button btnLoader;
    }
}

2、Form.cs

一定要改的地方:

(1)这个XXXX是蓝牙名称,我嫌搜到的蓝牙太多了,加了一层过滤,不影响原先的搜索速度

        if (!string.IsNullOrEmpty(device.Name) && device.Name.Contains("XXXX"))

(2)// 固定 UUID(来自 蓝牙的 规格书),三个UUID,搜索一下蓝牙能够看得到的
        private readonly Guid SERVICE_UUID = new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
        private readonly Guid NOTIFY_UUID = new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
        private readonly Guid WRITE_UUID = new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;


namespace TCM_OTA_APP
{
    public partial class Form1 : Form
    {
        private bool isSearching = false;
        //private bool isReading = false;
        private List<Windows.Devices.Bluetooth.BluetoothLEDevice> foundDevices = new List<Windows.Devices.Bluetooth.BluetoothLEDevice>();
        private Dictionary<string, Windows.Devices.Bluetooth.BluetoothLEDevice> deviceDictionary = new Dictionary<string, Windows.Devices.Bluetooth.BluetoothLEDevice>();
        private YmodemSender _ymodemSender;
        private string _lastConnectedMac = null;

        private string _selectedFilePath = null;


        // 固定 UUID(来自 蓝牙的 规格书)
        private readonly Guid SERVICE_UUID = new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
        private readonly Guid NOTIFY_UUID = new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
        private readonly Guid WRITE_UUID = new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");


        public Form1()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;
            this.bleCore.MessAgeChanged += BleCore_MessAgeChanged;
            this.bleCore.DeviceWatcherChanged += BleCore_DeviceWatcherChanged;
            this.bleCore.GattDeviceServiceAdded += BleCore_GattDeviceServiceAdded;
            this.bleCore.CharacteristicAdded += BleCore_CharacteristicAdded;

            _ymodemSender = new YmodemSender(bleCore);
            _ymodemSender.ProgressChanged += Ymodem_ProgressChanged;
            _ymodemSender.LogMessage += Ymodem_LogMessage;
            _ymodemSender.ErrorOccurred += Ymodem_ErrorOccurred;

            this.Init();
        }


        // 异步线程
        public static void RunAsync(Action action)
        {
            ((Action)(delegate ()
            {
                action.Invoke();
            })).BeginInvoke(null, null);
        }

        BleCore bleCore = new BleCore();
        /// <summary>
        /// 存储检测到的设备
        /// </summary>
        List<Windows.Devices.Bluetooth.BluetoothLEDevice> DeviceList = new List<Windows.Devices.Bluetooth.BluetoothLEDevice>();

        /// <summary>
        /// 当前蓝牙服务列表
        /// </summary>
        List<Windows.Devices.Bluetooth.GenericAttributeProfile.GattDeviceService> GattDeviceServices = new List<Windows.Devices.Bluetooth.GenericAttributeProfile.GattDeviceService>();

        /// <summary>
        /// 当前蓝牙服务特征列表
        /// </summary>
        List<Windows.Devices.Bluetooth.GenericAttributeProfile.GattCharacteristic> GattCharacteristics = new List<Windows.Devices.Bluetooth.GenericAttributeProfile.GattCharacteristic>();


        private void AppendLog(string text)
        {
            if (richTextBox1.InvokeRequired)
            {
                richTextBox1.BeginInvoke(new Action(() => AppendLog(text)));
                return;
            }
            richTextBox1.AppendText(text + Environment.NewLine);
            richTextBox1.SelectionStart = richTextBox1.Text.Length;
            richTextBox1.ScrollToCaret();
        }

        private void AppendTextWithNewLine(string text) => AppendLog(text);

        private string GetDeviceMacAddress(Windows.Devices.Bluetooth.BluetoothLEDevice device)
        {
            try
            {
                byte[] bytes = BitConverter.GetBytes(device.BluetoothAddress);
                Array.Reverse(bytes);
                return BitConverter.ToString(bytes, 2, 6).Replace('-', ':').ToLower();
            }
            catch
            {
                return "Unknown";
            }
        }

        private void btnSearch_Click(object sender, EventArgs e)
        {
            if (this.btnSearch.Text == "搜索")
            {
                this.listboxBleDevice.Items.Clear();
                this.richTextBox1.Clear();
                this.DeviceList.Clear();
                this.deviceDictionary.Clear();
                foundDevices.Clear();
                isSearching = true;

                this.bleCore.StartBleDeviceWatcher();
                this.btnSearch.Text = "停止";
            }
            else
            {
                isSearching = false;
                this.bleCore.StopBleDeviceWatcher();
                this.btnSearch.Text = "搜索";

                // 搜索停止后,一次性显示所有匹配的设备
                foreach (var device in foundDevices)
                {
                    //XXXX 写你要升级的蓝牙名字,这样可以排除掉搜到的其他蓝牙
                    if (!string.IsNullOrEmpty(device.Name) && device.Name.Contains("XXXX"))
                    {
                        string macAddress = GetDeviceMacAddress(device);
                        string displayName = $"{device.Name} (MAC: {macAddress})";


                        if (!deviceDictionary.ContainsKey(macAddress))
                        {
                            this.listboxBleDevice.Items.Add(displayName);
                            deviceDictionary[macAddress] = device;
                            this.DeviceList.Add(device);
                            this.btnConnect.Enabled = true;

                            // 👇 添加调试信息
                            this.AppendTextWithNewLine($"添加设备: {displayName}, MAC: {macAddress}");
                        }
                    }
                }
                this.AppendTextWithNewLine($"搜索完成,发现 {deviceDictionary.Count} 个目标设备");
            }
        }

        private async void btnConnect_Click(object sender, EventArgs e)
        {
            if (null == this.listboxBleDevice.SelectedItem)
            {
                MessageBox.Show("请先选择一个蓝牙设备", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            string selectedDisplayName = this.listboxBleDevice.SelectedItem.ToString();
            this.AppendTextWithNewLine($"选中的设备: {selectedDisplayName}");

            // 提取 MAC 地址
            string macAddress = null;
            System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(@"([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})");
            var match = regex.Match(selectedDisplayName);
            if (match.Success)
            {
                macAddress = match.Value.ToLower();
                this.AppendTextWithNewLine($"提取的 MAC 地址: {macAddress}");
            }
            else
            {
                this.AppendTextWithNewLine("未能提取到 MAC 地址");
            }

            Windows.Devices.Bluetooth.BluetoothLEDevice bluetoothLEDevice = null;
            if (!string.IsNullOrEmpty(macAddress))
            {
                if (deviceDictionary.TryGetValue(macAddress, out bluetoothLEDevice))
                {
                    this.AppendTextWithNewLine($"通过 MAC 地址找到设备: {macAddress}");
                }
                else
                {
                    this.AppendTextWithNewLine($"字典中未找到 MAC 地址: {macAddress}");
                    foreach (var key in deviceDictionary.Keys)
                    {
                        this.AppendTextWithNewLine($"字典中的 MAC: {key}");
                    }
                }
            }
            else
            {
                this.AppendTextWithNewLine("未能提取到 MAC 地址");
            }

            if (bluetoothLEDevice == null)
            {
                foreach (var device in DeviceList)
                {
                    string deviceMac = GetDeviceMacAddress(device);
                    if (deviceMac == macAddress)
                    {
                        bluetoothLEDevice = device;
                        break;
                    }
                }
            }

            if (bluetoothLEDevice == null)
            {
                MessageBox.Show($"未找到设备: {selectedDisplayName}", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            try
            {
                this.btnConnect.Enabled = false;
                this.Cursor = Cursors.WaitCursor;

                this.AppendTextWithNewLine($"正在匹配: {selectedDisplayName}");
                bleCore.StartMatching(bluetoothLEDevice);

                if (bleCore.CurrentDevice != null)
                {
                    _lastConnectedMac = GetDeviceMacAddress(bluetoothLEDevice);
                    // 自动获取服务和特征
                    await AutoConfigureDevice();
                    MessageBox.Show("设备已连接并配置完成", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"连接异常: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                this.btnConnect.Enabled = true;
                this.Cursor = Cursors.Default;
            }

 
        }

        /// <summary>
        /// 自动配置设备(使用固定 UUID)
        /// </summary>
        private async Task AutoConfigureDevice()
        {
            AppendTextWithNewLine("开始自动配置设备...");

            if (bleCore.CurrentDevice == null)
                throw new Exception("设备未连接");

            // 1. 获取服务
            var servicesResult = await bleCore.CurrentDevice.GetGattServicesAsync(Windows.Devices.Bluetooth.BluetoothCacheMode.Uncached);
            if (servicesResult.Status != GattCommunicationStatus.Success)
                throw new Exception($"获取服务失败: {servicesResult.Status}");

            var targetService = servicesResult.Services.FirstOrDefault(s => s.Uuid == SERVICE_UUID);
            if (targetService == null)
                throw new Exception($"未找到服务: {SERVICE_UUID}");
            AppendTextWithNewLine($"✅ 找到服务: {SERVICE_UUID}");


            // ==== 新增:赋值并打印 MTU ====
            bleCore.CurrentService = targetService;
            int mtuPayload = bleCore.GetMaxPacketSize();
            AppendTextWithNewLine($"📊 当前 ATT MTU 有效载荷: {mtuPayload} 字节");


            // 2. 获取特征
            var characteristicsResult = await targetService.GetCharacteristicsAsync(Windows.Devices.Bluetooth.BluetoothCacheMode.Uncached);
            if (characteristicsResult.Status != GattCommunicationStatus.Success)
                throw new Exception($"获取特征失败: {characteristicsResult.Status}");

            var characteristics = characteristicsResult.Characteristics;

            // 3. 配置 Notify 特征 (FFF1) —— 使用 SetOpteron 注册并启用通知
            var notifyChar = characteristics.FirstOrDefault(c => c.Uuid == NOTIFY_UUID);
            if (notifyChar != null)
            {
                await bleCore.SetOpteron(notifyChar);  // 内部注册事件并启用通知
                this.btnLoader.Enabled = true;
                AppendTextWithNewLine($"✅ 已配置 Notify 特征: {NOTIFY_UUID}");
            }
            else
            {
                AppendTextWithNewLine($"⚠️ 未找到 Notify 特征: {NOTIFY_UUID}");
            }

            // 4. 配置 Write 特征 (FFF2) —— 直接赋值,无需连接
            var writeChar = characteristics.FirstOrDefault(c => c.Uuid == WRITE_UUID);
            if (writeChar != null)
            {
                bleCore.CurrentWriteCharacteristic = writeChar;
                this.btnSend.Enabled = true;
                AppendTextWithNewLine($"✅ 已配置 Write 特征: {WRITE_UUID}");
            }
            else
            {
                AppendTextWithNewLine($"⚠️ 未找到 Write 特征: {WRITE_UUID}");
            }

            // 5. 更新 UI
            this.btnLoader.Enabled = true;
            this.btnOpteron.Text = "断开蓝牙";
            this.btnOpteron.Enabled = true;
           

            AppendTextWithNewLine("✅ 设备自动配置完成");
        }



        private async void btnOpteron_Click(object sender, EventArgs e)
        {
            if (this.btnOpteron.Text == "断开蓝牙")
            {
                _lastConnectedMac = bleCore.CurrentDeviceMAC?.ToLower();
                this.bleCore.Dispose();
                this.btnOpteron.Text = "连接蓝牙";
                this.btnSend.Enabled = false;
                this.btnLoader.Enabled = false;
                this.btnLoader.Enabled = false;
                this.AppendTextWithNewLine("已断开蓝牙连接");
                return;
            }

            // 如果是"连接蓝牙"状态,尝试重连
            if (!string.IsNullOrEmpty(_lastConnectedMac))
            {
                AppendTextWithNewLine($"尝试重新连接 {_lastConnectedMac} ...");
                var device = await bleCore.SelectDeviceFromIdAsync(_lastConnectedMac);
                if (device != null)
                {
                    await AutoConfigureDevice();
                    AppendTextWithNewLine("重连并配置成功");
                }
                else
                {
                    MessageBox.Show("无法重新连接设备,请重新搜索并连接", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                }
            }
            else
            {
                MessageBox.Show("请先通过【连接】按钮连接设备", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }





        private void btnLoader_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Title = "选择要发送的固件文件";
            ofd.Filter = "BIN文件 (*.bin)|*.bin|所有文件 (*.*)|*.*";
            ofd.FilterIndex = 1;
            ofd.DefaultExt = "bin";

            if (ofd.ShowDialog() == DialogResult.OK)
            {
                _selectedFilePath = ofd.FileName;
                // 显示选中文件
                this.linkLabelEdit1.Text = _selectedFilePath; // 如果有文本框显示路径
                this.richTextBox1.AppendText($"已选择文件: {_selectedFilePath}\n");
                // 启用发送按钮
                this.btnSend.Enabled = true;
            }



        }



        private async void btnSend_Click(object sender, EventArgs e)
        {
            // 1. 检查是否已选择有效文件
            if (string.IsNullOrEmpty(_selectedFilePath) || !System.IO.File.Exists(_selectedFilePath))
            {
                MessageBox.Show("请先通过【选择文件】按钮选择固件文件", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            // 2. 检查蓝牙连接和写特征是否就绪
            if (bleCore.CurrentDevice == null || bleCore.CurrentWriteCharacteristic == null)
            {
                MessageBox.Show("蓝牙未连接或未设置写特征,请先连接并配置特征", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            // 3. 禁用发送按钮,防止重复点击
            btnSend.Enabled = false;

            try
            {
                // 4. 启动 Ymodem 发送(异步)
                await _ymodemSender.SendFileAsync(_selectedFilePath);
                AppendTextWithNewLine("✅ 文件发送完成");
            }
            catch (Exception ex)
            {
                AppendTextWithNewLine($"❌ 发送失败: {ex.Message}");
                MessageBox.Show($"发送失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                btnSend.Enabled = true;
            }
        }

        /// <summary>
        /// 提示消息
        /// </summary>
        private void BleCore_MessAgeChanged(MsgType type, string message, byte[] data)
        {
            RunAsync(() =>
            {
                if (type == MsgType.BleData)
                {
                    //if (isReading)
                    //{
                        string timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
                        if (data != null && data.Length > 0)
                        {
                            //string hexStr = BitConverter.ToString(data);
                            //this.AppendTextWithNewLine($"[{timestamp}] 收到 {data.Length} 字节: {hexStr}");
                        }
                        else
                        {
                            this.AppendTextWithNewLine($"[{timestamp}] {message}");
                        }
                    //}
                }
                else
                {
                    this.AppendTextWithNewLine(message);
                }
            });
        }

        /// <summary>
        /// 搜索蓝牙设备列表
        /// </summary>
        private void BleCore_DeviceWatcherChanged(MsgType type, Windows.Devices.Bluetooth.BluetoothLEDevice bluetoothLEDevice)
        {
            /* RunAsync(() =>
             {
                 this.listboxBleDevice.AppendText(bluetoothLEDevice.Name);
                 this.DeviceList.Add(bluetoothLEDevice);
                 this.btnConnect.Enabled = true;
             });*/
            RunAsync(() =>
            {
                // 只收集设备,不显示
                if (bluetoothLEDevice != null && isSearching)
                {
                    foundDevices.Add(bluetoothLEDevice);
                }
            });
        }

        /// <summary>
        /// 获取蓝牙服务列表
        /// </summary>
        private void BleCore_GattDeviceServiceAdded(Windows.Devices.Bluetooth.GenericAttributeProfile.GattDeviceService gattDeviceService)
        {
            RunAsync(() =>
            {
                //this.cmbServer.Items.Add(gattDeviceService.Uuid.ToString());
                this.GattDeviceServices.Add(gattDeviceService);
               // this.btnFeatures.Enabled = true;
            });
        }

        /// <summary>
        /// 获取特征列表
        /// </summary>
        private void BleCore_CharacteristicAdded(Windows.Devices.Bluetooth.GenericAttributeProfile.GattCharacteristic gattCharacteristic)
        {
            RunAsync(() =>
            {
               // this.cmbFeatures.Items.Add(gattCharacteristic.Uuid);
                this.GattCharacteristics.Add(gattCharacteristic);
                this.btnOpteron.Enabled = true;
            });
        }

        private void Init()
        {
            this.btnConnect.Enabled = false;
            this.btnOpteron.Enabled = false;
            this.btnLoader.Enabled = true;
            this.btnSend.Enabled = false;

           

        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            this.bleCore.MessAgeChanged -= BleCore_MessAgeChanged;
            this.bleCore.DeviceWatcherChanged -= BleCore_DeviceWatcherChanged;
            //this.bleCore.GattDeviceServiceAdded -= BleCore_GattDeviceServiceAdded;
            //this.bleCore.CharacteristicAdded -= BleCore_CharacteristicAdded;
            this.bleCore.Dispose();
        }

     



        private void Ymodem_ProgressChanged(int percent, string status)
        {
            RunAsync(() => AppendLog($"[进度] {percent}% - {status}"));
        }

        private void Ymodem_LogMessage(string msg)
        {
            RunAsync(() => AppendLog($"[上位机] {msg}"));
        }

        private void Ymodem_ErrorOccurred(string error)
        {
            RunAsync(() =>
            {
                AppendLog($"[错误] {error}");
                MessageBox.Show(error, "Ymodem 错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            });
        }

        private void btnServer_Click(object sender, EventArgs e)
        {

        }

        private void btnFeatures_Click(object sender, EventArgs e)
        {

        }
    }
}

3、YmodemSender.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.IO.Ports;
using System.Threading;

namespace TCM_OTA_APP
{
    class YmodemSender
    {

        private BleCore _bleCore;
        private int _fileSize;
        private int _sentBytes;
        private byte[] _fileData;
        private string _fileName;
        private bool _cancel;

        // 事件
        public event Action<int, string> ProgressChanged;
        public event Action<string> LogMessage;
        public event Action<string> ErrorOccurred;

        private const byte SOH = 0x01;
        private const byte STX = 0x02;
        private const byte EOT = 0x04;
        private const byte ACK = 0x06;
        private const byte NAK = 0x15;
        private const byte CAN = 0x18;
        private const byte C = 0x43;

        private const int MAX_RETRIES = 30;
        private const int MAX_BLE_PACKET_SIZE = 10; // 默认 MTU,实际可根据协商调整
        private const int BLE_SEND_DELAY_MS = 50;    // 增加延时到30ms

        // 用于同步等待应答
        //private ManualResetEventSlim _ackEvent = new ManualResetEventSlim(false);
        private byte _lastReceivedByte;
        private bool _gotResponse;
        private int _maxBlePacketSize;

        public YmodemSender(BleCore bleCore)
        {
            _bleCore = bleCore;
            // 订阅 Notify 数据接收
            _bleCore.MessAgeChanged += OnBleDataReceived;

            _maxBlePacketSize = _bleCore.GetMaxPacketSize();
            Log($"📊 BLE 最大包大小: {_maxBlePacketSize} 字节");

        }
        private void OnBleDataReceived(MsgType type, string message, byte[] data)
        {
            if (type == MsgType.BleData && data != null && data.Length > 0)
            {
                _lastReceivedByte = data[0];
                _gotResponse = true;   // 仅设置标志,等待循环会轮询此标志
            }
        }
        /// <summary>
        /// 发送文件
        /// </summary>
        public async Task SendFileAsync(string filePath)
        {



            // 1. 先发送自定义命令使板端进入 Ymodem 模式
            Log("正在唤醒设备进入 Ymodem 模式...");
            if (!await SendEnterYmodemCommandAsync())
                throw new Exception("板端未响应进入 Ymodem 模式命令");



            // 2. 以下为标准 Ymodem 发送流程(与原先一致)
            if (!File.Exists(filePath))
                throw new FileNotFoundException("文件不存在", filePath);

            _fileData = File.ReadAllBytes(filePath);
            _fileSize = _fileData.Length;
            _fileName = Path.GetFileName(filePath);
            _cancel = false;


            DateTime startTime = DateTime.Now;
            Log($"⏱️ 开始发送时间: {startTime:HH:mm:ss.fff}");

            Log($"开始发送文件: {_fileName}, 大小: {_fileSize} 字节");

            // 等待接收方发送 'C' (CRC模式)
            Log("等待接收方 CRC 启动信号...");
            if (!await WaitForCharAsync(C, 60000))
                throw new TimeoutException("未收到启动信号 (C)");

            // 3. 发送文件名和大小 (0号包)
            Log("发送文件信息包...");
            await SendFileInfoPacketAsync(_fileName, _fileSize);
            if (!await WaitForAckAsync())
                throw new Exception("文件信息包未确认");

            // 4. 发送数据包
            Log($"开始发送数据,总大小: {_fileSize} 字节");
            _sentBytes = 0;
            int packetNumber = 1;
            int offset = 0;
            int maxPacketSize = 128; // Ymodem 标准包大小


         

            try
            {
                while (offset < _fileSize && !_cancel)
                {


                    Log($"🔁 循环开始: packetNumber={packetNumber}, offset={offset}/{_fileSize}");

                    int chunkSize = Math.Min(maxPacketSize, _fileSize - offset);
                    byte[] chunk = new byte[chunkSize];
                    Array.Copy(_fileData, offset, chunk, 0, chunkSize);

                    bool ack = false;
                    int retries = 0;
                    while (!ack && retries < MAX_RETRIES && !_cancel)
                    {
                        try
                        {
                            await SendDataPacketAsync(chunk, packetNumber);
                            ack = await WaitForAckAsync();
                        }
                        catch (Exception ex)
                        {
                            Log($"发送或等待异常: {ex.Message},将重试");
                            ack = false;
                        }
                        if (!ack)
                        {
                            Log($"⚠️ 数据包 #{packetNumber} 未确认,重试 ({retries + 1}/{MAX_RETRIES})");
                            retries++;
                            await Task.Delay(300); // 重试前等待稍长
                        }
                        else
                        {
                            Log($"✅ 数据包 #{packetNumber} 已确认");
                        }
                    }
                    if (!ack)
                        throw new Exception($"数据包 #{packetNumber} 发送失败,超过最大重试次数");

                    offset += chunkSize;
                    _sentBytes = offset;
                    int percent = (int)((double)offset / _fileSize * 100);
                    ProgressChanged?.Invoke(percent, $"发送中 {percent}%");

                    Log($"📈 准备递增 packetNumber,当前值={packetNumber}");
                    packetNumber++;
                    Log($"📈 packetNumber 递增后 = {packetNumber}");
                }

                DateTime endTime = DateTime.Now;
                TimeSpan elapsed = endTime - startTime;
                Log($"✅ 发送完成,总耗时: {elapsed.TotalSeconds:F2} 秒 ({elapsed.TotalMilliseconds:F0} ms)");
                ProgressChanged?.Invoke(100, $"完成 (耗时 {elapsed.TotalSeconds:F2}s)");


            }
            catch (Exception ex)
            {
                DateTime endTime = DateTime.Now;
                TimeSpan elapsed = endTime - startTime;
                Log($"❌ 发送失败,耗时: {elapsed.TotalSeconds:F2} 秒,错误: {ex.Message}");
                throw; // 或处理
            }
            if (_cancel)
            {
                Log("发送被取消");
                return;
            }

            // 5. 发送 EOT
            Log("所有数据发送完毕,发送 EOT...");
            await SendEotAsync();   // 内部已包含重试

            // 6. 发送结束包
            Log("发送结束包...");
            bool endAck = false;
            int endRetries = 0;
            int maxEndRetries = 3;   // 只尝试3次
            while (!endAck && endRetries < maxEndRetries && !_cancel)
            {
                try
                {
                    await SendEndPacketAsync();
                    endAck = await WaitForAckAsync();
                    if (!endAck)
                    {
                        Log($"⚠️ 结束包未确认,重试 ({endRetries + 1}/{maxEndRetries})");
                        endRetries++;
                        await Task.Delay(300);
                    }
                }
                catch (Exception ex)
                {
                    Log($"⚠️ 结束包异常: {ex.Message},继续重试");
                    endRetries++;
                    await Task.Delay(300);
                }
            }
            if (!endAck)
            {
                Log("⚠️ 结束包未确认,但 EOT 已确认,认为传输成功");
            }
            else
            {
                Log("✅ 结束包已确认");
            }

            Log("Ymodem 发送完成");
            ProgressChanged?.Invoke(100, "完成");


        }

        /// <summary>
        /// 取消发送
        /// </summary>
        public void Cancel()
        {
            _cancel = true;
            Log($"取消发送数据:AAAAA");
            //_ackEvent.Set(); // 唤醒等待
        }

        // ---------- 底层 BLE 发送与接收 ----------

        private async Task SendRawDataAsync(byte[] data)
        {
            try
            {
                int offset = 0;
                while (offset < data.Length)
                {
                    int len = Math.Min(_maxBlePacketSize, data.Length - offset);
                    byte[] chunk = new byte[len];
                    Array.Copy(data, offset, chunk, 0, len);
                    await _bleCore.Write(chunk);
                    offset += len;
                   //await Task.Delay(BLE_SEND_DELAY_MS); // 确保模块有足够时间处理
                }
            }
            catch (Exception ex)
            {
                Log($"发送原始数据异常: {ex.Message}");
                throw;
            }
        }

        private async Task SendPacketAsync(byte header, byte packetNumber, byte[] data)
        {
            // 计算 CRC
            ushort crc = Crc16.CalcCrc(data);
            byte[] crcBytes = new byte[] { (byte)(crc >> 8), (byte)(crc & 0xFF) };

            // 组装完整 Ymodem 包
            List<byte> packet = new List<byte>();
            packet.Add(header);
            packet.Add(packetNumber);
            packet.Add((byte)(~packetNumber));
            packet.AddRange(data);
            packet.AddRange(crcBytes);

            await SendRawDataAsync(packet.ToArray());
        }

        private async Task SendFileInfoPacketAsync(string fileName, int fileSize)
        {
            byte[] nameBytes = System.Text.Encoding.ASCII.GetBytes(fileName);
            byte[] sizeBytes = System.Text.Encoding.ASCII.GetBytes(fileSize.ToString());
            int packetLen = 128;
            byte[] packetData = new byte[packetLen];
            Array.Copy(nameBytes, 0, packetData, 0, Math.Min(nameBytes.Length, packetLen - 1));
            int pos = nameBytes.Length + 1;
            if (pos < packetLen)
            {
                Array.Copy(sizeBytes, 0, packetData, pos, Math.Min(sizeBytes.Length, packetLen - pos - 1));
            }
            await SendPacketAsync(SOH, 0x00, packetData);
        }

        private async Task SendDataPacketAsync(byte[] data, int packetNumber)
        {

            if (_cancel) return; // 或者抛出异常

            Log($"发送数据包 #{packetNumber}, 大小: {data.Length}");

            int dataLen = data.Length;

            bool useStx = data.Length > 128;
            byte header = useStx ? STX : SOH;
            int maxLen = useStx ? 1024 : 128;
            byte[] packetData = new byte[maxLen];
            Array.Copy(data, packetData, dataLen); // data.Length 可能大于 128 但 maxLen 只有 128 → 越界!    
                                                   // 填充 0x1A
            for (int i = data.Length; i < maxLen; i++)
                packetData[i] = 0x1A;
            await SendPacketAsync(header, (byte)(packetNumber & 0xFF), packetData);

            await Task.Delay(100); // 给模块处理时间
        }

        private async Task SendEndPacketAsync()
        {
            byte[] zeroData = new byte[128];
            await SendPacketAsync(SOH, 0x00, zeroData);
        }

        private async Task SendEotAsync()
        {
            bool ack = false;
            int retries = 0;
            while (!ack && retries < MAX_RETRIES && !_cancel)
            {
                await SendRawDataAsync(new byte[] { EOT });
                Log($"发送 EOT,等待确认...");
                ack = await WaitForAckAsync();
                if (!ack)
                {
                    Log($"⚠️ EOT 未确认,重试 ({retries + 1}/{MAX_RETRIES})");
                    retries++;
                    await Task.Delay(300);
                }
                else
                {
                    Log("✅ EOT 已确认");
                }
            }
            if (!ack)
                throw new Exception("EOT 发送失败,超过最大重试次数");
        }
        // ---------- 等待应答 ----------

        private async Task<bool> WaitForCharAsync(byte expected, int timeoutMs)
        {
            using (var cts = new CancellationTokenSource(timeoutMs))
            {
                while (!cts.Token.IsCancellationRequested)
                {
                    if (_gotResponse)
                    {
                        if (_lastReceivedByte == expected)
                            return true;
                        if (_lastReceivedByte == CAN)
                            throw new Exception("接收方发送 CAN 取消");
                        // 收到其他字节,重置标志,继续等待
                        _gotResponse = false;
                    }
                    // 等待 50ms 后再次检查,同时监听取消请求
                    await Task.Delay(50, cts.Token).ConfigureAwait(false);
                }
                return false;
            }
        }
        private async Task<bool> WaitForAckAsync()
        {
            int timeoutMs = 5000;
            using (var cts = new CancellationTokenSource(timeoutMs))
            {
                while (!cts.Token.IsCancellationRequested)
                {
                    if (_gotResponse)
                    {
                        byte b = _lastReceivedByte;
                        _gotResponse = false;
                        Log($"📥 WaitForAckAsync 收到字节: 0x{b:X2}");
                        if (b == ACK) return true;
                        if (b == CAN)
                        {
                            Log("接收方发送 CAN,终止传输");
                            return false;  // 不再抛出异常
                        }
                        if (b == NAK) return false;
                        // 过滤蓝牙 状态字节(0xE0, 0x00 等),继续等待
                        if (b == 0xE0 || b == 0x00 || b > 0x18) // 非协议字节
                        {
                            Log($"忽略无效字节 0x{b:X2},继续等待...");
                            continue;
                        }
                        // 其他未定义字节也忽略
                        continue;
                    }
                    await Task.Delay(100, cts.Token).ConfigureAwait(false);
                }
                Log("⏰ WaitForAckAsync 超时");
                return false;
            }
        }

        private void Log(string msg)
        {
            LogMessage?.Invoke(msg);
        }



        // 计算 CRC16/Modbus
        private static ushort CalcCrc16(byte[] data)
        {
            ushort crc = 0xFFFF;
            for (int i = 0; i < data.Length; i++)
            {
                crc ^= data[i];
                for (int j = 0; j < 8; j++)
                {
                    if ((crc & 0x0001) != 0)
                        crc = (ushort)((crc >> 1) ^ 0xA001);
                    else
                        crc >>= 1;
                }
            }
            return crc;
        }

        /// <summary>
        /// 发送自定义命令使板端进入 Ymodem 模式,并等待确认
        /// 这是我定义的指令,具体的根据自己的来
        /// </summary>
        private async Task<bool> SendEnterYmodemCommandAsync()
        {
            // 构造命令报文 发给控制板的升级指令
            List<byte> cmd = new List<byte>();
            cmd.Add(0xEE);          
            cmd.Add(0x01);          
            cmd.Add(0x00);          
            cmd.Add(0x08);          
            cmd.Add(0x54);          
                                    

            // 计算 CRC16(从 VERSION 开始到 CMD 结束,即索引 2~4)
            byte[] crcData = cmd.Skip(1).ToArray(); // {0x01, 0x00, 0x08, 0x54}
            ushort crc = CalcCrc16(crcData);
            cmd.Add((byte)(crc >> 8));
            cmd.Add((byte)(crc & 0xFF));
            cmd.Add(0xEE);          

            // 发送命令
            await _bleCore.Write(cmd.ToArray());
            Log("已发送进入 Ymodem 模式命令,等待确认...");

            await _bleCore.Write(cmd.ToArray());
            Log("已发送进入 Ymodem 模式命令(不等待响应)");
            return true;  // 始终认为成功
        }
    }
}

4、BleCore.cs

        XXXX改成蓝牙名称

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.Foundation;
using Windows.Networking;
using Windows.Networking.Proximity;
using Windows.Networking.Sockets;
using Windows.Security.Cryptography;
using Windows.Storage.Streams;

namespace TCM_OTA_APP
{


    public enum MsgType
    {
        NotifyTxt,
        BleData,
        BleDevice
    }
    public class BleCore
    {

        private Boolean asyncLock = false;



        /// <summary>
        /// 搜索蓝牙设备对象
        /// </summary>
        private DeviceWatcher deviceWatcher;

        /// <summary>
        /// 当前连接的服务
        /// </summary>
        public GattDeviceService CurrentService { get; set; }

        /// <summary>
        /// 当前连接的蓝牙设备
        /// </summary>
        public BluetoothLEDevice CurrentDevice { get; set; }

        /// <summary>
        /// 写特征对象
        /// </summary>
        public GattCharacteristic CurrentWriteCharacteristic { get; set; }

        /// <summary>
        /// 通知特征对象
        /// </summary>
        public GattCharacteristic CurrentNotifyCharacteristic { get; set; }

        /// <summary>
        /// 特性通知类型通知启用
        /// </summary>
        private const GattClientCharacteristicConfigurationDescriptorValue CHARACTERISTIC_NOTIFICATION_TYPE = GattClientCharacteristicConfigurationDescriptorValue.Notify;

        /// <summary>
        /// 存储检测到的设备
        /// </summary>
        private List<BluetoothLEDevice> DeviceList = new List<BluetoothLEDevice>();

        /// <summary>
        /// 定义搜索蓝牙设备委托
        /// </summary>
        public delegate void DeviceWatcherChangedEvent(MsgType type, BluetoothLEDevice bluetoothLEDevice);

        /// <summary>
        /// 搜索蓝牙事件
        /// </summary>
        public event DeviceWatcherChangedEvent DeviceWatcherChanged;

        /// <summary>
        /// 获取服务委托
        /// </summary>
        public delegate void GattDeviceServiceAddedEvent(GattDeviceService gattDeviceService);

        /// <summary>
        /// 获取服务事件
        /// </summary>
        public event GattDeviceServiceAddedEvent GattDeviceServiceAdded;

        /// <summary>
        /// 获取特征委托
        /// </summary>
        public delegate void CharacteristicAddedEvent(GattCharacteristic gattCharacteristic);

        /// <summary>
        /// 获取特征事件
        /// </summary>
        public event CharacteristicAddedEvent CharacteristicAdded;

        /// <summary>
        /// 提示信息委托
        /// </summary>
        public delegate void MessAgeChangedEvent(MsgType type, string message, byte[] data = null);

        /// <summary>
        /// 提示信息事件
        /// </summary>
        public event MessAgeChangedEvent MessAgeChanged;

        /// <summary>
        /// 当前连接的蓝牙Mac
        /// </summary>
       public string CurrentDeviceMAC { get; set; }

        public BleCore()
        {

        }

        /// <summary>
        /// 搜索蓝牙设备
        /// </summary>
        public void StartBleDeviceWatcher()
        {
            string[] requestedProperties = { "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.IsConnected", "System.Devices.Aep.Bluetooth.Le.IsConnectable" };
            string aqsAllBluetoothLEDevices = "(System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\")";
            deviceWatcher =
 DeviceInformation.CreateWatcher(aqsAllBluetoothLEDevices, requestedProperties, DeviceInformationKind.AssociationEndpoint);

            // Register event handlers before starting the watcher.
            deviceWatcher.Added += this.DeviceWatcher_Added;
            deviceWatcher.Updated += DeviceWatcher_Updated;
            deviceWatcher.Stopped += this.DeviceWatcher_Stopped;
            deviceWatcher.Start();
            string msg = "自动发现设备中..";

            this.MessAgeChanged(MsgType.NotifyTxt, msg);
        }

        private void DeviceWatcher_Updated(DeviceWatcher sender, DeviceInformationUpdate args)
        {
        }

        /// <summary>
        /// 停止搜索蓝牙
        /// </summary>
        public void StopBleDeviceWatcher()
        {
            this.deviceWatcher.Stop();
        }

        /// <summary>
        /// 获取发现的蓝牙设备
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private async void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args)
        {

            string deviceName = args.Name ?? "";

            if (deviceName.Contains("XXXX"))
            {
                // 👇 检查是否可连接(正在广播的设备)
                if (args.Properties.ContainsKey("System.Devices.Aep.Bluetooth.Le.IsConnectable"))
                {
                    bool isConnectable = (bool)args.Properties["System.Devices.Aep.Bluetooth.Le.IsConnectable"];
                    if (isConnectable)
                    {
                        this.MessAgeChanged(MsgType.NotifyTxt, $"发现目标设备(在线): {args.Id}");
                        await this.Matching(args.Id);
                    }
                    else
                    {
                        //this.MessAgeChanged(MsgType.NotifyTxt, $"发现目标设备(离线): {args.Name} - 已忽略");
                    }
                }
                else
                {
                    // 如果没有 IsConnectable 属性,可能是不支持的旧设备,选择处理
                    this.MessAgeChanged(MsgType.NotifyTxt, $"发现目标设备(未知状态): {args.Name}");
                    await this.Matching(args.Id);
                }
            }

        }

        /// <summary>
        /// 停止搜索蓝牙设备
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void DeviceWatcher_Stopped(DeviceWatcher sender, object args)
        {
            string msg = "自动发现设备停止";
            this.MessAgeChanged(MsgType.NotifyTxt, msg);
        }

        /// <summary>
        /// 匹配
        /// </summary>
        /// <param name="Device"></param>
        public void StartMatching(BluetoothLEDevice Device)
        {
            this.CurrentDevice = Device;
        }

        /// <summary>
        /// 获取蓝牙服务
        /// </summary>
        public async Task<bool> FindService()
        {
            /*           this.CurrentDevice.GetGattServicesAsync().Completed = async (asyncInfo, asyncStatus) =>
                       {
                           if (asyncStatus == AsyncStatus.Completed)
                           {
                               var services = asyncInfo.GetResults().Services;
                               this.MessAgeChanged(MsgType.NotifyTxt, "GattServices size=" + services.Count);
                               foreach (GattDeviceService ser in services)
                               {
                                   GattDeviceServiceAdded(ser);
                               }
                               this.MessAgeChanged(MsgType.NotifyTxt, "获取服务码收集完毕");

                           }
                       };*/
            try
            {
                var servicesResult = await this.CurrentDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached);
                if (servicesResult.Status == GattCommunicationStatus.Success)
                {
                    var services = servicesResult.Services;
                    this.MessAgeChanged(MsgType.NotifyTxt, $"GattServices size={services.Count}");

                    foreach (GattDeviceService ser in services)
                    {
                        GattDeviceServiceAdded(ser);
                    }
                    this.MessAgeChanged(MsgType.NotifyTxt, "获取服务码收集完毕");
                    return true;
                }
                else
                {
                    this.MessAgeChanged(MsgType.NotifyTxt, $"获取服务失败: {servicesResult.Status}");
                    return false;
                }
            }
            catch (Exception ex)
            {
                this.MessAgeChanged(MsgType.NotifyTxt, $"获取服务异常: {ex.Message}");
                return false;
            }

        }

        /// <summary>
        /// 获取特性
        /// </summary>
        public async Task<bool> FindCharacteristic(GattDeviceService gattDeviceService)
        {
            /*this.CurrentService = gattDeviceService;
            foreach (var c in gattDeviceService.GetAllCharacteristics())
            {
                this.CharacteristicAdded(c);
            }
            this.MessAgeChanged(MsgType.NotifyTxt, "获取特性码收集完毕");*/

            try
            {
                this.CurrentService = gattDeviceService;
                var characteristicsResult = await gattDeviceService.GetCharacteristicsAsync(BluetoothCacheMode.Uncached);

                if (characteristicsResult.Status == GattCommunicationStatus.Success)
                {
                    var characteristics = characteristicsResult.Characteristics;
                    this.MessAgeChanged(MsgType.NotifyTxt, $"获取到 {characteristics.Count} 个特征码");

                    foreach (var c in characteristics)
                    {
                        this.CharacteristicAdded(c);
                    }
                    this.MessAgeChanged(MsgType.NotifyTxt, "获取特性码收集完毕");
                    return true;
                }
                else
                {
                    this.MessAgeChanged(MsgType.NotifyTxt, $"获取特性码失败: {characteristicsResult.Status}");
                    return false;
                }
            }
            catch (Exception ex)
            {
                this.MessAgeChanged(MsgType.NotifyTxt, $"获取特性码异常: {ex.Message}");
                return false;
            }
        }

        /// <summary>
        /// 获取操作
        /// </summary>
        /// <returns></returns>
        public async Task<bool> SetOpteron(GattCharacteristic gattCharacteristic)
        {
            if (this.CurrentDevice != null)
            {
                this.MessAgeChanged(MsgType.NotifyTxt, $"设备名称: {this.CurrentDevice.Name}");
                this.MessAgeChanged(MsgType.NotifyTxt, $"连接状态: {this.CurrentDevice.ConnectionStatus}");
                this.MessAgeChanged(MsgType.NotifyTxt, $"设备ID: {this.CurrentDevice.DeviceId}");
            }

            /* if (gattCharacteristic.CharacteristicProperties == GattCharacteristicProperties.Write)
             {
                 this.CurrentWriteCharacteristic = gattCharacteristic;
             }
             if (gattCharacteristic.CharacteristicProperties == GattCharacteristicProperties.Notify)
             {
                 this.CurrentNotifyCharacteristic = gattCharacteristic;
             }
             if ((uint)gattCharacteristic.CharacteristicProperties == 26)
             { }

             if (gattCharacteristic.CharacteristicProperties == (GattCharacteristicProperties.Notify | GattCharacteristicProperties.Read | GattCharacteristicProperties.Write))
             {
                 this.CurrentWriteCharacteristic = gattCharacteristic;

                 this.CurrentNotifyCharacteristic = gattCharacteristic;
                 this.CurrentNotifyCharacteristic.ProtectionLevel = GattProtectionLevel.Plain;
                 this.CurrentNotifyCharacteristic.ValueChanged += Characteristic_ValueChanged;
                 await this.EnableNotifications(CurrentNotifyCharacteristic);
             }

             this.Connect();*/
            try
            {
                // 设置写特征
                if (gattCharacteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Write))
                {
                    this.CurrentWriteCharacteristic = gattCharacteristic;
                    this.MessAgeChanged(MsgType.NotifyTxt, "已设置写特征");
                }

                // 👇 修改:只要支持 Notify 就启用通知(不要求同时支持 Read 和 Write)
                if (gattCharacteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify))
                {
                    this.CurrentNotifyCharacteristic = gattCharacteristic;
                    this.CurrentNotifyCharacteristic.ProtectionLevel = GattProtectionLevel.Plain;
                    this.CurrentNotifyCharacteristic.ValueChanged += Characteristic_ValueChanged;

                    this.MessAgeChanged(MsgType.NotifyTxt, "已注册通知事件,正在启用通知...");
                    await this.EnableNotifications(CurrentNotifyCharacteristic);
                    this.MessAgeChanged(MsgType.NotifyTxt, "通知启用完成");
                }
                else
                {
                    this.MessAgeChanged(MsgType.NotifyTxt, "该特征不支持 Notify");
                }

                // 执行连接操作
                return await this.Connect();
            }
            catch (Exception ex)
            {
                string msg = $"设置操作失败: {ex.Message}";
                this.MessAgeChanged(MsgType.NotifyTxt, msg);
                return false;
            }
        }

        /// <summary>
        /// 连接蓝牙
        /// </summary>
        /// <returns></returns>
        private async Task<bool> Connect()
        {
            /* byte[] _Bytes1 = BitConverter.GetBytes(this.CurrentDevice.BluetoothAddress);
             Array.Reverse(_Bytes1);
             this.CurrentDeviceMAC = BitConverter.ToString(_Bytes1, 2, 6).Replace('-', ':').ToLower();

             string msg = "正在连接设备<" + this.CurrentDeviceMAC + ">..";
             this.MessAgeChanged(MsgType.NotifyTxt, msg);
             this.CurrentDevice.ConnectionStatusChanged += this.CurrentDevice_ConnectionStatusChanged;*/
            try
            {
                if (this.CurrentDevice == null)
                {
                    this.MessAgeChanged(MsgType.NotifyTxt, "设备为空,无法连接");
                    return false;
                }

                // 获取MAC地址
                byte[] _Bytes1 = BitConverter.GetBytes(this.CurrentDevice.BluetoothAddress);
                Array.Reverse(_Bytes1);
                this.CurrentDeviceMAC = BitConverter.ToString(_Bytes1, 2, 6).Replace('-', ':').ToLower();

                // 检查当前连接状态
                if (this.CurrentDevice.ConnectionStatus == BluetoothConnectionStatus.Connected)
                {
                    string msg = $"设备<{this.CurrentDeviceMAC}>已连接";
                    this.MessAgeChanged(MsgType.NotifyTxt, msg);
                    return true;
                }

                string connectMsg = $"正在连接设备<{this.CurrentDeviceMAC}>..";
                this.MessAgeChanged(MsgType.NotifyTxt, connectMsg);

                // 注册连接状态变化事件
                this.CurrentDevice.ConnectionStatusChanged += this.CurrentDevice_ConnectionStatusChanged;

                // 尝试通过获取服务来触发连接
                var servicesResult = await this.CurrentDevice.GetGattServicesAsync();

                if (servicesResult.Status == GattCommunicationStatus.Success)
                {
                    connectMsg = $"设备<{this.CurrentDeviceMAC}>连接成功";
                    this.MessAgeChanged(MsgType.NotifyTxt, connectMsg);
                    return true;
                }
                else
                {
                    connectMsg = $"设备<{this.CurrentDeviceMAC}>连接失败: {servicesResult.Status}";
                    this.MessAgeChanged(MsgType.NotifyTxt, connectMsg);
                    return false;
                }
            }
            catch (Exception ex)
            {
                string msg = $"连接异常: {ex.Message}";
                this.MessAgeChanged(MsgType.NotifyTxt, msg);
                return false;
            }
        }

        /// <summary>
        /// 搜索到的蓝牙设备
        /// </summary>
        /// <returns></returns>
        private async Task Matching(string Id)
        {
            try
            {
                BluetoothLEDevice bleDevice = await BluetoothLEDevice.FromIdAsync(Id);
                this.DeviceList.Add(bleDevice);
                this.DeviceWatcherChanged(MsgType.BleDevice, bleDevice);
            }
            catch (Exception e)
            {
                string msg = "没有发现设备" + e.ToString();
                this.MessAgeChanged(MsgType.NotifyTxt, msg);
                this.StartBleDeviceWatcher();
            }
        }

        /// <summary>
        /// 主动断开连接
        /// </summary>
        /// <returns></returns>
        public void Dispose()
        {

            CurrentDeviceMAC = null;
            CurrentService?.Dispose();
            CurrentDevice?.Dispose();
            CurrentDevice = null;
            CurrentService = null;
            CurrentWriteCharacteristic = null;
            CurrentNotifyCharacteristic = null;
            MessAgeChanged(MsgType.NotifyTxt, "主动断开连接");
        }

        private async void CurrentDevice_ConnectionStatusChanged(BluetoothLEDevice sender, object args)
        {
            if (sender.ConnectionStatus == BluetoothConnectionStatus.Disconnected && CurrentDeviceMAC != null)
            {
                string msg = "设备已断开,自动重连";
                MessAgeChanged(MsgType.NotifyTxt, msg);
                if (!asyncLock)
                {
                    asyncLock = true;
                    this.CurrentDevice.Dispose();
                    this.CurrentDevice = null;
                    CurrentService = null;
                    CurrentWriteCharacteristic = null;
                    CurrentNotifyCharacteristic = null;
                    await SelectDeviceFromIdAsync(CurrentDeviceMAC);
                }
            }
            else
            {
                string msg = "设备已连接";
                MessAgeChanged(MsgType.NotifyTxt, msg);
            }
        }



        /// <summary>
        /// 设置特征对象为接收通知对象
        /// </summary>
        /// <param name="characteristic"></param>
        /// <returns></returns>
        public async Task EnableNotifications(GattCharacteristic characteristic)
        {
            string msg = $"正在启用通知,设备状态: {CurrentDevice.ConnectionStatus}";
            this.MessAgeChanged(MsgType.NotifyTxt, msg);

            try
            {
                GattCommunicationStatus status = await characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(CHARACTERISTIC_NOTIFICATION_TYPE);

                if (status == GattCommunicationStatus.Success)
                {
                    this.MessAgeChanged(MsgType.NotifyTxt, "✅ 通知启用成功");
                }
                else if (status == GattCommunicationStatus.Unreachable)
                {
                    this.MessAgeChanged(MsgType.NotifyTxt, "❌ 设备不可达,通知启用失败");
                    if (CurrentNotifyCharacteristic != null && !asyncLock)
                    {
                        asyncLock = true;
                        await this.EnableNotifications(CurrentNotifyCharacteristic);
                        asyncLock = false;
                    }
                }
                else
                {
                    this.MessAgeChanged(MsgType.NotifyTxt, $"❌ 通知启用失败: {status}");
                }
            }
            catch (Exception ex)
            {
                this.MessAgeChanged(MsgType.NotifyTxt, $"❌ 通知启用异常: {ex.Message}");
            }
        }

        /// <summary>
        /// 接受到蓝牙数据
        /// </summary>
        private void Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
        {
            byte[] data;
            CryptographicBuffer.CopyToByteArray(args.CharacteristicValue, out data);
            string str = BitConverter.ToString(data);

            // 👇 添加调试日志
            this.MessAgeChanged(MsgType.NotifyTxt, $"[板端回复:]收到原始数据: {str}");
            this.MessAgeChanged(MsgType.BleData, str, data);
        }

        /// <summary>
        /// 发送数据接口
        /// </summary>
        /// <returns></returns>
        public async Task Write(byte[] data)
        {
            if (CurrentWriteCharacteristic != null)
            {
                await CurrentWriteCharacteristic.WriteValueAsync(CryptographicBuffer.CreateFromByteArray(data), GattWriteOption.WriteWithResponse);
                string str = "发送数据:" + BitConverter.ToString(data);
                this.MessAgeChanged(MsgType.BleData, str, data);
            }

        }

        /// <summary>
        /// 按MAC地址查找系统中配对设备
        /// </summary>
        /// <param name="MAC"></param>
        public async Task SelectDevice(string MAC)
        {
            CurrentDeviceMAC = MAC;
            CurrentDevice = null;
            var deviceInformation = await DeviceInformation.FindAllAsync(BluetoothLEDevice.GetDeviceSelector());
            foreach (DeviceInformation di in deviceInformation)
            {
                await Matching(di.Id);
            }
            if (CurrentDevice == null)
            {
                string msg = "没有发现设备";
                this.MessAgeChanged(MsgType.NotifyTxt, msg);
                StartBleDeviceWatcher();
            }
        }

        /// <summary>
        /// 按MAC地址直接组装设备ID查找设备
        /// </summary>
        /// <param name="MAC"></param>
        /// <returns></returns>
        public async Task<BluetoothLEDevice> SelectDeviceFromIdAsync(string MAC)
        {
            CurrentDeviceMAC = MAC;
            CurrentDevice = null;

            try
            {
                // 将 MAC 字符串转换为 ulong (去掉冒号)
                string hex = MAC.Replace(":", "");
                if (ulong.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out ulong address))
                {
                    // 直接通过蓝牙地址获取设备
                    var device = await BluetoothLEDevice.FromBluetoothAddressAsync(address);
                    if (device != null)
                    {
                        CurrentDevice = device;
                        this.DeviceWatcherChanged?.Invoke(MsgType.BleDevice, device);
                        // 尝试获取服务以触发连接
                        var servicesResult = await device.GetGattServicesAsync();
                        if (servicesResult.Status == GattCommunicationStatus.Success)
                        {
                            MessAgeChanged?.Invoke(MsgType.NotifyTxt, $"重连设备 {MAC} 成功");
                            return device;
                        }
                        else
                        {
                            MessAgeChanged?.Invoke(MsgType.NotifyTxt, $"重连失败: {servicesResult.Status}");
                            return null;
                        }
                    }
                }

                // 若直接获取失败,则枚举已配对设备(备用方案)
                var devices = await DeviceInformation.FindAllAsync(BluetoothLEDevice.GetDeviceSelector());
                foreach (var di in devices)
                {
                    var device = await BluetoothLEDevice.FromIdAsync(di.Id);
                    if (device != null)
                    {
                        byte[] bytes = BitConverter.GetBytes(device.BluetoothAddress);
                        Array.Reverse(bytes);
                        string mac = BitConverter.ToString(bytes, 2, 6).Replace('-', ':').ToLower();
                        if (mac == MAC.ToLower())
                        {
                            CurrentDevice = device;
                            this.DeviceWatcherChanged?.Invoke(MsgType.BleDevice, device);
                            var servicesResult = await device.GetGattServicesAsync();
                            if (servicesResult.Status == GattCommunicationStatus.Success)
                            {
                                MessAgeChanged?.Invoke(MsgType.NotifyTxt, $"重连设备 {MAC} 成功");
                                return device;
                            }
                            else
                            {
                                MessAgeChanged?.Invoke(MsgType.NotifyTxt, $"重连失败: {servicesResult.Status}");
                                return null;
                            }
                        }
                    }
                }

                MessAgeChanged?.Invoke(MsgType.NotifyTxt, "未找到该MAC设备,启动扫描...");
                StartBleDeviceWatcher();
                return null;
            }
            catch (Exception ex)
            {
                MessAgeChanged?.Invoke(MsgType.NotifyTxt, $"重连异常: {ex.Message}");
                return null;
            }
        }


        public int GetMaxPacketSize()
        {
            try
            {
                if (CurrentService != null)
                {
                    var session = CurrentService.Session;
                    if (session != null)
                    {
                        int mtu = session.MaxPduSize;
                        if (mtu > 0)
                            return mtu - 3; // 减去 ATT 头部
                    }
                }
            }
            catch { }
            // 默认返回模块支持的最大值 244
            return 20;
        }
    }
}

5、结束语

  后续控制板的Bootloader 和APP跳转这些需要自己去钻研了,包括Ymodem协议的数据接收,包括地址的分配等;我的思路是:APP接收到升级跳转指令,APP程序跳转到Bootloader程序,跳转后等待升级(升级超时机制,升级重试机制等等),这些网上一堆,我主要想分享上位机的程序开发,希望能够对各位有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值