UCDeviceUpgrade.xaml.cs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125
  1. using Microsoft.Win32;
  2. using MV485.db;
  3. using MV485.Dlg;
  4. using MV485.helper;
  5. using MV485.model;
  6. using MV485.util;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Collections.ObjectModel;
  10. using System.ComponentModel;
  11. using System.IO;
  12. using System.Linq;
  13. using System.Security.Cryptography;
  14. using System.Text;
  15. using System.Threading.Tasks;
  16. using System.Windows;
  17. using System.Windows.Controls;
  18. using System.Windows.Data;
  19. using System.Windows.Documents;
  20. using System.Windows.Input;
  21. using System.Windows.Media;
  22. using System.Windows.Media.Imaging;
  23. using System.Windows.Navigation;
  24. namespace MV485.uc
  25. {
  26. /// <summary>
  27. /// UCDeviceUpgrade.xaml 的交互逻辑
  28. /// </summary>
  29. public partial class UCDeviceUpgrade : UserControl, INotifyPropertyChanged
  30. {
  31. public event PropertyChangedEventHandler PropertyChanged;
  32. protected void OnPropertyChanged(string propertyName)
  33. {
  34. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  35. }
  36. // "\n烧写固件时需认真核对烧写文件,错误的文件回导致设备无法启动。\n" +
  37. public const string _warnInfo = "注意:要先读取到当前设备的版本信息后,再进行固件升级或回滚操作。" +
  38. "\n固件升级时建议修改波特率为115200,升级时间可控制在2分钟左右,波特率为9600时升级时长会超过10分钟。";
  39. public int[] PageSizeOptions = new int[] { 10, 20, 50 };
  40. //private DeviceUpgrade _deviceUpgrade;
  41. //private Progress<string> _progress;
  42. private RWRunConfig _rwRunConfig;
  43. private static readonly string LogFilePath =
  44. Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Documents", "deviceUpgrade_log.txt");
  45. private List<LogEntry> _logEntries = new List<LogEntry>();
  46. public ObservableCollection<TUpgradeHis> _hisList { get; set; }
  47. private int _hisRecords;
  48. public int HisRecords
  49. {
  50. get => _hisRecords;
  51. set
  52. {
  53. if(_hisRecords != value)
  54. {
  55. _hisRecords = value;
  56. OnPropertyChanged(nameof(HisRecords));
  57. }
  58. }
  59. }
  60. private TUpgradeHis _selectedHis;
  61. public TUpgradeHis SelectedHis
  62. {
  63. get => _selectedHis;
  64. set
  65. {
  66. if(_selectedHis != value)
  67. {
  68. _selectedHis = value;
  69. OnPropertyChanged(nameof(SelectedHis));
  70. }
  71. }
  72. }
  73. public PageModel DetailPage { get; set; }
  74. private int _curPageSize;
  75. public int CurPageSize
  76. {
  77. get => _curPageSize;
  78. set
  79. {
  80. if (_curPageSize != value)
  81. {
  82. _curPageSize = value;
  83. OnPropertyChanged(nameof(CurPageSize));
  84. DetailPage.InitDefaulValue(CurPageSize);
  85. LoadHisList();
  86. ConfigManager.Instance.UpdateConfig(ConfigKey.PageSize, _curPageSize.ToString());
  87. }
  88. }
  89. }
  90. public UCDeviceUpgrade()
  91. {
  92. InitializeComponent();
  93. this.DataContext = this;
  94. _hisList = new ObservableCollection<TUpgradeHis>();
  95. dgUpgradHis.ItemsSource = _hisList;
  96. cmbPageSize.ItemsSource = PageSizeOptions;
  97. if (int.TryParse(ConfigManager.Instance.GetConfigValue(ConfigKey.PageSize, "20"), out int curPageSize))
  98. {
  99. _curPageSize = curPageSize;
  100. }
  101. else
  102. {
  103. _curPageSize = 20;
  104. }
  105. DetailPage = new PageModel()
  106. {
  107. PageSize = _curPageSize,
  108. PageNumber = 1
  109. };
  110. LoadHisList();
  111. InitLoadItemsAndView(); //加载数据项及初始化页面内容
  112. LoadConfigLog();
  113. _rwRunConfig = new RWRunConfig();
  114. _rwRunConfig.RWLog += (message) =>{
  115. AppendLog(message);
  116. };
  117. _rwRunConfig.SerialConnected += (blConnected, portName, baudrate, address) =>
  118. {
  119. };
  120. _rwRunConfig.SerialStatusChanged += (blStatusChanged, portName, baudrate, address) =>
  121. {
  122. };
  123. txtWarmInfo.Text = _warnInfo;
  124. }
  125. //读取并加载日志
  126. private void LoadConfigLog()
  127. {
  128. Dispatcher.BeginInvoke(new Action(() =>
  129. {
  130. LoadLogs();
  131. }));
  132. }
  133. private void LoadLogs()
  134. {
  135. if (!File.Exists(LogFilePath))
  136. return;
  137. _logEntries.Clear(); // 先清空已有的日志记录
  138. //var lines = File.ReadAllLines("config_log.txt");
  139. var lines = File.ReadAllLines(LogFilePath);
  140. foreach (var line in lines)
  141. {
  142. var parts = line.Split(new[] { " - " }, 2, StringSplitOptions.None);
  143. if (parts.Length == 2)
  144. {
  145. _logEntries.Add(new LogEntry { Time = parts[0], Message = parts[1] });
  146. }
  147. }
  148. // 更新 ListView
  149. lvLogs.ItemsSource = null;
  150. lvLogs.ItemsSource = _logEntries;
  151. // 滚动到最后一条
  152. if (_logEntries.Count > 0)
  153. {
  154. lvLogs.ScrollIntoView(_logEntries[_logEntries.Count - 1]);
  155. }
  156. }
  157. //初始化加载页面项
  158. private void InitLoadItemsAndView()
  159. {
  160. LoadSerialPorts();
  161. //与设备通信的参数
  162. cmbBaudrate.ItemsSource = SerialHelper.BaudRates;
  163. var baudrates = ConfigManager.Instance.GetConfigValue(ConfigKey.ConfigBaudrate, "9600");
  164. cmbBaudrate.SelectedValue = baudrates;
  165. cmbDevId.ItemsSource = Enumerable.Range(1, 247).ToList();
  166. var devId = ConfigManager.Instance.GetConfigValue(ConfigKey.ConfigDevId, "1");
  167. cmbDevId.Text = devId;
  168. //初始化读数栏视图
  169. InitDataView();
  170. var upgradeFile = ConfigManager.Instance.GetConfigValue(ConfigKey.UpgradeFile, "");
  171. txtNewFirewarePath.Text = upgradeFile;
  172. txtNewFireware.Text = "";
  173. if (File.Exists(upgradeFile))
  174. {
  175. string fileNameOnly = Path.GetFileName(upgradeFile);
  176. if(Tools.TryParseFirmwareName(fileNameOnly,out string version))
  177. {
  178. txtNewFireware.Text = version;
  179. }
  180. }
  181. }
  182. private void InitDataView()
  183. {
  184. txtDeviceSn.Text = "";
  185. txtOldFireware.Text = "";
  186. }
  187. //加载串口列表
  188. private void LoadSerialPorts()
  189. {
  190. Dispatcher.BeginInvoke(new Action(() =>
  191. {
  192. cmbPortNames.ItemsSource = SerialHelper.GetSerialPortsWithDetails();
  193. if (cmbPortNames.Items.Count > 0)
  194. cmbPortNames.SelectedIndex = 0;
  195. //是否使用-基本配置选择的端口(应该用)
  196. var portName = ConfigManager.Instance.GetConfigValue(ConfigKey.ConfigPortName, "");
  197. cmbPortNames.SelectedValue = portName;
  198. }));
  199. }
  200. private void btnRefreshPorts_Click(object sender, RoutedEventArgs e)
  201. {
  202. LoadSerialPorts();
  203. AppendLog("串口列表已刷新");
  204. }
  205. private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
  206. {
  207. double totalWidth = lvLogs.ActualWidth;
  208. double timeColumnWidth = 120; // 时间列固定宽度
  209. double minMessageWidth = 200; // 消息列的最小宽度
  210. double newMessageWidth = Math.Max(minMessageWidth, totalWidth - timeColumnWidth - 40); // 计算新宽度
  211. ((GridView)lvLogs.View).Columns[1].Width = newMessageWidth;
  212. }
  213. //加载历史数据
  214. private async void LoadHisList()
  215. {
  216. _hisList.Clear();
  217. try
  218. {
  219. var result = await Task.Run(() =>
  220. {
  221. return DBUpgradeHis.GetPagedUpgradeHis(DetailPage.PageNumber, DetailPage.PageSize);
  222. });
  223. HisRecords = result.Item1;
  224. DetailPage.PageCount = result.Item2;
  225. var detailList = result.Item3;
  226. int index = 0;
  227. foreach (var detail in detailList)
  228. {
  229. index++;
  230. detail.Index = index + ((DetailPage.PageNumber - 1) * DetailPage.PageSize);
  231. _hisList.Add(detail);
  232. }
  233. }
  234. catch(Exception ex)
  235. {
  236. MessageBox.Show(Application.Current.MainWindow,
  237. $"加载历史数据时发生错误:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
  238. }
  239. }
  240. private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
  241. {
  242. if (e.Text.All(char.IsDigit))
  243. {
  244. e.Handled = false; // 如果是数字,允许输入
  245. }
  246. else
  247. {
  248. // 如果当前输入的字符不是数字,并且是中文输入法的拼音或候选字,禁止输入
  249. e.Handled = true;
  250. }
  251. }
  252. //读取设备版本的相关信息
  253. private async void BtnReadFireware_Click(object sender, RoutedEventArgs e)
  254. {
  255. if (!GetSerialParameter(out string portName, out byte devId, out int baudrate))
  256. {
  257. return;
  258. }
  259. InitDataView();
  260. string titleInfo = "正在读取抄表器版本相关数据";
  261. WaitWindow waitWindow = new WaitWindow(titleInfo)
  262. {
  263. Owner = Application.Current.MainWindow,
  264. WindowStartupLocation = WindowStartupLocation.CenterOwner
  265. };
  266. waitWindow.Show();
  267. try
  268. {
  269. string deviceSn = "";
  270. string fireware ="";
  271. bool blRead = await Task.Run(() =>
  272. {
  273. return _rwRunConfig.ReadFireware(portName, baudrate, devId,out deviceSn,out fireware);
  274. });
  275. AppendLog($"读取抄表器版本相关数据{(blRead ? "成功" : "失败")}");
  276. if (blRead)
  277. {
  278. txtDeviceSn.Text = deviceSn;
  279. txtOldFireware.Text = fireware;
  280. }
  281. else
  282. {
  283. MessageBox.Show(Application.Current.MainWindow, _rwRunConfig.GetLastError(), "错误",
  284. MessageBoxButton.OK, MessageBoxImage.Error);
  285. }
  286. }
  287. catch { }
  288. finally
  289. {
  290. waitWindow.Close();
  291. }
  292. }
  293. private async void BtnDeviceUpgrade_Click(object sender, RoutedEventArgs e)
  294. {
  295. //先验证bin文件
  296. string binPath = txtNewFirewarePath.Text.Trim();
  297. if (!File.Exists(binPath)) return;
  298. bool blResult = BinInspector.Inspect(binPath);
  299. if (!blResult)
  300. {
  301. MessageBox.Show(Application.Current.MainWindow,
  302. $"固件镜像文件验证不通过", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
  303. return;
  304. }
  305. //判断串口参数是否正确
  306. if (!GetSerialParameter(out string portName, out byte devId, out int baudrate))
  307. {
  308. return;
  309. }
  310. //判断是否读取了deviceSn,设备的当前版本
  311. if(string.IsNullOrEmpty(txtDeviceSn.Text) || string.IsNullOrEmpty(txtOldFireware.Text))
  312. {
  313. MessageBox.Show(Application.Current.MainWindow,
  314. $"请先读取设备的当前版本", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
  315. return;
  316. }
  317. //判断新的版本是否存在
  318. if (string.IsNullOrEmpty(txtNewFireware.Text) || string.IsNullOrEmpty(txtNewFirewarePath.Text))
  319. {
  320. MessageBox.Show(Application.Current.MainWindow,
  321. $"请选择要写入的新固件版本", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
  322. return;
  323. }
  324. string filePath = txtNewFirewarePath.Text.Trim();
  325. //判断新的固件文件是否存在
  326. if (!File.Exists(filePath))
  327. {
  328. MessageBox.Show(Application.Current.MainWindow,
  329. "新固件版本文件不存在", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
  330. return;
  331. }
  332. //读取新固件的大小及数据的CRC32值
  333. //计算CRC32的值
  334. byte[] fileData = File.ReadAllBytes(filePath);
  335. uint fileLen = (uint)fileData.Length;
  336. if (fileLen > 1024 * 1024)
  337. {
  338. MessageBox.Show(Application.Current.MainWindow,
  339. "新固件版本文件大小不合理", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
  340. return;
  341. }
  342. uint fileCRC32 = CRC32Helper.Crc32(fileData, fileData.Length);
  343. AppendLog($"fileSize={fileLen},fileCRC32={fileCRC32}");
  344. //开始升级
  345. //1.先向设备写入(通知)升级包文件的长度及CRC32的值
  346. string titleInfo = "开始写入升级包的信息";
  347. WaitUpgradeWindow waitWindow = new WaitUpgradeWindow(titleInfo)
  348. {
  349. Owner = Application.Current.MainWindow,
  350. WindowStartupLocation = WindowStartupLocation.CenterOwner
  351. };
  352. waitWindow.Show();
  353. Application.Current.MainWindow.IsEnabled = false;
  354. try
  355. {
  356. //开始先设备写入文件信息
  357. bool blWriteInfo = await Task.Run(() =>
  358. {
  359. return _rwRunConfig.WrtieUpgradeInfo(portName, baudrate, devId, fileLen, fileCRC32);
  360. });
  361. if (!blWriteInfo)
  362. {
  363. MessageBox.Show(Application.Current.MainWindow, "向设备写入固件信息失败", "错误",
  364. MessageBoxButton.OK, MessageBoxImage.Error);
  365. return;
  366. }
  367. //记录开始升级的信息
  368. TUpgradeHis upgradeHis = new TUpgradeHis()
  369. {
  370. HisId = Guid.NewGuid().ToString().Replace("-", ""),
  371. DeviceSn = txtDeviceSn.Text,
  372. PortName = portName,
  373. BaudRate = baudrate,
  374. Address = devId,
  375. OldFireware = txtOldFireware.Text,
  376. UpgradeTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
  377. NewFireware = txtNewFireware.Text,
  378. UpgradeResult = 0
  379. };
  380. await AddUpgradeItem(upgradeHis);
  381. //开始通信
  382. DeviceUpgrade deviceUpgrade = new DeviceUpgrade();
  383. Progress<string> progress = new Progress<string>(msg =>
  384. {
  385. AppendLog(msg);
  386. });
  387. string percentText = "";
  388. //$"已发送:{packNum}/{totalPacks}({(double)packNum / totalPacks:P1})";
  389. deviceUpgrade.OnProgress += (packNum, totalPacks) =>
  390. {
  391. waitWindow.Dispatcher.BeginInvoke(new Action(() =>
  392. {
  393. //percentText = $"已发送:{packNum}/{totalPacks}({(double)packNum / totalPacks:P1})";
  394. percentText = $"已下载:{(double)packNum / totalPacks:P1}";
  395. waitWindow.TitleInfo = percentText;
  396. //$"已发送{packNum}/{totalPacks}";
  397. }));
  398. //waitWindow.Dispatcher.InvokeAsync(() =>
  399. //{
  400. // waitWindow.TitleInfo = $"已发送{packNum}/{totalPacks}";
  401. //});
  402. };
  403. waitWindow.StopUpgrade += async () =>
  404. {
  405. deviceUpgrade.StopSendFile();
  406. upgradeHis.UpgradeResult = -1; //升级失败
  407. await UpdateUpgradeResult(upgradeHis);
  408. return;
  409. };
  410. bool blSend = await deviceUpgrade.StartSendFileAsync(progress, portName, baudrate, fileData);
  411. if (blSend)
  412. {
  413. upgradeHis.UpgradeResult = 1; //升级失败
  414. await UpdateUpgradeResult(upgradeHis);
  415. //提示继续等待设备重启,并验证升级是否成功
  416. //MessageBox.Show(Application.Current.MainWindow,"向设备写入新固件成功,")
  417. //MessageBoxResult result = MessageBox.Show("向设备写入新固件成功,设备即将重启。\n是否等待自动验证重启后的版本是否正确",
  418. // "确认", MessageBoxButton.YesNo, MessageBoxImage.Question);
  419. //if (result == MessageBoxResult.No) return;
  420. //继续等待失败是否成功
  421. titleInfo = $"向设备写入新固件成功,设备即将重启。\n正在等待验证重启后版本是否正确(1~2分钟)。";
  422. waitWindow.TitleInfo = titleInfo;
  423. //进入等待线程
  424. //开始及时
  425. string chDeviceSn="";
  426. string chFireware="";
  427. bool blWait = await Task.Run(() =>
  428. {
  429. int timeout = 2 * 60 * 1000; //等待2分钟
  430. while(timeout > 0)
  431. {
  432. //读取版本信息
  433. bool blReadFireware = _rwRunConfig.ReadFireware(portName, baudrate, devId, out chDeviceSn, out chFireware);
  434. if (blReadFireware) return true;
  435. Task.Delay(3000);
  436. timeout -= 1000;
  437. }
  438. return false;
  439. });
  440. if (blWait)
  441. {
  442. bool blSame = chDeviceSn.Equals(txtDeviceSn.Text) && chFireware.Equals(txtNewFireware.Text);
  443. MessageBox.Show(Application.Current.MainWindow, $"验证{(blSame ? "一致" : "不一致")}", "提示",
  444. MessageBoxButton.OK, MessageBoxImage.Information);
  445. }
  446. else
  447. {
  448. MessageBox.Show(Application.Current.MainWindow, "等待验证超时,请手动检查升级后的版本是否正确。", "警告",
  449. MessageBoxButton.OK, MessageBoxImage.Warning);
  450. }
  451. }
  452. else
  453. {
  454. upgradeHis.UpgradeResult = -1; //升级失败
  455. await UpdateUpgradeResult(upgradeHis);
  456. MessageBox.Show(Application.Current.MainWindow, "向设备写入新固件时失败", "错误",
  457. MessageBoxButton.OK, MessageBoxImage.Error);
  458. }
  459. }
  460. catch { }
  461. finally
  462. {
  463. waitWindow.Close();
  464. Application.Current.MainWindow.IsEnabled = true;
  465. }
  466. }
  467. private async void BtnClearUpgradeHis_Click(object sender, RoutedEventArgs e)
  468. {
  469. //if (SelectedHis == null) return;
  470. MessageBoxResult result = MessageBox.Show("确定要清空所有数据吗?", "确认", MessageBoxButton.YesNo, MessageBoxImage.Question);
  471. if (result != MessageBoxResult.Yes) return;
  472. string titleInfo = "正在清空数据,请稍后...";
  473. WaitWindow waitWindow = new WaitWindow(titleInfo)
  474. {
  475. Owner = Application.Current.MainWindow,
  476. WindowStartupLocation = WindowStartupLocation.CenterOwner
  477. };
  478. waitWindow.Show();
  479. try
  480. {
  481. bool deleteSuccess = false;
  482. await Task.Run(() =>
  483. {
  484. deleteSuccess = DBUpgradeHis.DeleteUpgradeAllHis();
  485. });
  486. if (deleteSuccess)
  487. {
  488. DetailPage.InitDefaulValue();
  489. _hisList.Clear();
  490. HisRecords = 0;
  491. }
  492. }
  493. catch (Exception ex)
  494. {
  495. MessageBox.Show(Application.Current.MainWindow, $"删除失败:{ex.Message}", "错误",
  496. MessageBoxButton.OK, MessageBoxImage.Error);
  497. }
  498. finally
  499. {
  500. waitWindow.Close();
  501. }
  502. }
  503. private void BtnRefreshUpgradeHis_Click(object sender, RoutedEventArgs e)
  504. {
  505. DetailPage.InitDefaulValue();
  506. LoadHisList();
  507. }
  508. private void BtnClearLog_Click(object sender, RoutedEventArgs e)
  509. {
  510. _logEntries.Clear();
  511. lvLogs.ItemsSource = null;
  512. File.WriteAllText(LogFilePath, "");
  513. AppendLog("日志已清空");
  514. }
  515. private void UserControl_Unloaded(object sender, RoutedEventArgs e)
  516. {
  517. }
  518. private void BtnDelUpgradeHis_Click(object sender, RoutedEventArgs e)
  519. {
  520. Button button = sender as Button;
  521. if (button == null) return;
  522. DataGridRow row = Tools.FindAncestor<DataGridRow>(button);
  523. if (row == null) return;
  524. //获取当前行绑定的数据项
  525. TUpgradeHis his = row.Item as TUpgradeHis;
  526. DeleteHis(his);
  527. }
  528. private void MiDeleteHis_Click(object sender, RoutedEventArgs e)
  529. {
  530. if (SelectedHis == null) return;
  531. DeleteHis(SelectedHis);
  532. }
  533. private void MenuItem_CopyMessage_Click(object sender, RoutedEventArgs e)
  534. {
  535. if (lvLogs.SelectedItem is LogEntry logEntry)
  536. {
  537. if (!LicenseMana.mIsLicensed)
  538. {
  539. MessageBox.Show(Application.Current.MainWindow, "软件未注册,不能使用此功能。", "提示",
  540. MessageBoxButton.OK, MessageBoxImage.Information);
  541. return;
  542. }
  543. Clipboard.SetText(logEntry.Message);
  544. }
  545. }
  546. private void CmbPortNames_SelectionChanged(object sender, SelectionChangedEventArgs e)
  547. {
  548. }
  549. private void BtnDataFirstPage_Click(object sender, RoutedEventArgs e)
  550. {
  551. if (DetailPage.PageNumber > 1)
  552. {
  553. DetailPage.PageNumber = 1;
  554. LoadHisList();
  555. }
  556. }
  557. private void BtnDataPrePage_Click(object sender, RoutedEventArgs e)
  558. {
  559. if (DetailPage.PageNumber > 1)
  560. {
  561. DetailPage.PageNumber -= 1;
  562. LoadHisList();
  563. }
  564. }
  565. private void BtnDataNextPage_Click(object sender, RoutedEventArgs e)
  566. {
  567. if (DetailPage.PageNumber < DetailPage.PageCount)
  568. {
  569. DetailPage.PageNumber += 1;
  570. LoadHisList();
  571. }
  572. }
  573. private void BtnDataLastPage_Click(object sender, RoutedEventArgs e)
  574. {
  575. if (DetailPage.PageNumber < DetailPage.PageCount)
  576. {
  577. DetailPage.PageNumber = DetailPage.PageCount;
  578. LoadHisList();
  579. }
  580. }
  581. private void BtnDataSpeciPage_Click(object sender, RoutedEventArgs e)
  582. {
  583. if (!int.TryParse(txtDataPageNumber.Text, out int pageNumber))
  584. {
  585. return;
  586. }
  587. if (pageNumber != DetailPage.PageNumber &&
  588. pageNumber > 0 && pageNumber <= DetailPage.PageCount)
  589. {
  590. DetailPage.PageNumber = pageNumber;
  591. LoadHisList();
  592. }
  593. }
  594. private void AppendLog(string message)
  595. {
  596. Dispatcher.Invoke(() =>
  597. {
  598. var logEntry = new LogEntry { Time = DateTime.Now.ToString("HH:mm:ss.fff"), Message = message };
  599. _logEntries.Add(logEntry);
  600. lvLogs.ItemsSource = null;
  601. lvLogs.ItemsSource = _logEntries;
  602. lvLogs.ScrollIntoView(logEntry);
  603. // 确保 Logs 目录存在
  604. Directory.CreateDirectory(Path.GetDirectoryName(LogFilePath));
  605. //File.AppendAllText("config_log.txt", $"{logEntry.Time} - {logEntry.Message}\n");
  606. // 追加日志
  607. File.AppendAllText(LogFilePath, $"{logEntry.Time} - {logEntry.Message}\n");
  608. });
  609. }
  610. //增加升级版本的历史
  611. private async Task AddUpgradeItem(TUpgradeHis his)
  612. {
  613. if (his == null) return;
  614. try
  615. {
  616. bool blInsert = await Task.Run(() =>
  617. {
  618. return DBUpgradeHis.InsertUpgradeHis(his);
  619. });
  620. if (!blInsert) return;
  621. await Dispatcher.InvokeAsync(() =>
  622. {
  623. _hisList.Insert(0, his);
  624. HisRecords++;
  625. DetailPage.PageCount = (int)Math.Ceiling(((double)HisRecords / DetailPage.PageSize));
  626. if (_hisList.Count > DetailPage.PageSize)
  627. {
  628. for (int i = _hisList.Count - 1; i >= DetailPage.PageSize; i--)
  629. {
  630. _hisList.Remove(_hisList[i]);
  631. }
  632. }
  633. for (int i = 0; i < _hisList.Count; i++)
  634. {
  635. _hisList[i].Index = i + 1;
  636. }
  637. });
  638. AppendLog("新增升级条目");
  639. }
  640. catch { }
  641. }
  642. private async Task UpdateUpgradeResult(TUpgradeHis his)
  643. {
  644. if (his == null) return;
  645. try
  646. {
  647. bool blUpdate = await Task.Run(() =>
  648. {
  649. return DBUpgradeHis.UpdateUpgradeResult(his);
  650. });
  651. AppendLog("更新条目的升级结果");
  652. }
  653. catch { }
  654. }
  655. private async Task UpdateUpgradeResultAndNewFireware(TUpgradeHis his)
  656. {
  657. if (his == null) return;
  658. try
  659. {
  660. bool blUpdate = await Task.Run(() =>
  661. {
  662. return DBUpgradeHis.UpdateUpgradeResultAndNewFrieware(his);
  663. });
  664. AppendLog("更新条目的升级结果");
  665. }
  666. catch { }
  667. }
  668. private async void BtnClearDataA_Click(object sender, RoutedEventArgs e)
  669. {
  670. TUpgradeHis his = new TUpgradeHis()
  671. {
  672. HisId = Guid.NewGuid().ToString().Replace("-", ""),
  673. DeviceSn = "12345678901",
  674. PortName = "COM6",
  675. BaudRate = 115200,
  676. Address = 1,
  677. OldFireware = "99.11.22",
  678. UpgradeTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
  679. NewFireware = "99.11.23",
  680. UpgradeResult = 1
  681. };
  682. bool blInsert = await Task.Run(() =>
  683. {
  684. return DBUpgradeHis.InsertUpgradeHis(his);
  685. });
  686. if (blInsert)
  687. {
  688. _hisList.Insert(0, his);
  689. HisRecords++;
  690. DetailPage.PageCount = (int)Math.Ceiling(((double)HisRecords / DetailPage.PageSize));
  691. }
  692. if (_hisList.Count > DetailPage.PageSize)
  693. {
  694. for (int i = _hisList.Count - 1; i >= 10; i--)
  695. {
  696. _hisList.Remove(_hisList[i]);
  697. }
  698. }
  699. }
  700. private async void DeleteHis(TUpgradeHis his)
  701. {
  702. if (his == null) return;
  703. MessageBoxResult result = MessageBox.Show("确定要删除此条目吗?", "确认删除", MessageBoxButton.YesNo, MessageBoxImage.Question);
  704. if (result != MessageBoxResult.Yes) return;
  705. string titleInfo = "正在删除,请稍后...";
  706. WaitWindow waitWindow = new WaitWindow(titleInfo)
  707. {
  708. Owner = Application.Current.MainWindow,
  709. WindowStartupLocation = WindowStartupLocation.CenterOwner
  710. };
  711. waitWindow.Show();
  712. try
  713. {
  714. bool deleteSuccess = false;
  715. await Task.Run(() =>
  716. {
  717. deleteSuccess = DBUpgradeHis.DeleteUpgradeHisByHisId(his.HisId);
  718. });
  719. if (deleteSuccess)
  720. {
  721. //int index = _slaveList.IndexOf(slave);
  722. _hisList.Remove(his);
  723. //for (int i = index; i < _slaveList.Count; i++)
  724. //{
  725. // _slaveList[i].Index = i + 1;
  726. //}
  727. //改变它后面的Index
  728. SelectedHis = null;
  729. }
  730. }
  731. catch (Exception ex)
  732. {
  733. MessageBox.Show(Application.Current.MainWindow, $"删除失败:{ex.Message}", "错误",
  734. MessageBoxButton.OK, MessageBoxImage.Error);
  735. }
  736. finally
  737. {
  738. waitWindow.Close();
  739. }
  740. }
  741. private bool GetSerialParameter(out string sPortName, out byte bDevId, out int iBaudrate)
  742. {
  743. sPortName = "";
  744. bDevId = 0;
  745. iBaudrate = 0;
  746. //串口号
  747. if (cmbPortNames.SelectedItem == null)
  748. {
  749. MessageBox.Show(Application.Current.MainWindow, "请选择串口", "提示",
  750. MessageBoxButton.OK, MessageBoxImage.Warning);
  751. return false;
  752. }
  753. var portName = cmbPortNames.SelectedValue.ToString();
  754. ConfigManager.Instance.UpdateConfig(ConfigKey.ConfigPortName, portName);
  755. //波特率
  756. if (cmbBaudrate.SelectedItem == null)
  757. {
  758. MessageBox.Show(Application.Current.MainWindow, "请选择波特率", "提示",
  759. MessageBoxButton.OK, MessageBoxImage.Warning);
  760. return false;
  761. }
  762. var sBaudrate = cmbBaudrate.SelectedValue.ToString();
  763. ConfigManager.Instance.UpdateConfig(ConfigKey.ConfigBaudrate, sBaudrate);
  764. if (!byte.TryParse(cmbDevId.Text, out byte devid) || devid < 0 || devid > 247)
  765. {
  766. MessageBox.Show(Application.Current.MainWindow, "请输入正确的设备地址", "提示",
  767. MessageBoxButton.OK, MessageBoxImage.Warning);
  768. return false;
  769. }
  770. ConfigManager.Instance.UpdateConfig(ConfigKey.ConfigDevId, devid.ToString());
  771. int baudrate = int.Parse(sBaudrate);
  772. sPortName = portName;
  773. bDevId = devid;
  774. iBaudrate = baudrate;
  775. return true;
  776. }
  777. //选择新版本文件
  778. private void BtnSelNewFireware_Click(object sender, RoutedEventArgs e)
  779. {
  780. // 打开文件选择对话框
  781. var openFileDialog = new OpenFileDialog
  782. {
  783. Filter = "Firware Files (*.bin)|*.bin",
  784. Title = "选择新固件文件"
  785. };
  786. if (openFileDialog.ShowDialog() == true)
  787. {
  788. // 显示选择的文件名
  789. //txtExcelFile.Text = openFileDialog.FileName;
  790. //if (string.IsNullOrWhiteSpace(txtStandName.Text.Trim()))
  791. //{
  792. // txtStandName.Text = ThisApp.GetFileNameWithoutExtension(txtExcelFile.Text.Trim());
  793. //}
  794. string filePath = openFileDialog.FileName;
  795. string fileNameOnly = openFileDialog.SafeFileName;
  796. if(!Tools.TryParseFirmwareName(fileNameOnly,out string version))
  797. {
  798. MessageBox.Show(Application.Current.MainWindow, "请选择正确的固件文件", "提示",
  799. MessageBoxButton.OK, MessageBoxImage.Warning);
  800. return;
  801. }
  802. if (!Tools.IsFileSizeValid(filePath))
  803. {
  804. MessageBox.Show(Application.Current.MainWindow, "固件文件的大小超出合理范围", "提示",
  805. MessageBoxButton.OK, MessageBoxImage.Warning);
  806. return;
  807. }
  808. txtNewFirewarePath.Text = filePath;
  809. txtNewFireware.Text = version;
  810. ConfigManager.Instance.UpdateConfig(ConfigKey.UpgradeFile, filePath);
  811. //txtNewFireware.Tag = version; //存储版本
  812. }
  813. }
  814. private async void BtnDeviceRollback_Click(object sender, RoutedEventArgs e)
  815. {
  816. if (!GetSerialParameter(out string portName, out byte devId, out int baudrate))
  817. {
  818. return;
  819. }
  820. //判断是否读取了deviceSn,设备的当前版本
  821. if (string.IsNullOrEmpty(txtDeviceSn.Text) || string.IsNullOrEmpty(txtOldFireware.Text))
  822. {
  823. MessageBox.Show(Application.Current.MainWindow,
  824. $"请先读取设备的当前版本", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
  825. return;
  826. }
  827. //开始通过xModem协议读取照片
  828. string titleInfo = "正在通知设备回滚固件版本";
  829. WaitWindow waitWindow = new WaitWindow(titleInfo)
  830. {
  831. Owner = Application.Current.MainWindow,
  832. WindowStartupLocation = WindowStartupLocation.CenterOwner
  833. };
  834. waitWindow.Show();
  835. Application.Current.MainWindow.IsEnabled = false;
  836. try
  837. {
  838. bool blWrite = await Task.Run(() =>
  839. {
  840. return _rwRunConfig.WriteDeviceRollback(portName, baudrate, devId);
  841. });
  842. string msgInfo = $"通知回滚固件版本{(blWrite ? "成功" : "失败")}";
  843. AppendLog(msgInfo);
  844. if (!blWrite)
  845. {
  846. //msgInfo += "\n抄表器会重启,请等待1-2分钟,再尝试读取设备的版本信息。";
  847. MessageBox.Show(Application.Current.MainWindow, "通知回滚固件版本失败", "错误",
  848. MessageBoxButton.OK, MessageBoxImage.Error);
  849. return;
  850. }
  851. TUpgradeHis upgradeHis = new TUpgradeHis()
  852. {
  853. HisId = Guid.NewGuid().ToString().Replace("-", ""),
  854. DeviceSn = txtDeviceSn.Text,
  855. PortName = portName,
  856. BaudRate = baudrate,
  857. Address = devId,
  858. //OldFireware = txtOldFireware.Text,
  859. UpgradeTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
  860. NewFireware = txtNewFireware.Text,
  861. UpgradeResult = 0
  862. };
  863. await AddUpgradeItem(upgradeHis);
  864. titleInfo = $"通知回滚固件版本成功,设备即将重启。\n正在等待验证重启后的版本(1~2分钟)。";
  865. waitWindow.TitleInfo = titleInfo;
  866. //进入等待线程
  867. //开始及时
  868. string chDeviceSn = "";
  869. string chFireware = "";
  870. bool blWait = await Task.Run(() =>
  871. {
  872. int timeout = 2 * 60 * 1000; //等待2分钟
  873. while (timeout > 0)
  874. {
  875. //读取版本信息
  876. bool blReadFireware = _rwRunConfig.ReadFireware(portName, baudrate, devId, out chDeviceSn, out chFireware);
  877. if (blReadFireware) return true;
  878. Task.Delay(3000);
  879. timeout -= 1000;
  880. }
  881. return false;
  882. });
  883. if (blWait)
  884. {
  885. upgradeHis.UpgradeResult = 2; //回滚成功标志
  886. upgradeHis.NewFireware = chFireware;
  887. await UpdateUpgradeResultAndNewFireware(upgradeHis);
  888. MessageBox.Show(Application.Current.MainWindow, $"回滚后的版本为{chFireware}", "提示",
  889. MessageBoxButton.OK, MessageBoxImage.Information);
  890. }
  891. else
  892. {
  893. MessageBox.Show(Application.Current.MainWindow, "等待验证超时,请手动检查回滚后的版本是否正确。", "警告",
  894. MessageBoxButton.OK, MessageBoxImage.Warning);
  895. }
  896. }
  897. catch { }
  898. finally
  899. {
  900. waitWindow.Close();
  901. }
  902. }
  903. private void BtnVerity_Click(object sender, RoutedEventArgs e)
  904. {
  905. string filePath = txtNewFirewarePath.Text;
  906. if (!File.Exists(filePath)) return;
  907. byte[] data = File.ReadAllBytes(filePath);
  908. // Step 1: 查找 TLV Info 位置(TLV 区域从 data 末尾往前查找 0x07 0x69)
  909. int tlvStart = FindTlvStart(data);
  910. if (tlvStart == -1)
  911. {
  912. Console.WriteLine("未找到 TLV 区域");
  913. return;
  914. }
  915. ushort magic = BitConverter.ToUInt16(data, tlvStart);
  916. ushort totalTlvSize = BitConverter.ToUInt16(data, tlvStart + 2);
  917. Console.WriteLine($"TLV Magic: 0x{magic:X4}, Total TLV Size: {totalTlvSize}");
  918. if (magic != IMAGE_TLV_INFO_MAGIC)
  919. {
  920. Console.WriteLine("TLV magic 不匹配,非合法 bin 文件");
  921. return;
  922. }
  923. int hashOffset = -1;
  924. for (int offset = tlvStart + 4; offset < tlvStart + totalTlvSize;)
  925. {
  926. byte type = data[offset];
  927. ushort len = BitConverter.ToUInt16(data, offset + 2);
  928. if (type == 0x10) // IMAGE_TLV_SHA256
  929. {
  930. hashOffset = offset + 4;
  931. Console.WriteLine($"找到 SHA256 TLV,长度: {len} 字节");
  932. break;
  933. }
  934. offset += 4 + len;
  935. }
  936. if (hashOffset == -1)
  937. {
  938. Console.WriteLine("未找到 SHA256 TLV");
  939. return;
  940. }
  941. byte[] hashFromFile = data.Skip(hashOffset).Take(32).ToArray();
  942. // Step 2: 计算 SHA256(从 offset 0 到 TLV 起始位置)
  943. int hashRegionLength = tlvStart; // 不包含 TLV
  944. byte[] hashRegion = data.Take(hashRegionLength).ToArray();
  945. byte[] hashCalculated;
  946. using (SHA256 sha256 = SHA256.Create())
  947. {
  948. hashCalculated = sha256.ComputeHash(hashRegion);
  949. }
  950. Console.WriteLine("文件中保存的 SHA256:");
  951. Console.WriteLine(BitConverter.ToString(hashFromFile).Replace("-", ""));
  952. Console.WriteLine("计算得到的 SHA256:");
  953. Console.WriteLine(BitConverter.ToString(hashCalculated).Replace("-", ""));
  954. bool match = hashFromFile.SequenceEqual(hashCalculated);
  955. Console.WriteLine(match ? "✅ 验证通过,文件完整" : "❌ 哈希不一致,文件可能被修改");
  956. }
  957. const int IMAGE_HEADER_SIZE = 0x400; // MCUBoot header 1024 bytes
  958. const int IMAGE_TLV_INFO_MAGIC = 0x6907;
  959. static int FindTlvStart(byte[] data)
  960. {
  961. // TLV 的起始在文件尾部往前扫描 0x07 0x69(即 0x6907,低字节在前)
  962. for (int i = data.Length - 4; i >= IMAGE_HEADER_SIZE; i--)
  963. {
  964. if (data[i] == 0x07 && data[i + 1] == 0x69)
  965. {
  966. return i;
  967. }
  968. }
  969. return -1;
  970. }
  971. //---------------------------------------------------------
  972. }
  973. }