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; using System.ComponentModel; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; namespace MV485.uc { /// /// UCDeviceUpgrade.xaml 的交互逻辑 /// public partial class UCDeviceUpgrade : UserControl, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } // "\n烧写固件时需认真核对烧写文件,错误的文件回导致设备无法启动。\n" + public const string _warnInfo = "注意:要先读取到当前设备的版本信息后,再进行固件升级或回滚操作。" + "\n固件升级时建议修改波特率为115200,升级时间可控制在2分钟左右,波特率为9600时升级时长会超过10分钟。"; public int[] PageSizeOptions = new int[] { 10, 20, 50 }; //private DeviceUpgrade _deviceUpgrade; //private Progress _progress; private RWRunConfig _rwRunConfig; private static readonly string LogFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Documents", "deviceUpgrade_log.txt"); private List _logEntries = new List(); public ObservableCollection _hisList { get; set; } private int _hisRecords; public int HisRecords { get => _hisRecords; set { if(_hisRecords != value) { _hisRecords = value; OnPropertyChanged(nameof(HisRecords)); } } } private TUpgradeHis _selectedHis; public TUpgradeHis SelectedHis { get => _selectedHis; set { if(_selectedHis != value) { _selectedHis = value; OnPropertyChanged(nameof(SelectedHis)); } } } public PageModel DetailPage { get; set; } private int _curPageSize; public int CurPageSize { get => _curPageSize; set { if (_curPageSize != value) { _curPageSize = value; OnPropertyChanged(nameof(CurPageSize)); DetailPage.InitDefaulValue(CurPageSize); LoadHisList(); ConfigManager.Instance.UpdateConfig(ConfigKey.PageSize, _curPageSize.ToString()); } } } public UCDeviceUpgrade() { InitializeComponent(); this.DataContext = this; _hisList = new ObservableCollection(); dgUpgradHis.ItemsSource = _hisList; cmbPageSize.ItemsSource = PageSizeOptions; if (int.TryParse(ConfigManager.Instance.GetConfigValue(ConfigKey.PageSize, "20"), out int curPageSize)) { _curPageSize = curPageSize; } else { _curPageSize = 20; } DetailPage = new PageModel() { PageSize = _curPageSize, PageNumber = 1 }; LoadHisList(); InitLoadItemsAndView(); //加载数据项及初始化页面内容 LoadConfigLog(); _rwRunConfig = new RWRunConfig(); _rwRunConfig.RWLog += (message) =>{ AppendLog(message); }; _rwRunConfig.SerialConnected += (blConnected, portName, baudrate, address) => { }; _rwRunConfig.SerialStatusChanged += (blStatusChanged, portName, baudrate, address) => { }; txtWarmInfo.Text = _warnInfo; } //读取并加载日志 private void LoadConfigLog() { Dispatcher.BeginInvoke(new Action(() => { LoadLogs(); })); } private void LoadLogs() { if (!File.Exists(LogFilePath)) return; _logEntries.Clear(); // 先清空已有的日志记录 //var lines = File.ReadAllLines("config_log.txt"); var lines = File.ReadAllLines(LogFilePath); foreach (var line in lines) { var parts = line.Split(new[] { " - " }, 2, StringSplitOptions.None); if (parts.Length == 2) { _logEntries.Add(new LogEntry { Time = parts[0], Message = parts[1] }); } } // 更新 ListView lvLogs.ItemsSource = null; lvLogs.ItemsSource = _logEntries; // 滚动到最后一条 if (_logEntries.Count > 0) { lvLogs.ScrollIntoView(_logEntries[_logEntries.Count - 1]); } } //初始化加载页面项 private void InitLoadItemsAndView() { LoadSerialPorts(); //与设备通信的参数 cmbBaudrate.ItemsSource = SerialHelper.BaudRates; var baudrates = ConfigManager.Instance.GetConfigValue(ConfigKey.ConfigBaudrate, "9600"); cmbBaudrate.SelectedValue = baudrates; cmbDevId.ItemsSource = Enumerable.Range(1, 247).ToList(); var devId = ConfigManager.Instance.GetConfigValue(ConfigKey.ConfigDevId, "1"); cmbDevId.Text = devId; //初始化读数栏视图 InitDataView(); var upgradeFile = ConfigManager.Instance.GetConfigValue(ConfigKey.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() { txtDeviceSn.Text = ""; txtOldFireware.Text = ""; } //加载串口列表 private void LoadSerialPorts() { Dispatcher.BeginInvoke(new Action(() => { cmbPortNames.ItemsSource = SerialHelper.GetSerialPortsWithDetails(); if (cmbPortNames.Items.Count > 0) cmbPortNames.SelectedIndex = 0; //是否使用-基本配置选择的端口(应该用) var portName = ConfigManager.Instance.GetConfigValue(ConfigKey.ConfigPortName, ""); cmbPortNames.SelectedValue = portName; })); } private void btnRefreshPorts_Click(object sender, RoutedEventArgs e) { LoadSerialPorts(); AppendLog("串口列表已刷新"); } private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e) { double totalWidth = lvLogs.ActualWidth; double timeColumnWidth = 120; // 时间列固定宽度 double minMessageWidth = 200; // 消息列的最小宽度 double newMessageWidth = Math.Max(minMessageWidth, totalWidth - timeColumnWidth - 40); // 计算新宽度 ((GridView)lvLogs.View).Columns[1].Width = newMessageWidth; } //加载历史数据 private async void LoadHisList() { _hisList.Clear(); try { var result = await Task.Run(() => { return DBUpgradeHis.GetPagedUpgradeHis(DetailPage.PageNumber, DetailPage.PageSize); }); HisRecords = result.Item1; DetailPage.PageCount = result.Item2; var detailList = result.Item3; int index = 0; foreach (var detail in detailList) { index++; detail.Index = index + ((DetailPage.PageNumber - 1) * DetailPage.PageSize); _hisList.Add(detail); } } catch(Exception ex) { MessageBox.Show(Application.Current.MainWindow, $"加载历史数据时发生错误:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e) { if (e.Text.All(char.IsDigit)) { e.Handled = false; // 如果是数字,允许输入 } else { // 如果当前输入的字符不是数字,并且是中文输入法的拼音或候选字,禁止输入 e.Handled = true; } } //读取设备版本的相关信息 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 async void BtnDeviceUpgrade_Click(object sender, RoutedEventArgs e) { //先验证bin文件 string binPath = txtNewFirewarePath.Text.Trim(); if (!File.Exists(binPath)) return; bool blResult = BinInspector.Inspect(binPath); if (!blResult) { MessageBox.Show(Application.Current.MainWindow, $"固件镜像文件验证不通过", "错误", MessageBoxButton.OK, MessageBoxImage.Error); return; } //判断串口参数是否正确 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 = "开始写入升级包的信息"; WaitUpgradeWindow waitWindow = new WaitUpgradeWindow(titleInfo) { Owner = Application.Current.MainWindow, WindowStartupLocation = WindowStartupLocation.CenterOwner }; waitWindow.Show(); Application.Current.MainWindow.IsEnabled = false; 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; } //记录开始升级的信息 TUpgradeHis upgradeHis = new TUpgradeHis() { HisId = Guid.NewGuid().ToString().Replace("-", ""), DeviceSn = txtDeviceSn.Text, PortName = portName, BaudRate = baudrate, Address = devId, OldFireware = txtOldFireware.Text, UpgradeTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), NewFireware = txtNewFireware.Text, UpgradeResult = 0 }; await AddUpgradeItem(upgradeHis); //开始通信 DeviceUpgrade deviceUpgrade = new DeviceUpgrade(); Progress progress = new Progress(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:P1}"; waitWindow.TitleInfo = percentText; //$"已发送{packNum}/{totalPacks}"; })); //waitWindow.Dispatcher.InvokeAsync(() => //{ // waitWindow.TitleInfo = $"已发送{packNum}/{totalPacks}"; //}); }; waitWindow.StopUpgrade += async () => { deviceUpgrade.StopSendFile(); upgradeHis.UpgradeResult = -1; //升级失败 await UpdateUpgradeResult(upgradeHis); return; }; bool blSend = await deviceUpgrade.StartSendFileAsync(progress, portName, baudrate, fileData); if (blSend) { upgradeHis.UpgradeResult = 1; //升级失败 await UpdateUpgradeResult(upgradeHis); //提示继续等待设备重启,并验证升级是否成功 //MessageBox.Show(Application.Current.MainWindow,"向设备写入新固件成功,") //MessageBoxResult result = MessageBox.Show("向设备写入新固件成功,设备即将重启。\n是否等待自动验证重启后的版本是否正确", // "确认", MessageBoxButton.YesNo, MessageBoxImage.Question); //if (result == MessageBoxResult.No) return; //继续等待失败是否成功 titleInfo = $"向设备写入新固件成功,设备即将重启。\n正在等待验证重启后版本是否正确(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 true; Task.Delay(3000); 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 { upgradeHis.UpgradeResult = -1; //升级失败 await UpdateUpgradeResult(upgradeHis); MessageBox.Show(Application.Current.MainWindow, "向设备写入新固件时失败", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } catch { } finally { waitWindow.Close(); Application.Current.MainWindow.IsEnabled = true; } } 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) { DetailPage.InitDefaulValue(); LoadHisList(); } 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(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) { } 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) { Dispatcher.Invoke(() => { var logEntry = new LogEntry { Time = DateTime.Now.ToString("HH:mm:ss.fff"), Message = message }; _logEntries.Add(logEntry); lvLogs.ItemsSource = null; lvLogs.ItemsSource = _logEntries; lvLogs.ScrollIntoView(logEntry); // 确保 Logs 目录存在 Directory.CreateDirectory(Path.GetDirectoryName(LogFilePath)); //File.AppendAllText("config_log.txt", $"{logEntry.Time} - {logEntry.Message}\n"); // 追加日志 File.AppendAllText(LogFilePath, $"{logEntry.Time} - {logEntry.Message}\n"); }); } //增加升级版本的历史 private async Task AddUpgradeItem(TUpgradeHis his) { if (his == null) return; try { 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; } }); AppendLog("新增升级条目"); } catch { } } private async Task UpdateUpgradeResult(TUpgradeHis his) { if (his == null) return; try { bool blUpdate = await Task.Run(() => { return DBUpgradeHis.UpdateUpgradeResult(his); }); AppendLog("更新条目的升级结果"); } catch { } } private async Task UpdateUpgradeResultAndNewFireware(TUpgradeHis his) { if (his == null) return; try { bool blUpdate = await Task.Run(() => { return DBUpgradeHis.UpdateUpgradeResultAndNewFrieware(his); }); AppendLog("更新条目的升级结果"); } catch { } } private async void BtnClearDataA_Click(object sender, RoutedEventArgs e) { TUpgradeHis his = new TUpgradeHis() { HisId = Guid.NewGuid().ToString().Replace("-", ""), DeviceSn = "12345678901", PortName = "COM6", BaudRate = 115200, Address = 1, OldFireware = "99.11.22", UpgradeTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), NewFireware = "99.11.23", UpgradeResult = 1 }; bool blInsert = await Task.Run(() => { return DBUpgradeHis.InsertUpgradeHis(his); }); if (blInsert) { _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 >= 10; i--) { _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; //存储版本 } } private async void BtnDeviceRollback_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; } //开始通过xModem协议读取照片 string titleInfo = "正在通知设备回滚固件版本"; WaitWindow waitWindow = new WaitWindow(titleInfo) { Owner = Application.Current.MainWindow, WindowStartupLocation = WindowStartupLocation.CenterOwner }; waitWindow.Show(); Application.Current.MainWindow.IsEnabled = false; try { bool blWrite = await Task.Run(() => { return _rwRunConfig.WriteDeviceRollback(portName, baudrate, devId); }); string msgInfo = $"通知回滚固件版本{(blWrite ? "成功" : "失败")}"; AppendLog(msgInfo); if (!blWrite) { //msgInfo += "\n抄表器会重启,请等待1-2分钟,再尝试读取设备的版本信息。"; MessageBox.Show(Application.Current.MainWindow, "通知回滚固件版本失败", "错误", MessageBoxButton.OK, MessageBoxImage.Error); return; } TUpgradeHis upgradeHis = new TUpgradeHis() { HisId = Guid.NewGuid().ToString().Replace("-", ""), DeviceSn = txtDeviceSn.Text, PortName = portName, BaudRate = baudrate, Address = devId, //OldFireware = txtOldFireware.Text, UpgradeTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), NewFireware = txtNewFireware.Text, UpgradeResult = 0 }; await AddUpgradeItem(upgradeHis); titleInfo = $"通知回滚固件版本成功,设备即将重启。\n正在等待验证重启后的版本(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 true; Task.Delay(3000); timeout -= 1000; } return false; }); if (blWait) { upgradeHis.UpgradeResult = 2; //回滚成功标志 upgradeHis.NewFireware = chFireware; await UpdateUpgradeResultAndNewFireware(upgradeHis); MessageBox.Show(Application.Current.MainWindow, $"回滚后的版本为{chFireware}", "提示", MessageBoxButton.OK, MessageBoxImage.Information); } else { MessageBox.Show(Application.Current.MainWindow, "等待验证超时,请手动检查回滚后的版本是否正确。", "警告", MessageBoxButton.OK, MessageBoxImage.Warning); } } catch { } finally { waitWindow.Close(); } } private void BtnVerity_Click(object sender, RoutedEventArgs e) { string filePath = txtNewFirewarePath.Text; if (!File.Exists(filePath)) return; byte[] data = File.ReadAllBytes(filePath); // Step 1: 查找 TLV Info 位置(TLV 区域从 data 末尾往前查找 0x07 0x69) int tlvStart = FindTlvStart(data); if (tlvStart == -1) { Console.WriteLine("未找到 TLV 区域"); return; } ushort magic = BitConverter.ToUInt16(data, tlvStart); ushort totalTlvSize = BitConverter.ToUInt16(data, tlvStart + 2); Console.WriteLine($"TLV Magic: 0x{magic:X4}, Total TLV Size: {totalTlvSize}"); if (magic != IMAGE_TLV_INFO_MAGIC) { Console.WriteLine("TLV magic 不匹配,非合法 bin 文件"); return; } int hashOffset = -1; for (int offset = tlvStart + 4; offset < tlvStart + totalTlvSize;) { byte type = data[offset]; ushort len = BitConverter.ToUInt16(data, offset + 2); if (type == 0x10) // IMAGE_TLV_SHA256 { hashOffset = offset + 4; Console.WriteLine($"找到 SHA256 TLV,长度: {len} 字节"); break; } offset += 4 + len; } if (hashOffset == -1) { Console.WriteLine("未找到 SHA256 TLV"); return; } byte[] hashFromFile = data.Skip(hashOffset).Take(32).ToArray(); // Step 2: 计算 SHA256(从 offset 0 到 TLV 起始位置) int hashRegionLength = tlvStart; // 不包含 TLV byte[] hashRegion = data.Take(hashRegionLength).ToArray(); byte[] hashCalculated; using (SHA256 sha256 = SHA256.Create()) { hashCalculated = sha256.ComputeHash(hashRegion); } Console.WriteLine("文件中保存的 SHA256:"); Console.WriteLine(BitConverter.ToString(hashFromFile).Replace("-", "")); Console.WriteLine("计算得到的 SHA256:"); Console.WriteLine(BitConverter.ToString(hashCalculated).Replace("-", "")); bool match = hashFromFile.SequenceEqual(hashCalculated); Console.WriteLine(match ? "✅ 验证通过,文件完整" : "❌ 哈希不一致,文件可能被修改"); } const int IMAGE_HEADER_SIZE = 0x400; // MCUBoot header 1024 bytes const int IMAGE_TLV_INFO_MAGIC = 0x6907; static int FindTlvStart(byte[] data) { // TLV 的起始在文件尾部往前扫描 0x07 0x69(即 0x6907,低字节在前) for (int i = data.Length - 4; i >= IMAGE_HEADER_SIZE; i--) { if (data[i] == 0x07 && data[i + 1] == 0x69) { return i; } } return -1; } //--------------------------------------------------------- } }