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;
}
//---------------------------------------------------------
}
}