Преглед на файлове

升级包发送基本完成

djs преди 3 месеца
родител
ревизия
7119ea42b6

+ 14 - 1
MV485/Dlg/WaitWindow.xaml.cs

@@ -26,7 +26,20 @@ namespace MV485.Dlg
         {
             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
-        public string TitleInfo { get; set; }
+
+        private string _titleInfo;
+        public string TitleInfo
+        {
+            get => _titleInfo;
+            set
+            {
+                if (_titleInfo != value)
+                {
+                    _titleInfo = value;
+                    OnPropertyChanged(nameof(TitleInfo));
+                }
+            }
+        }
 
         public WaitWindow(string titleInfo)
         {

+ 1 - 0
MV485/MV485.csproj

@@ -208,6 +208,7 @@
       <DependentUpon>WndUpdateAsk.xaml</DependentUpon>
     </Compile>
     <Compile Include="util\AesEncryptionHelper.cs" />
+    <Compile Include="util\CRC32Helper.cs" />
     <Compile Include="util\LicenseMana.cs" />
     <Page Include="Dlg\AboutWindow.xaml">
       <SubType>Designer</SubType>

+ 18 - 0
MV485/db/DBUpgradeHis.cs

@@ -203,6 +203,24 @@ namespace MV485.db
                 return false;
             }
         }
+
+        public static bool DeleteUpgradeAllHis()
+        {
+            string sql = "DELETE FROM t_upgrade_his";
+            //SQLiteParameter[] parameters = { new SQLiteParameter("@DeviceSn", deviceSn) };
+
+            try
+            {
+                //return SQLiteHelper.ExecuteSql(sql, parameters) > 0;
+                return SQLiteHelper.ExecuteSql(sql) >= 0;
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine("DeleteUpgradeHisByDeviceSn Error: " + ex.Message);
+                return false;
+            }
+        }
+        //
     }
 
 }

+ 61 - 23
MV485/helper/DeviceUpgrade.cs

@@ -21,24 +21,31 @@ namespace MV485.helper
         private const byte C = 0x43;
 
         private SerialPort _serialPort;
-        private readonly int _packetSize = 1024;
+        private readonly int _packetSize = 128; //1024;
         private readonly int _maxRetry = 10;
-        private readonly int _ackTimeout = 2000;
+        private readonly int _ackTimeout = 500; //2000;
 
         private IProgress<string> _progress;
         private CancellationToken _cancellationToken;
 
         public bool IsOpen => _serialPort?.IsOpen ?? false;
 
-       
+        public event Action<int, int> OnProgress;
+
+        private string _message;
+
+        private string _portName;
+        private int _baudrate;
+        
         public DeviceUpgrade()
         {
             
         }
 
 
-        public bool OpenPort(string portName, int baudRate = 115200, int dataBits = 8, Parity parity = Parity.None, StopBits stopBits = StopBits.One)
+        private bool OpenPort(string portName, int baudRate = 115200, int dataBits = 8, Parity parity = Parity.None, StopBits stopBits = StopBits.One)
         {
+            ClosePort();
             try
             {
                 _serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits)
@@ -47,16 +54,19 @@ namespace MV485.helper
                     WriteTimeout = 1000
                 };
                 _serialPort.Open();
-
+                _progress?.Report($"串口{portName}连接成功");
+                Logger.Info($"串口{portName}连接成功");
                 return true;
             }
             catch (Exception)
             {
+                _progress?.Report($"串口{portName}连接失败");
+                Logger.Info($"串口{portName}连接失败");
                 return false;
             }
         }
 
-        public void ClosePort()
+        private void ClosePort()
         {
             try
             {
@@ -65,15 +75,25 @@ namespace MV485.helper
                     _serialPort.Close();
                     _serialPort.Dispose();
                     _serialPort = null;
+                    _message = $"串口{_portName}已关闭";
+                    _progress.Report(_message);
+                    Logger.Info(_message);
                 }
             }
-            catch { }
+            catch(Exception ex)
+            {
+                _message = $"关闭串口失败: {ex.Message}";
+                _progress?.Report(_message);
+                Logger.Info(_message);
+            }
         }
 
         //开始异步发送升级包文件
 
         public async Task<bool> StartSendFileAsync(IProgress<string> progress, string portName,int baudRate,byte[] fileData)
         {
+            _portName = portName;
+            _baudrate = baudRate;
             try
             {
                 _progress = progress;
@@ -99,20 +119,23 @@ namespace MV485.helper
             return false;
         }
 
-        public async Task<bool> SendFileAsync(byte[] fileData, IProgress<string> progress, CancellationToken cancellationToken)
+        private async Task<bool> SendFileAsync(byte[] fileData, IProgress<string> progress, CancellationToken cancellationToken)
         {
             //byte[] fileData = File.ReadAllBytes(filePath);
             //常用的 trick,用于模拟 向上取整除法
             int totalPackets = (fileData.Length + _packetSize - 1) / _packetSize;
 
             //这里一定是C,嵌入式设备已经定义好了,不需要处理其它情况
-            progress.Report("等待设备发出 'C' 信号...");
-            byte handshakeChar = await WaitForByteAsync(cancellationToken, C);
-            if(handshakeChar != C)
+            progress.Report("等待设备发出 'NAK' 信号...");
+            await Task.Delay(2000);
+            byte handshakeChar = await WaitForByteAsync(cancellationToken, NAK);
+            //progress.Report($"{handshakeChar:02x}");
+            if(handshakeChar != NAK)
             {
-                progress.Report("未收到 'C',设备未准备好。");
+                progress.Report("未收到 'NAK',设备未准备好。");
                 return false;
             }
+            progress.Report($"{(handshakeChar == NAK ?"已开始传输":"未收到NAK")}");
 
             for (int packetNum = 1; packetNum <= totalPackets; packetNum++)
             {
@@ -130,11 +153,12 @@ namespace MV485.helper
 
                 for (int attempt = 0; attempt < _maxRetry; attempt++)
                 {
-                    progress.Report($"发送第 {packetNum}/{totalPackets} 包,尝试 {attempt + 1}");
-
+                    //progress.Report($"发送第 {packetNum}/{totalPackets} 包,尝试 {attempt + 1}");
                     byte[] packet = BuildPacket(packetNum, dataBlock);
                     try
                     {
+                        //progress?.Report($"发送{packet.Length}字节");
+                        //progress?.Report(BitConverter.ToString(packet).Replace("-", ""));
                         _serialPort.Write(packet, 0, packet.Length);
                     }
                     catch (Exception ex)
@@ -146,14 +170,21 @@ namespace MV485.helper
                     var result = await WaitForByteAsync(cancellationToken,ACK,NAK,CAN);
                     if (result == ACK)
                     {
+                        //progress?.Report("接收到ACK");
+                        //发送成功
+                        OnProgress?.Invoke(packetNum, totalPackets);
                         break;
                     }
                     if(result == CAN)
                     {
-                        progress.Report("接收到取消信号,终止发送。");
+                        progress.Report("接收到CAN取消信号,终止发送。");
                         _serialPort.Write(new[] { CAN, CAN }, 0, 2);
                         return false;
                     }
+                    if(result == NAK)
+                    {
+                        progress?.Report("接收到NAK");
+                    }
 
                     if (attempt == _maxRetry - 1)
                     {
@@ -195,16 +226,23 @@ namespace MV485.helper
 
         private byte[] BuildPacket(int packetNum, byte[] data)
         {
-            byte[] packet = new byte[3 + _packetSize + 2];
-            packet[0] = STX;   //帧头     
+            byte[] packet = new byte[3 + _packetSize + 1];
+            packet[0] = SOH;     //STX;   //帧头     //改为128,1024的包有问题
             packet[1] = (byte)(packetNum % 256);            //包序号
             packet[2] = (byte)(255 - packet[1]);            //包序号反码
-            Array.Copy(data, 0, packet, 3, _packetSize);    
+            Array.Copy(data, 0, packet, 3, _packetSize);
 
             //计算CRC
-            ushort crc = CRC16_XModem(data, 0, _packetSize);
-            packet[3 + _packetSize] = (byte)(crc >> 8);
-            packet[4 + _packetSize] = (byte)(crc & 0xFF);
+            //ushort crc = CRC16_XModem(data, 0, _packetSize);
+
+            //packet[3 + _packetSize] = (byte)(crc >> 8);
+            //packet[4 + _packetSize] = (byte)(crc & 0xFF);
+            int sum = 0;
+            foreach(byte bt in data)
+            {
+                sum = (sum + bt) % 256;
+            }
+            packet[3 + _packetSize] = (byte)sum;
 
             return packet;
         }
@@ -220,8 +258,8 @@ namespace MV485.helper
                     if (Array.Exists(expected, b => b == (byte)value))
                         return (byte)value;
                 }
-                await Task.Delay(100, token);
-                timeout -= 100;
+                await Task.Delay(10, token);
+                timeout -= 10;
             }
             return 0;
         }

+ 131 - 2
MV485/helper/RWRunConfig.cs

@@ -372,9 +372,84 @@ namespace MV485.helper
                 RWLog?.Invoke(_message);
                 return null;
             }
+        }
+
+        public bool ReadFireware(string portName,int baudrate,byte devId,out string deviceSn,out string fireware)
+        {
+            bool blRead = false;
+            deviceSn = "";
+            fireware = "";
+
+            _portName = portName;
+            _baudrate = baudrate;
+            _address = devId;
 
+            if (OpenSerial(portName, baudrate))
+            {
+                blRead = GetFirewareInfo(devId, out deviceSn, out fireware);
+            }
+            CloseSerial();
+            return blRead;
+        }
+
+        private bool GetFirewareInfo(byte devId,out string outDeviceSn,out string outFireware)
+        {
+            outDeviceSn = "";
+            outFireware = "";
+
+            string readName = "";
+
+            try
+            {
+                ushort[] readRegisters;
+
+                readName = "固件版本";
+                readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
+                    Constant.MB_REGISTER_ADD_FIREWARE, Constant.MB_REGISTER_NUM_FIREWARE);
+                string fireware = $"{(readRegisters[0] & 0xFF).ToString("D2")}." +
+                    $"{(readRegisters[1] >> 8).ToString("D2")}." +
+                    $"{(readRegisters[1] & 0xFF).ToString("D2")}";
+                outFireware = fireware;
+                GenerateSendAndRecvHexLog(true, readName);
+                GenerateValueLog(readName, fireware);
+
+                readName = "设备SN";
+                readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
+                    Constant.MB_REGISTER_ADD_DEVICE_SN, Constant.MB_REGISTER_NUM_DEVICE_SN);
+                string deviceSn = Tools.SNBcdToString(readRegisters);
+                outDeviceSn = deviceSn;
+                GenerateSendAndRecvHexLog(true, deviceSn);
+                GenerateValueLog(readName, deviceSn);
+
+                return true;
+            }
+            catch (TimeoutException ex)
+            {
+                GenerateSendHexLog(readName);
+                _message = $"读取{readName}超时,{ex.Message}";
+                _lastErrorMessage = _message;
+                RWLog?.Invoke(_message);
+                return false;
+            }
+            catch (SlaveException ex)
+            {
+                GenerateSendHexLog(readName);
+                _message = $"读取{readName}Modbus错误,{ex.Message}";
+                _lastErrorMessage = _message;
+                RWLog?.Invoke(_message);
+                return false;
+            }
+            catch (Exception ex)
+            {
+                GenerateSendHexLog(readName);
+                _message = $"读取{readName}错误,{ex.Message}";
+                _lastErrorMessage = _message;
+                RWLog?.Invoke(_message);
+                return false;
+            }
         }
 
+
         //读取照片字节数
         public ushort ReadImageSize(string portName,int baudrate,byte devId)
         {
@@ -617,6 +692,60 @@ namespace MV485.helper
             return blWrite;
         }
 
+        //写升级包的信息
+        public bool WrtieUpgradeInfo(string portName,int baudrate,byte devId,uint fileSize,uint fileCrc32)
+        {
+            bool blWrite = false;
+            _portName = portName;
+            _baudrate = baudrate;
+            _address = devId;
+            if (OpenSerial(portName, baudrate))
+            {
+                blWrite = SetUpgradeInfo(devId, fileSize, fileCrc32);
+            }
+            CloseSerial();
+            return blWrite;
+        }
+
+        private bool SetUpgradeInfo(byte devId,uint fileSize,uint fileCrc32)
+        {
+            bool blWrite = false;
+            string writeName = "";
+            try
+            {
+                writeName = "升级文件信息";
+                ushort[] infos = new ushort[4];
+                infos[0] = (ushort)(fileSize >> 16);
+                infos[1] = (ushort)(fileSize & 0xFFFF);
+                infos[2] = (ushort)(fileCrc32 >> 16);
+                infos[3] = (ushort)(fileCrc32 & 0xFFFF);
+                _modbusMaster.WriteMultipleRegisters(devId, Constant.MB_REGISTER_ADD_UPGRADE_DATA, infos);
+                GenerateValueLog(writeName, infos);
+                GenerateSendAndRecvHexLog(false, writeName);
+
+                blWrite = true;
+            }
+            catch (TimeoutException ex)
+            {
+                GenerateSendHexLog(writeName);
+                _message = $"写入{writeName}超时,{ex.Message}";
+                RWLog?.Invoke(_message);
+            }
+            catch (SlaveException ex)
+            {
+                GenerateSendHexLog(writeName);
+                _message = $"写入{writeName}Modbus错误,{ex.Message}";
+                RWLog?.Invoke(_message);
+            }
+            catch (Exception ex)
+            {
+                GenerateSendHexLog(writeName);
+                _message = $"写入{writeName}错误,{ex.Message}";
+                RWLog?.Invoke(_message);
+            }
+            return blWrite;
+        }
+
 
         private bool OpenSerial(string portName, int baudRate)
         {
@@ -625,8 +754,8 @@ namespace MV485.helper
             {
                 //除去波特率外,其它参数默认为不会被修改
                 SerialPort serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);
-                serialPort.ReadTimeout = 100;      //读取的超时时间应该短一些
-                serialPort.WriteTimeout = 100;
+                serialPort.ReadTimeout = 1000;      //读取的超时时间应该短一些
+                serialPort.WriteTimeout = 1000;
                 serialPort.Open();
 
                 _serialPort = serialPort;

+ 31 - 0
MV485/helper/Tools.cs

@@ -1,8 +1,10 @@
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.IO.Ports;
 using System.Linq;
 using System.Text;
+using System.Text.RegularExpressions;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Media;
@@ -230,6 +232,35 @@ namespace MV485.helper
             }
             return $"{resultType}: 未知";
         }
+
+        public static bool TryParseFirmwareName(string fileName, out string versionString)
+        {
+            versionString = null;
+
+            // 正则匹配:aimr_aa后跟6个数字,后缀是.bin
+            var match = Regex.Match(fileName, @"^aimr_aa(\d{6})\.bin$", RegexOptions.IgnoreCase);
+            if (!match.Success)
+                return false;
+
+            string digits = match.Groups[1].Value; // 提取出的6个数字
+
+            // 将6位数字分成三组,每组2位
+            string part1 = int.Parse(digits.Substring(0, 2)).ToString();
+            string part2 = int.Parse(digits.Substring(2, 2)).ToString();
+            string part3 = int.Parse(digits.Substring(4, 2)).ToString();
+
+            versionString = $"{part1}.{part2}.{part3}";
+            return true;
+        }
+
+        public static bool IsFileSizeValid(string filePath, long maxSizeInBytes = 1 * 1024 * 1024)
+        {
+            if (!File.Exists(filePath))
+                return false;
+
+            long length = new FileInfo(filePath).Length;
+            return length <= maxSizeInBytes;
+        }
         //----------------------------------------------------------
     }
 

+ 5 - 0
MV485/model/Constant.cs

@@ -29,6 +29,11 @@ namespace MV485.model
         public const ushort MB_REGISTER_ADD_SAMPLE_FIRST_HOUR = 0x000F;     //首次采样时间
         public const ushort MB_REGISTER_NUM_SAMPLE_FIRST_HOUR = 1;
 
+        //写升级包信息
+        public const ushort MB_REGISTER_ADD_UPGRADE_DATA = 0x005C;      //升级包的基本信息
+        //前2个地址为升级包的大小,后2个字节为升级文件的CRC32
+        public const ushort MB_REGISTER_NUM_UPGRADE_DATA = 4;			//寄存器数量(4个)
+
         public const ushort MB_REGISTER_ADD_MODEL_NAME = 0x00D2;    //模块名称,如:0x4001,即RS485水表
         public const ushort MB_REGISTER_NUM_MODEL_NAME = 1;
 

+ 14 - 2
MV485/uc/UCDeviceUpgrade.xaml

@@ -97,10 +97,22 @@
                     </Grid.ColumnDefinitions>
                     <!--ComboBox x:Name="cmbNewFireware" Grid.Column="0"  Padding="5 0 0 0" Margin="0" FontSize="14" 
                         VerticalContentAlignment="Center" Height="28" IsEditable="True" />-->
-                    <TextBox Grid.Column="0" IsReadOnly="True" x:Name="txtNewFireware" VerticalContentAlignment="Center"
+                    <TextBox Grid.Column="0" IsReadOnly="True" x:Name="txtNewFirewarePath" VerticalContentAlignment="Center"
                                     Text="c:\sdfds.bin" FontSize="14" Height="26" Padding="5 0 0 0" />
-                    <Button x:Name="btnSelNewFireware" Content="..." Grid.Column="1" Height="28" Margin="5 0 0 0"/>
+                    <Button x:Name="btnSelNewFireware" Content="..." Click="BtnSelNewFireware_Click"
+                            Grid.Column="1" Height="28" Margin="5 0 0 0"/>
                 </Grid>
+
+                <Grid Height="50" Margin="10 0 10 0">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="65" />
+                        <ColumnDefinition Width="*" />
+                    </Grid.ColumnDefinitions>
+                    <TextBlock Grid.Column="0" Text="固件版本" FontSize="14" VerticalAlignment="Center" />
+                    <TextBox Grid.Column="1" IsReadOnly="True" x:Name="txtNewFireware" VerticalContentAlignment="Center"
+                                             Text="240505102203" FontSize="14" Height="26" Padding="5 0 0 0" />
+                </Grid>
+
                 <Grid Height="40">
                     <zdfflatui:FlatButton Grid.Column="1" x:Name="btnDeviceUpgrade" Background="#2196F3" Foreground="White" 
                                                           Content="开始升级固件"  Click="BtnDeviceUpgrade_Click" 

+ 489 - 23
MV485/uc/UCDeviceUpgrade.xaml.cs

@@ -1,6 +1,9 @@
-using MV485.db;
+using Microsoft.Win32;
+using MV485.db;
+using MV485.Dlg;
 using MV485.helper;
 using MV485.model;
+using MV485.util;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
@@ -34,8 +37,10 @@ namespace MV485.uc
 
         public int[] PageSizeOptions = new int[] { 10, 20, 50 };
 
-        private DeviceUpgrade _deviceUpgrade;
-        private Progress<string> _progress;
+        //private DeviceUpgrade _deviceUpgrade;
+        //private Progress<string> _progress;
+
+        private RWRunConfig _rwRunConfig;
 
         private static readonly string LogFilePath =
             Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Documents", "deviceUpgrade_log.txt");
@@ -119,11 +124,18 @@ namespace MV485.uc
             InitLoadItemsAndView(); //加载数据项及初始化页面内容
             LoadConfigLog();
 
-            _deviceUpgrade = new DeviceUpgrade();
-            _progress = new Progress<string>(msg =>
+            _rwRunConfig = new RWRunConfig();
+            _rwRunConfig.RWLog += (message) =>{
+                AppendLog(message);
+            };
+            _rwRunConfig.SerialConnected += (blConnected, portName, baudrate, address) =>
             {
-                AppendLog(msg);
-            });
+
+            };
+            _rwRunConfig.SerialStatusChanged += (blStatusChanged, portName, baudrate, address) =>
+            {
+
+            };
         }
 
         //读取并加载日志
@@ -181,7 +193,16 @@ namespace MV485.uc
             InitDataView();
             
             var upgradeFile = ConfigManager.Instance.GetConfigValue(ConfigKey.UpgradeFile, "");
-            txtNewFireware.Text = upgradeFile;
+            txtNewFirewarePath.Text = upgradeFile;
+            txtNewFireware.Text = "";
+            if (File.Exists(upgradeFile))
+            {
+                string fileNameOnly = Path.GetFileName(upgradeFile);
+                if(Tools.TryParseFirmwareName(fileNameOnly,out string version))
+                {
+                    txtNewFireware.Text = version;
+                }
+            }
         }
 
         private void InitDataView()
@@ -268,19 +289,253 @@ namespace MV485.uc
             }
         }
 
-        private void BtnReadFireware_Click(object sender, RoutedEventArgs e)
+        //读取设备版本的相关信息
+        private async void BtnReadFireware_Click(object sender, RoutedEventArgs e)
         {
+            if (!GetSerialParameter(out string portName, out byte devId, out int baudrate))
+            {
+                return;
+            }
+            InitDataView();
+
+            string titleInfo = "正在读取抄表器版本相关数据";
+            WaitWindow waitWindow = new WaitWindow(titleInfo)
+            {
+                Owner = Application.Current.MainWindow,
+                WindowStartupLocation = WindowStartupLocation.CenterOwner
+            };
+            waitWindow.Show();
+
+            try
+            {
+                string deviceSn = "";
+                string fireware ="";
+                bool blRead = await Task.Run(() =>
+                {
+                    return _rwRunConfig.ReadFireware(portName, baudrate, devId,out deviceSn,out fireware);
+                });
+                AppendLog($"读取抄表器版本相关数据{(blRead ? "成功" : "失败")}");
+                if (blRead)
+                {
+                    txtDeviceSn.Text = deviceSn;
+                    txtOldFireware.Text = fireware;
+                }
+                else
+                {
+                    MessageBox.Show(Application.Current.MainWindow, _rwRunConfig.GetLastError(), "错误",
+                        MessageBoxButton.OK, MessageBoxImage.Error);
+                }
+            }
+            catch { }
+            finally
+            {
+                waitWindow.Close();
+            }
 
         }
 
-        private void BtnDeviceUpgrade_Click(object sender, RoutedEventArgs e)
+        private async void BtnDeviceUpgrade_Click(object sender, RoutedEventArgs e)
         {
+            //判断串口参数是否正确
+            if (!GetSerialParameter(out string portName, out byte devId, out int baudrate))
+            {
+                return;
+            }
+
+            //判断是否读取了deviceSn,设备的当前版本
+            if(string.IsNullOrEmpty(txtDeviceSn.Text) || string.IsNullOrEmpty(txtOldFireware.Text))
+            {
+                MessageBox.Show(Application.Current.MainWindow,
+                    $"请先读取设备的当前版本", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+                return;
+            }
+            //判断新的版本是否存在
+            if (string.IsNullOrEmpty(txtNewFireware.Text) || string.IsNullOrEmpty(txtNewFirewarePath.Text))
+            {
+                MessageBox.Show(Application.Current.MainWindow,
+                    $"请选择要写入的新固件版本", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+                return;
+            }
+
+            string filePath = txtNewFirewarePath.Text.Trim();
+            //判断新的固件文件是否存在
+            if (!File.Exists(filePath))
+            {
+                MessageBox.Show(Application.Current.MainWindow,
+                    "新固件版本文件不存在", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+                return;
+            }
+
+            
+            //读取新固件的大小及数据的CRC32值
+
+            //计算CRC32的值
+            byte[] fileData = File.ReadAllBytes(filePath);
+            uint fileLen = (uint)fileData.Length;
+            if (fileLen > 1024 * 1024)
+            {
+                MessageBox.Show(Application.Current.MainWindow,
+                    "新固件版本文件大小不合理", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+                return;
+            }
+
+            uint fileCRC32 = CRC32Helper.Crc32(fileData, fileData.Length);
+
+            AppendLog($"fileSize={fileLen},fileCRC32={fileCRC32}");
+
+            //开始升级
+            //1.先向设备写入(通知)升级包文件的长度及CRC32的值
+            string titleInfo = "开始写入升级包的信息";
+            WaitWindow waitWindow = new WaitWindow(titleInfo)
+            {
+                Owner = Application.Current.MainWindow,
+                WindowStartupLocation = WindowStartupLocation.CenterOwner
+            };
+            waitWindow.Show();
+
+            try
+            {
+                //开始先设备写入文件信息
+                bool blWriteInfo = await Task.Run(() =>
+                {
+                    return _rwRunConfig.WrtieUpgradeInfo(portName, baudrate, devId, fileLen, fileCRC32);
+                });
+                if (!blWriteInfo)
+                {
+                    MessageBox.Show(Application.Current.MainWindow, "向设备写入固件信息失败", "错误",
+                        MessageBoxButton.OK, MessageBoxImage.Error);
+                    return;
+                }
+
+                //开始通信
+                DeviceUpgrade deviceUpgrade = new DeviceUpgrade();
+                Progress<string> progress = new Progress<string>(msg =>
+                {
+                    AppendLog(msg);
+                });
+                string percentText = "";
+                //$"已发送:{packNum}/{totalPacks}({(double)packNum / totalPacks:P1})";
+                deviceUpgrade.OnProgress += (packNum, totalPacks) =>
+                {
+                    waitWindow.Dispatcher.BeginInvoke(new Action(() =>
+                    {
+                        //percentText = $"已发送:{packNum}/{totalPacks}({(double)packNum / totalPacks:P1})";
+                        percentText = $"已下载:{(double)packNum / totalPacks:P2}";
+                        waitWindow.TitleInfo = percentText;
+                        //$"已发送{packNum}/{totalPacks}";
+                    }));
+                    //waitWindow.Dispatcher.InvokeAsync(() =>
+                    //{
+                    //    waitWindow.TitleInfo = $"已发送{packNum}/{totalPacks}";
+                    //});
+                };
+
+                bool blSend = await deviceUpgrade.StartSendFileAsync(progress, portName, baudrate, fileData);
+
+                if (blSend)
+                {
+                    //提示继续等待设备重启,并验证升级是否成功
+                    //MessageBox.Show(Application.Current.MainWindow,"向设备写入新固件成功,")
+
+                    MessageBoxResult result = MessageBox.Show("向设备写入新固件成功,设备即将重启。\n是否等待自动验证重启后的版本是否正确", 
+                        "确认", MessageBoxButton.YesNo, MessageBoxImage.Question);
+
+                    if (result != MessageBoxResult.No) return;
+
+                    //继续等待失败是否成功
+                    titleInfo = $"正在等待验证重启后版本是否正确(1~2分钟)";
+                    waitWindow.TitleInfo = titleInfo;
+
+                    //进入等待线程
+                    //开始及时
+                    string chDeviceSn="";
+                    string chFireware="";
+                    bool blWait = await Task.Run(() =>
+                    {
+                        int timeout = 2 * 60 * 1000;    //等待2分钟
+                        while(timeout > 0)
+                        {
+                            //读取版本信息
+                            bool blReadFireware = _rwRunConfig.ReadFireware(portName, baudrate, devId, out chDeviceSn, out chFireware);
+                            //if (blReadFireware)
+                            //{
+                            //return fireware.Equals(txtNewFireware.Text);
+                            //    return blReadFireware;
+                            //}
+                            if (blReadFireware) return true;
+                            Task.Delay(1000);
+                            timeout -= 1000;
+                        }                        
+                        return false;
+                    });
+
+                    if (blWait)
+                    {
+                        bool blSame = chDeviceSn.Equals(txtDeviceSn.Text) && chFireware.Equals(txtNewFireware.Text);
+                        MessageBox.Show(Application.Current.MainWindow, $"验证{(blSame ? "一致" : "不一致")}", "提示",
+                            MessageBoxButton.OK, MessageBoxImage.Information);
+                    }
+                    else
+                    {
+                        MessageBox.Show(Application.Current.MainWindow, "等待验证超时", "警告",
+                            MessageBoxButton.OK, MessageBoxImage.Warning);
+                    }
+                }
+                else
+                {
+                    MessageBox.Show(Application.Current.MainWindow, "向设备写入新固件时失败", "错误",
+                        MessageBoxButton.OK, MessageBoxImage.Error);
+                }
+            }
+            catch { }
+            finally
+            {
+                waitWindow.Close();
+            }
 
         }
 
-        private void BtnClearUpgradeHis_Click(object sender, RoutedEventArgs e)
+        private async void BtnClearUpgradeHis_Click(object sender, RoutedEventArgs e)
         {
+            if (SelectedHis == null) return;
+
+            MessageBoxResult result = MessageBox.Show("确定要清空所有数据吗?", "确认", MessageBoxButton.YesNo, MessageBoxImage.Question);
+            if (result != MessageBoxResult.Yes) return;
 
+
+            string titleInfo = "正在清空数据,请稍后...";
+            WaitWindow waitWindow = new WaitWindow(titleInfo)
+            {
+                Owner = Application.Current.MainWindow,
+                WindowStartupLocation = WindowStartupLocation.CenterOwner
+            };
+            waitWindow.Show();
+
+            try
+            {
+
+                bool deleteSuccess = false;
+                await Task.Run(() =>
+                {
+                    deleteSuccess = DBUpgradeHis.DeleteUpgradeAllHis();
+                });
+
+                if (deleteSuccess)
+                {
+                    DetailPage.InitDefaulValue();
+                    _hisList.Clear();
+                    HisRecords = 0;
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show(Application.Current.MainWindow, $"删除失败:{ex.Message}", "错误",
+                    MessageBoxButton.OK, MessageBoxImage.Error);
+            }
+            finally
+            {
+                waitWindow.Close();
+            }
         }
 
         private void BtnRefreshUpgradeHis_Click(object sender, RoutedEventArgs e)
@@ -289,30 +544,50 @@ namespace MV485.uc
             LoadHisList();
         }
 
-        private void BtnDelUpgradeHis_Click(object sender, RoutedEventArgs e)
-        {
-
-        }
-
 
         private void BtnClearLog_Click(object sender, RoutedEventArgs e)
         {
-
+            _logEntries.Clear();
+            lvLogs.ItemsSource = null;
+            File.WriteAllText(LogFilePath, "");
+            AppendLog("日志已清空");
         }
 
         private void UserControl_Unloaded(object sender, RoutedEventArgs e)
         {
 
         }
+        private void BtnDelUpgradeHis_Click(object sender, RoutedEventArgs e)
+        {
+            Button button = sender as Button;
+            if (button == null) return;
+
+            DataGridRow row = Tools.FindAncestor<DataGridRow>(button);
+            if (row == null) return;
+
+            //获取当前行绑定的数据项
+            TUpgradeHis his = row.Item as TUpgradeHis;
+            DeleteHis(his);
+        }
 
         private void MiDeleteHis_Click(object sender, RoutedEventArgs e)
         {
-
+            if (SelectedHis == null) return;
+            DeleteHis(SelectedHis);
         }
 
         private void MenuItem_CopyMessage_Click(object sender, RoutedEventArgs e)
         {
-
+            if (lvLogs.SelectedItem is LogEntry logEntry)
+            {
+                if (!LicenseMana.mIsLicensed)
+                {
+                    MessageBox.Show(Application.Current.MainWindow, "软件未注册,不能使用此功能。", "提示",
+                        MessageBoxButton.OK, MessageBoxImage.Information);
+                    return;
+                }
+                Clipboard.SetText(logEntry.Message);
+            }
         }
 
         private void CmbPortNames_SelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -322,27 +597,53 @@ namespace MV485.uc
 
         private void BtnDataFirstPage_Click(object sender, RoutedEventArgs e)
         {
-
+            if (DetailPage.PageNumber > 1)
+            {
+                DetailPage.PageNumber = 1;
+                LoadHisList();
+            }
         }
 
         private void BtnDataPrePage_Click(object sender, RoutedEventArgs e)
         {
-
+            if (DetailPage.PageNumber > 1)
+            {
+                DetailPage.PageNumber -= 1;
+                LoadHisList();
+            }
         }
 
         private void BtnDataNextPage_Click(object sender, RoutedEventArgs e)
         {
-
+            if (DetailPage.PageNumber < DetailPage.PageCount)
+            {
+                DetailPage.PageNumber += 1;
+                LoadHisList();
+            }
         }
 
         private void BtnDataLastPage_Click(object sender, RoutedEventArgs e)
         {
-
+            if (DetailPage.PageNumber < DetailPage.PageCount)
+            {
+                DetailPage.PageNumber = DetailPage.PageCount;
+                LoadHisList();
+            }
         }
 
         private void BtnDataSpeciPage_Click(object sender, RoutedEventArgs e)
         {
+            if (!int.TryParse(txtDataPageNumber.Text, out int pageNumber))
+            {
+                return;
+            }
 
+            if (pageNumber != DetailPage.PageNumber &&
+                pageNumber > 0 && pageNumber <= DetailPage.PageCount)
+            {
+                DetailPage.PageNumber = pageNumber;
+                LoadHisList();
+            }
         }
 
         private void AppendLog(string message)
@@ -365,6 +666,39 @@ namespace MV485.uc
             });
         }
 
+        //增加升级版本的历史
+        private async Task AddUpgradeItem(TUpgradeHis his)
+        {
+            if (his == null) return;
+
+            bool blInsert = await Task.Run(() =>
+            {
+                return DBUpgradeHis.InsertUpgradeHis(his);
+            });
+
+            if (!blInsert) return;
+
+            await Dispatcher.InvokeAsync( ()=>
+            {
+                _hisList.Insert(0, his);
+                HisRecords++;
+                DetailPage.PageCount = (int)Math.Ceiling(((double)HisRecords / DetailPage.PageSize));
+
+                if (_hisList.Count > DetailPage.PageSize)
+                {
+                    for (int i = _hisList.Count - 1; i >= DetailPage.PageSize; i--)
+                    {
+                        _hisList.Remove(_hisList[i]);
+                    }
+                }
+
+                for (int i = 0; i < _hisList.Count; i++)
+                {
+                    _hisList[i].Index = i + 1;
+                }
+            });
+        }
+
         private async void BtnClearDataA_Click(object sender, RoutedEventArgs e)
         {
             TUpgradeHis his = new TUpgradeHis()
@@ -397,7 +731,139 @@ namespace MV485.uc
                     _hisList.Remove(_hisList[i]);
                 }
             }
+        }
 
+        
+        private async void DeleteHis(TUpgradeHis his)
+        {
+            if (his == null) return;
+
+            MessageBoxResult result = MessageBox.Show("确定要删除此条目吗?", "确认删除", MessageBoxButton.YesNo, MessageBoxImage.Question);
+            if (result != MessageBoxResult.Yes) return;
+
+
+            string titleInfo = "正在删除,请稍后...";
+            WaitWindow waitWindow = new WaitWindow(titleInfo)
+            {
+                Owner = Application.Current.MainWindow,
+                WindowStartupLocation = WindowStartupLocation.CenterOwner
+            };
+            waitWindow.Show();
+
+            try
+            {
+
+                bool deleteSuccess = false;
+                await Task.Run(() =>
+                {
+                    deleteSuccess = DBUpgradeHis.DeleteUpgradeHisByHisId(his.HisId);
+                });
+
+                if (deleteSuccess)
+                {
+                    //int index = _slaveList.IndexOf(slave);
+                    _hisList.Remove(his);
+                    //for (int i = index; i < _slaveList.Count; i++)
+                    //{
+                    //    _slaveList[i].Index = i + 1;
+                    //}
+                    //改变它后面的Index
+                    SelectedHis = null;
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show(Application.Current.MainWindow, $"删除失败:{ex.Message}", "错误",
+                    MessageBoxButton.OK, MessageBoxImage.Error);
+            }
+            finally
+            {
+                waitWindow.Close();
+            }
+        }
+
+        private bool GetSerialParameter(out string sPortName, out byte bDevId, out int iBaudrate)
+        {
+            sPortName = "";
+            bDevId = 0;
+            iBaudrate = 0;
+            //串口号
+            if (cmbPortNames.SelectedItem == null)
+            {
+                MessageBox.Show(Application.Current.MainWindow, "请选择串口", "提示",
+                    MessageBoxButton.OK, MessageBoxImage.Warning);
+                return false;
+            }
+            var portName = cmbPortNames.SelectedValue.ToString();
+            ConfigManager.Instance.UpdateConfig(ConfigKey.ConfigPortName, portName);
+
+            //波特率
+            if (cmbBaudrate.SelectedItem == null)
+            {
+                MessageBox.Show(Application.Current.MainWindow, "请选择波特率", "提示",
+                    MessageBoxButton.OK, MessageBoxImage.Warning);
+                return false;
+            }
+            var sBaudrate = cmbBaudrate.SelectedValue.ToString();
+            ConfigManager.Instance.UpdateConfig(ConfigKey.ConfigBaudrate, sBaudrate);
+
+            if (!byte.TryParse(cmbDevId.Text, out byte devid) || devid < 0 || devid > 247)
+            {
+                MessageBox.Show(Application.Current.MainWindow, "请输入正确的设备地址", "提示",
+                    MessageBoxButton.OK, MessageBoxImage.Warning);
+                return false;
+            }
+            ConfigManager.Instance.UpdateConfig(ConfigKey.ConfigDevId, devid.ToString());
+
+            int baudrate = int.Parse(sBaudrate);
+
+
+            sPortName = portName;
+            bDevId = devid;
+            iBaudrate = baudrate;
+            return true;
+        }
+
+        
+        //选择新版本文件
+        private void BtnSelNewFireware_Click(object sender, RoutedEventArgs e)
+        {
+            // 打开文件选择对话框
+            var openFileDialog = new OpenFileDialog
+            {
+                Filter = "Firware Files (*.bin)|*.bin",
+                Title = "选择新固件文件"
+            };
+
+            if (openFileDialog.ShowDialog() == true)
+            {
+                // 显示选择的文件名
+                //txtExcelFile.Text = openFileDialog.FileName;
+                //if (string.IsNullOrWhiteSpace(txtStandName.Text.Trim()))
+                //{
+                //    txtStandName.Text = ThisApp.GetFileNameWithoutExtension(txtExcelFile.Text.Trim());
+                //}
+                string filePath = openFileDialog.FileName;
+                string fileNameOnly = openFileDialog.SafeFileName;
+                if(!Tools.TryParseFirmwareName(fileNameOnly,out string version))
+                {
+                    MessageBox.Show(Application.Current.MainWindow, "请选择正确的固件文件", "提示",
+                        MessageBoxButton.OK, MessageBoxImage.Warning);
+                    return;
+                }
+
+                if (!Tools.IsFileSizeValid(filePath))
+                {
+                    MessageBox.Show(Application.Current.MainWindow, "固件文件的大小超出合理范围", "提示",
+                        MessageBoxButton.OK, MessageBoxImage.Warning);
+                    return;
+                }
+
+                txtNewFirewarePath.Text = filePath;
+                txtNewFireware.Text = version;
+                ConfigManager.Instance.UpdateConfig(ConfigKey.UpgradeFile, filePath);
+                //txtNewFireware.Tag = version;       //存储版本
+            }
         }
         //---------------------------------------------------------
     }

+ 1 - 1
MV485/uc/UCRunConfig.xaml.cs

@@ -1074,7 +1074,7 @@ namespace MV485.uc
 
         private void AppendLog(string message)
         {
-            Dispatcher.Invoke(() =>
+            Dispatcher.InvokeAsync(() =>
             {
                 var logEntry = new LogEntry { Time = DateTime.Now.ToString("HH:mm:ss.fff"), Message = message };
                 _logEntries.Add(logEntry);

+ 91 - 0
MV485/util/CRC32Helper.cs

@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MV485.util
+{
+    public class CRC32Helper
+    {
+        private static readonly uint[] crc32tab = new uint[]
+        {
+            0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
+            0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+            0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+            0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+            0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
+            0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+            0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
+            0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+            0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+            0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+            0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
+            0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+            0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
+            0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+            0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+            0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+            0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
+            0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+            0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
+            0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+            0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+            0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+            0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
+            0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+            0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
+            0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+            0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+            0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+            0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
+            0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+            0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
+            0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+            0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+            0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+            0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
+            0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+            0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
+            0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+            0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+            0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+            0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
+            0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+            0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
+            0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+            0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+            0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+            0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
+            0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+            0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
+            0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+            0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+            0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+            0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
+            0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+            0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
+            0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+            0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+            0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+            0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
+            0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+            0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
+            0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+            0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+            0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+        };
+
+        public static uint Crc32(byte[] buffer, int size)
+        {
+            uint crc = 0xFFFFFFFF;
+
+            for (int i = 0; i < size; i++)
+            {
+                crc = crc32tab[(crc ^ buffer[i]) & 0xff] ^ (crc >> 8);
+            }
+
+            return crc ^ 0xFFFFFFFF;
+        }
+    }
+}