每个作品都是产品
C# WPF版效果:
C# winForm版效果:
一.布局设计UI
1.主体:grid 2行 2列
00 下载按钮 20% 01进度条 80% (同时显示百分比)
10 11都是跨列 显示日志
2.细节:百分比与进度条Value绑定。下载按钮默认获得焦点回车就能点击。日志只读等。各控件Name命名等。百分比水平垂直居中等。
(&D)快捷方式设置不了?
我想百分比后加%,绑定时如何设置?我知道用程序设置。
<Window x:Class="WpfM20UpdateFW.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfM20UpdateFW"
mc:Ignorable="d"
Title="M20 Update Firmware V1.0" Height="450" Width="800" Closed="Window_Closed">
<Grid>
<Grid ShowGridLines="false" MinWidth="20" FocusManager.FocusedElement="{Binding ElementName=download}">
<Grid.RowDefinitions>
<RowDefinition Height="0.2*"></RowDefinition>
<RowDefinition Height="0.8*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*"></ColumnDefinition>
<ColumnDefinition Width="0.8*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button x:Name="download" Grid.Row="0" Grid.Column="0" Click="Button_Click" >Download FW</Button>
<ProgressBar x:Name="Progress" Grid.Row="0" Grid.Column="1" />
<Label Grid.Row="0" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding ElementName=Progress,Path=Value}" FontSize="48"/>
<TextBox x:Name="logText" Grid.Row="1" Grid.ColumnSpan="2" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" AcceptsReturn="True" IsReadOnly="True" />
</Grid>
</Grid>
</Window>
二、程序代码
1.生成各控件的方法
2.协议(固件下载),与我协议一致,都通用。
07首包 4字节固件长度+32字节固件MD5大写字符串
08中间包 1024字节每包,分包,分成多个中间包,最后一包发剩下(不剩就不发)
09尾包
协议框架:头1字节+APDU长度2字节+APDU内容+CRC16 2字节
APDU结构:FC +功能码一字节(07,08,09)+ 00 00 00 +DATA长度2字节+DATA内容
3.流程
开串口,准备数据等。
线程一 清响应 发送 等信号或超时 有信号且结果正确,继续发送。发送完成显结果关串口等。
线程二 接收响应 解析结果 发信号 持续接收。
4.附加功能
首次选择文件后,后续 插拨设备自动批量下载。监听USB插拨。
拖放文件到界面。winForm版我做了。
5.其他数据转换等功能API
6.细节
退出应用,关闭线程,关闭串口。
再次点下载,变为停止。
日志写入文件。
下载成功或失败 日志区变为绿色或红色。
支持全屏。
using FT_Tools;
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO.Ports;
using System.Management;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using static FT_Tools.MySerialPort;
namespace WpfM20UpdateFW
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
private bool responseIsOK = false;
private Stopwatch sw;
private bool thread_isRunning = false;
private Thread thread = null;
public static string templatePathName = @""; //资源号或本地文件名
private EventWaitHandle _waitHandle = new AutoResetEvent(false);
public MainWindow()
{
InitializeComponent();
//插入设备
//Description = USB Composite Device
//DeviceID = USB\VID_2C7C & PID_0901\5 & 352FD79 & 0 & 1
WqlEventQuery insertQuery = new WqlEventQuery("SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_USBHub'");
ManagementEventWatcher insertWatcher = new ManagementEventWatcher(insertQuery);
insertWatcher.EventArrived += (s, e) =>
{
//Log("M20 Arrived");
var instance = (ManagementBaseObject)e.NewEvent["TargetInstance"];
//var description = instance.Properties["Description"];
//Log(description.Name + " = " + description.Value);
var deviceId = instance.Properties["DeviceID"];
//Log(deviceId.Name + " = " + deviceId.Value);
if (deviceId.Value.ToString().Contains("VID_2C7C&PID_0901"))
{
Log("M20 Arrived");
if (templatePathName.Length > 0)
{
thread = new Thread(new ThreadStart(ThreadDownload));
thread.Start();
}
}
};
insertWatcher.Start();
WqlEventQuery removeQuery = new WqlEventQuery("SELECT * FROM __InstanceDeletionEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_USBHub'");
ManagementEventWatcher removeWatcher = new ManagementEventWatcher(removeQuery);
removeWatcher.EventArrived += (s, e) =>
{
//Log("M20 Removed");
var instance = (ManagementBaseObject)e.NewEvent["TargetInstance"];
//var description = instance.Properties["Description"];
//Log(description.Name + " = " + description.Value);
var deviceId = instance.Properties["DeviceID"];
//Log(deviceId.Name + " = " + deviceId.Value);
if (deviceId.Value.ToString().Contains("VID_2C7C&PID_0901"))
{
Log("M20 Removed");
}
};
removeWatcher.Start();
}
private SerialPort serialPort = new SerialPort();
private void SerialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
if (serialPort.BytesToRead > 0)
{
byte[] buffer = new byte[serialPort.BytesToRead];
int length = serialPort.Read(buffer, 0, buffer.Length);
responseIsOK=reponse_Check(buffer, length);
_waitHandle.Set(); // 唤醒等待
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (thread_isRunning)
{
Window_Closed(null,null);
logText.Text="";
Log("Stop Download");//显示在最后
}
else
{
var openFileDialog = new Microsoft.Win32.OpenFileDialog()
{
Filter = "FW File|*.bin",
Multiselect = false
};
var result = openFileDialog.ShowDialog();
if (result==true)
{
templatePathName = openFileDialog.FileName;
thread = new Thread(new ThreadStart(ThreadDownload));
thread.Start();
}
}
}
public void ThreadDownload()
{
if (thread_isRunning) { Log("There are unfinished tasks, please wait..."); return; }
thread_isRunning = true;
sw = new Stopwatch();//计时
sw.Start();
bool b = ThreadDownload2();///
sw.Stop();
Log((b ? "Download success---------------" : "Download fail ***************") + ".Time consumption:" + sw.ElapsedMilliseconds + "ms");
Log(b);
if (serialPort.IsOpen) { serialPort.Close(); }
thread_isRunning = false;
}
public bool ThreadDownload2()
{
Log("FW File:" + templatePathName);
byte[] fBuffer = MyAPI.readFile(Log, templatePathName);
if (fBuffer == null || fBuffer.Length == 0) { Log("fBuffer is empty"); return false; }
string md5 = MyAPI.ComputeMD5(fBuffer);
Log("fBuffer Size:" + fBuffer.Length + " md5:" + md5);
string md5_hex = "";
foreach (byte b2 in md5.ToUpper())
{
md5_hex += (string.Format("{0:X2}", b2));
}
int packageLength = 1024;
Dispatcher.Invoke((Action)(() => {
//Progress.Maximum = fBuffer.Length / packageLength + 3;
//Progress.Visibility = Visibility.Visible;
Progress.Value = 0;
logText.Background = System.Windows.SystemColors.ControlBrush;
logText.Text = ""; //清空还原
}));
//right response:A5 00 02 90 00 A5 D9
int quantity = ((fBuffer.Length % packageLength) == 0) ? (fBuffer.Length / packageLength) : (fBuffer.Length / packageLength + 1);//发送包数量
if (!SendString("FC 07 00 00 00 " + String.Format("{0:X4} ", 4 + 32) + String.Format("{0:X8} ", fBuffer.Length) + md5_hex)) { return false; } //start
int len = packageLength;
for (int i = 0; i < fBuffer.Length / packageLength; i++)
{
Dispatcher.Invoke((Action)(() => { Progress.Value = 100*i/(fBuffer.Length / packageLength+3); }));
if (!SendString("FC 08 00 00 00 " + String.Format("{0:X4} ", len) + byteArrayConvertToHexStr(fBuffer, i * packageLength, len))) { return false; }
}
if ((fBuffer.Length % packageLength) != 0) //最后一包
{
len = fBuffer.Length % packageLength;
if (!SendString("FC 08 00 00 00 " + String.Format("{0:X4} ", len) + byteArrayConvertToHexStr(fBuffer, fBuffer.Length - len, len))) { return false; }
}
if (!SendString("FC 09 00 00 00 0000")) { return false; } //end
Dispatcher.Invoke((Action)(() => { Progress.Value = Progress.Maximum; }));
return true;
}
public Boolean SendString(string strHex)
{
strHex = strHex.Replace(" ", "");
strHex = "5A " + String.Format("{0:X4} ", strHex.Length / 2) + strHex + " ";//加上框架头1+APDU长度2+APDU+CRC16
strHex += Crc16(strHex);
Byte[] package = hexConvertToByteArray(strHex);
Log("send:" + strHex);
if (!serialPort.IsOpen)
{
Log("The serial port is not opened, and automatically try to open the serial port!");
if (!uartOpen()) { return false; }
}
responseIsOK = false;
_waitHandle.Reset();
//Log("before send time consumption:" + sw.ElapsedMilliseconds + "ms");
serialPort.Write(package, 0, package.Length);//向串口发送一包(18字节)的数据
//Log("after send time consumption:" + sw.ElapsedMilliseconds + "ms");
if (!_waitHandle.WaitOne(10000)) { Log("Wait timeout 10s"); return false; }// 等待通知
//Log("response time consumption:" + sw.ElapsedMilliseconds+"ms");
return responseIsOK;
//return false;
}
public bool reponse_Check(byte[] buffer,int length)
{
if (buffer == null) { Log("buffer is null"); return false; }
Log("recv(" + length + "):" + byteArrayConvertToHexStr(buffer, 0, length));
if (length > 6 && buffer[0] == 0xA5 && Crc16(buffer, (ushort)(length - 2)) == (buffer[length - 2]) * 256 + buffer[length - 1])
{
if (buffer[length - 2 - 2] == 0x90 && buffer[length - 1 - 2] == 0x00)
{
//Log("Response check OK");
return true;
}
else
{
Log("Response XXXXXXXXXXXXXXXXXXXXXX fail\r\n"); return false;
}
}
else { Log("Response protocol format error XXXXXXXXXXXXXXXXXXXXXX fail\r\n"); }
return false;
}
public string GetComName()
{
string[] ports = SerialPort.GetPortNames();
if (ports.Length < 1)
{
Log("No serial port available");
return null;
}
//getDevice();
string[] strArr = GetHarewareInfo(HardwareEnum.Win32_PnPEntity, "Name");
foreach (string s in strArr)
{
//Quectel USB Serial - 1 Port(COM7)
if (s.Contains("COM"))
{
Log(s);
if (s.Contains("Quectel USB Serial-1"))
{
//Log(s);
string com = s.Replace("Quectel USB Serial-1 Port", "");
com = com.Replace(" ", "");
com = com.Replace(")", "");
com = com.Replace("(", "");
Log("Identified M20 serial port:" + com);
return com;
}
}
}
Log("Serial port not found,TEST MODE,return " + ports[0]);
return ports[0];
Log("Serial port not found, please check whether the driver is installed, whether the cable is plugged in, and whether the power is on");
return null;
}
public bool uartOpen()
{
try
{
string portName = GetComName();
if (portName == null) { return false; }
serialPort.PortName = portName;
serialPort.BaudRate = 2000000;
serialPort.DataReceived += SerialPort_DataReceived;//添加事件注册
serialPort.Close(); //先关再开
serialPort.Open();
serialPort.DiscardOutBuffer();
serialPort.DiscardInBuffer();//清空缓冲
Log("Serial port has been opened " + serialPort.PortName + " " + serialPort.BaudRate);
return true;
}
catch (Exception e)
{
Log("The serial port has been occupied! " + serialPort.PortName + e.ToString() + "\r\n\r\n"); //+" " + e.ToString()
}
return false;
}
private void Log(string str)
{
Dispatcher.BeginInvoke((Action)(() => { //异步
// Dispatcher.Invoke((Action)(() => { //同步
MyLog.MyLog.WriteLogs(MyLog.MyLog.fileName, "", str);
if (logText.Text.Length > 5000)
{
logText.Text = logText.Text.Substring(logText.Text.Length - 100); //clear
}
logText.Text += str+"\r\n";
}));
}
private void Log(bool b)
{
Dispatcher.BeginInvoke((Action)(() => { //异步
// Dispatcher.Invoke((Action)(() => { //同步
if (b) { logText.Background = new SolidColorBrush(Colors.Green); }
else { logText.Background = new SolidColorBrush(Colors.Red); }
}));
}
private void Window_Closed(object sender, EventArgs e)
{
try
{
if (thread != null)
{
thread_isRunning = false;
thread.Abort(); //终止线程
}
if (serialPort.IsOpen)
{
serialPort.Close();
}
}
catch { }
}
}
}