Browse Source

1.增加升级版本的验证
2.增加读写上传红色指针的配置
3.获取结果时获取应该保留的小数位数

djs 3 months ago
parent
commit
07fd73be88

+ 2 - 0
MV485/MV485.csproj

@@ -211,6 +211,8 @@
       <DependentUpon>WndUpdateAsk.xaml</DependentUpon>
     </Compile>
     <Compile Include="util\AesEncryptionHelper.cs" />
+    <Compile Include="util\BinInspector.cs" />
+    <Compile Include="util\BinVerifier.cs" />
     <Compile Include="util\CRC32Helper.cs" />
     <Compile Include="util\LicenseMana.cs" />
     <Page Include="Dlg\AboutWindow.xaml">

+ 26 - 0
MV485/helper/RWRunConfig.cs

@@ -133,6 +133,15 @@ namespace MV485.helper
                 GenerateSendAndRecvHexLog(true, readName);
                 GenerateValueLog(readName, lastUnitLevel);
 
+                readName = "上传红色指针读数";
+                readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
+                    Constant.MB_REGISTER_ADD_UPLOAD_REDIND, Constant.MB_REGISTER_NUM_UPLOAD_REDIND);
+                byte uploadRedind = (byte)(readRegisters[0] & 0xFF);
+                runConfig.UploadRedindWRFlag = true;
+                runConfig.UploadRedind = uploadRedind;
+                GenerateSendAndRecvHexLog(true, readName);
+                GenerateValueLog(readName, uploadRedind);
+
                 readName = "表底读数";
                 readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
                     Constant.MB_REGISTER_ADD_LATEST_VALUE, Constant.MB_REGISTER_NUM_LATEST_VALUE);
@@ -348,6 +357,15 @@ namespace MV485.helper
                 GenerateSendAndRecvHexLog(true, readName);
                 GenerateValueLog(readName, resultType);
 
+                //保留的小数位数
+                readName = "单位为立方时保留的小数位数";
+                readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
+                    Constant.MB_REGISTER_ADD_RESULT_DECIMAL_PLACES, Constant.MB_REGISTER_NUM_RESULT_DECIMAL_PLACES);
+                byte decimalPlaces = (byte)(readRegisters[0] & 0xFF);
+                data.ResultDecimalPlaces = decimalPlaces; 
+                GenerateSendAndRecvHexLog(true, readName);
+                GenerateValueLog(readName, decimalPlaces);
+
                 return data;
             }
             catch(TimeoutException ex)
@@ -675,6 +693,14 @@ namespace MV485.helper
                     GenerateSendAndRecvHexLog(false, writeName);
                 }
 
+                if (runConfig.UploadRedindWRFlag)
+                {
+                    writeName = "上传红色指针读数";
+                    _modbusMaster.WriteSingleRegister(devId, Constant.MB_REGISTER_ADD_UPLOAD_REDIND , runConfig.UploadRedind);
+                    GenerateValueLog(writeName, runConfig.UploadRedind);
+                    GenerateSendAndRecvHexLog(false, writeName);
+                }
+
                 if (runConfig.LatestValueWRFlag)
                 {
                     writeName = "表底读数";

+ 11 - 3
MV485/helper/Tools.cs

@@ -42,6 +42,14 @@ namespace MV485.helper
                         new KeyValuePair<byte, string>(2, "2 - 全指针")
             };
 
+        //红色指针读数上传标志
+        public static List<KeyValuePair<byte, string>> UploadRedindList =
+        new List<KeyValuePair<byte, string>>()
+        {
+                    new KeyValuePair<byte, string>(0, "不上传"),
+                    new KeyValuePair<byte, string>(1, "上传")
+        };
+
         public static List<KeyValuePair<ushort, string>> FlowRateList =
             new List<KeyValuePair<ushort, string>>()
             {
@@ -274,9 +282,9 @@ namespace MV485.helper
             string digits = match.Groups[1].Value; // 提取出的6个数字
 
             // 将6位数字分成三组,每组2位
-            string part1 = int.Parse(digits.Substring(0, 2)).ToString();
-            string part2 = int.Parse(digits.Substring(2, 2)).ToString();
-            string part3 = int.Parse(digits.Substring(4, 2)).ToString();
+            string part1 = int.Parse(digits.Substring(0, 2)).ToString("D2");
+            string part2 = int.Parse(digits.Substring(2, 2)).ToString("D2");
+            string part3 = int.Parse(digits.Substring(4, 2)).ToString("D2");
 
             versionString = $"{part1}.{part2}.{part3}";
             return true;

+ 12 - 5
MV485/model/Constant.cs

@@ -23,12 +23,10 @@ namespace MV485.model
         public const ushort MB_REGISTER_ADD_RESULT_TYPE = 0x000A;		//识别结果的类型
         public const ushort MB_REGISTER_NUM_RESULT_TYPE = 1;    //寄存器数量
 
-        public const ushort MB_REGISTER_ADD_SAMPLE_INTERVAL = 0x000D;		//采样间隔
-        public const ushort MB_REGISTER_NUM_SAMPLE_INTERVAL = 1;
-
-        public const ushort MB_REGISTER_ADD_SAMPLE_FIRST_HOUR = 0x000F;     //首次采样时间
-        public const ushort MB_REGISTER_NUM_SAMPLE_FIRST_HOUR = 1;
 
+        //结果已立方为单位时保留的小数位数
+        public const ushort MB_REGISTER_ADD_RESULT_DECIMAL_PLACES = 0x000C;	//识别结果小数点位数
+        public const ushort MB_REGISTER_NUM_RESULT_DECIMAL_PLACES = 1;
 
         public const ushort MB_REGISTER_ADD_DEVICE_TIME = 0x0053;		//设备时钟
         public const ushort MB_REGISTER_NUM_DEVICE_TIME = 6;			//6个寄存器
@@ -96,6 +94,15 @@ namespace MV485.model
         public const ushort MB_REGISTER_ADD_FTIND_REGION = 0x008B;		//指针水表的首尾同刻度坐标
         public const ushort MB_REGISTER_NUM_FTIND_REGION = 4;
 
+        public const ushort MB_REGISTER_ADD_UPLOAD_REDIND = 0x008D;		//是否上传红色指针部分
+        public const ushort MB_REGISTER_NUM_UPLOAD_REDIND = 1;
+
+
+        public const ushort MB_REGISTER_ADD_SAMPLE_INTERVAL = 0x008F;		//采样间隔
+        public const ushort MB_REGISTER_NUM_SAMPLE_INTERVAL = 1;
+
+        public const ushort MB_REGISTER_ADD_SAMPLE_FIRST_HOUR = 0x0091;     //首次采样时间
+        public const ushort MB_REGISTER_NUM_SAMPLE_FIRST_HOUR = 1;
 
         public static List<int> AddressList = new List<int>
         {

+ 4 - 0
MV485/model/RunConfig.cs

@@ -26,6 +26,9 @@ namespace MV485.model
         public byte LastUnitLevel { get; set; }
         public bool LastUnitLevelWRFlag { get; set; }
 
+        public byte UploadRedind { get; set; }
+        public bool UploadRedindWRFlag { get; set; }
+
         public ulong LatestValue { get; set; }
         public bool LatestValueWRFlag { get; set; }
 
@@ -230,6 +233,7 @@ namespace MV485.model
             IndCountWRFlag = false;
             BrightValueWRFlag = false;
             LastUnitLevelWRFlag = false;
+            UploadRedindWRFlag = false;
             LatestValueWRFlag = false;
             LatestTimeWRFlag = false;
             MeterRegionWRFlag = false;

+ 6 - 1
MV485/model/WMData.cs

@@ -31,11 +31,16 @@ namespace MV485.model
         //public byte DeviceAddress { get; set; }
         public ulong TotalFlow { get; set; } // 单位:升
 
+        //结果已立方为单位时保留的小数位数
+        public byte ResultDecimalPlaces { get; set; }
+
         public string SampleResult
         {
             get
             {
-                return (TotalFlow / Constant.CUBE_VALUE).ToString();
+                double value = TotalFlow / (double)Constant.CUBE_VALUE;
+                return value.ToString($"F{ResultDecimalPlaces}");
+                //return (TotalFlow / Constant.CUBE_VALUE).ToString();
             }
         }
 

+ 4 - 1
MV485/uc/UCDeviceUpgrade.xaml

@@ -94,6 +94,7 @@
                     <Grid.ColumnDefinitions>
                         <ColumnDefinition Width="*" />
                         <ColumnDefinition Width="40" />
+                        <!--<ColumnDefinition Width="40" />-->
                     </Grid.ColumnDefinitions>
                     <!--ComboBox x:Name="cmbNewFireware" Grid.Column="0"  Padding="5 0 0 0" Margin="0" FontSize="14" 
                         VerticalContentAlignment="Center" Height="28" IsEditable="True" />-->
@@ -101,6 +102,8 @@
                                     Text="c:\sdfds.bin" FontSize="14" Height="26" Padding="5 0 0 0" />
                     <Button x:Name="btnSelNewFireware" Content="..." Click="BtnSelNewFireware_Click"
                             Grid.Column="1" Height="28" Margin="5 0 0 0"/>
+                    <Button x:Name="btnVerity" Content="Verity" Click="BtnVerity_Click" Visibility="Collapsed"
+                            Grid.Column="2" Height="28" Margin="5 0 0 0"/>
                 </Grid>
 
                 <Grid Height="50" Margin="10 0 10 0">
@@ -153,7 +156,7 @@
                                           Click="BtnRefreshUpgradeHis_Click" Margin="10 0 10 0" Width="30" Height="28" FontSize="20px"  />
 
                         <zdfflatui:FlatButton x:Name="btnClearDataA" HorizontalAlignment="Right"
-                                                      Background="Black" Content="测试增加" Visibility="Visible"
+                                                      Background="Black" Content="测试增加" Visibility="Collapsed"
                                                         Click="BtnClearDataA_Click" Foreground="White"
                                                       Width="80" Height="28" FontSize="13" Margin="10 0 10 0" />
                     </StackPanel>

+ 97 - 0
MV485/uc/UCDeviceUpgrade.xaml.cs

@@ -10,6 +10,7 @@ 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;
@@ -340,6 +341,18 @@ namespace MV485.uc
 
         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))
             {
@@ -1022,6 +1035,90 @@ namespace MV485.uc
                 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;
+        }
         //---------------------------------------------------------
     }
 }

+ 16 - 3
MV485/uc/UCRunConfig.xaml

@@ -286,6 +286,22 @@
                                             Height="20" Width="60" Background="Transparent" Foreground="Blue" FontSize="14" />
                             </Grid>
 
+                            <Grid Height="50" Margin="10 0 10 0">
+                                <Grid.ColumnDefinitions>
+                                    <ColumnDefinition Width="90" />
+                                    <ColumnDefinition Width="*" />
+                                    <ColumnDefinition Width="80" />
+                                </Grid.ColumnDefinitions>
+                                <TextBlock x:Name="txtUploadRedInd" Grid.Column="0" Text="红色指针读数" VerticalAlignment="Center" FontSize="14" TextWrapping="Wrap" />
+                                <ComboBox Grid.Column="1" x:Name="cmbUploadRedind" SelectedValuePath="Key" DisplayMemberPath="Value" Height="26" FontSize="14px"/>
+                                <!--<ComboBox x:Name="cmbMeterType"  Height="26" FontSize="14px" Grid.Column="1"
+                                          SelectionChanged="CmbMeterType_SelectionChanged" 
+                                          SelectedValuePath="Key" DisplayMemberPath="Value" />-->
+                                <Button x:Name="btnUploadRedind" Click="BtnUploadRedind_Click" 
+                                        Grid.Column="3" Content="说明示例" BorderThickness="0 0 0 1" BorderBrush="Blue"
+                                            Height="20" Width="60" Background="Transparent" Foreground="Blue" FontSize="14" />
+                            </Grid>
+
                             <Grid Height="50" Margin="10 0 10 0">
                                 <Grid.ColumnDefinitions>
                                     <ColumnDefinition Width="90" />
@@ -309,9 +325,6 @@
                                     <ColumnDefinition Width="80" />
                                 </Grid.ColumnDefinitions>
                                 <TextBlock Grid.Column="0" Text="读数时间" VerticalAlignment="Center" FontSize="14" />
-                                <!--<ComboBox Grid.Column="1" x:Name="cmbLastUnit" Height="26" FontSize="14px"/>-->
-                                <!--<wpftoolkit:DateTimePicker Grid.Column="1" x:Name="dtpLastValueTime" Height="26"
-                                                 TextAlignment="Left" FontSize="14" Format="Custom" FormatString="yyyy-MM-dd HH:mm"/>-->
                                 <zdfflatui:DateTimePicker x:Name="dtpLastValueTime"  Grid.Column="1" 
                                                           Height="26" FontSize="14" DateFormat="yyyy-MM-dd HH:mm"/>
                                 <Button x:Name="btnLastValueTime" Click="BtnLastValueTime_Click" 

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

@@ -132,7 +132,7 @@ namespace MV485.uc
             txtAIResult.Text = "";
             txtSampleTime.Text = "";
             txtMeterResult.Text = "";
-            //imgMeter.Source = null;
+            imgMeter.Source = null;
         }
 
         //初始化配置栏视图
@@ -147,6 +147,8 @@ namespace MV485.uc
             cmbIndCount.SelectedItem = null;
             //cmbBrightVal.Text = "";
             cmbLastUnit.SelectedItem = null;
+            cmbUploadRedind.SelectedItem = null;
+
             txtLastValue.Text = "";
             //dtpLastValueTime.Text = "";
             //dtpLastValueTime.Value = null;
@@ -181,6 +183,7 @@ namespace MV485.uc
             cmbDnValue.ItemsSource = Tools.DnValueList;
 
             cmbLastUnit.ItemsSource = Tools.LastUnitMap;    //.UnitList;
+            cmbUploadRedind.ItemsSource = Tools.UploadRedindList;
             cmbNumCount.ItemsSource = Tools.NumList;
             cmbIndCount.ItemsSource = Tools.IndList;     //Tools.NumList;
             //cmbBrightVal.ItemsSource = Tools.BrightList;
@@ -765,6 +768,11 @@ namespace MV485.uc
                 cmbLastUnit.SelectedValue = runConfig.LastUnitLevel;
             }
 
+            if (runConfig.UploadRedindWRFlag)
+            {
+                cmbUploadRedind.SelectedValue = runConfig.UploadRedind;
+            }
+
             if(runConfig.LatestValueWRFlag && runConfig.LatestTimeWRFlag && runConfig.LatestTime != null)
             {
                 txtLastValue.Text = ((int)(runConfig.LatestValue / Constant.CUBE_VALUE)).ToString();
@@ -940,6 +948,15 @@ namespace MV485.uc
             runConfig.LastUnitLevelWRFlag = true;
             runConfig.LastUnitLevel = (byte)cmbLastUnit.SelectedValue;
 
+            if(cmbUploadRedind.SelectedItem == null)
+            {
+                MessageBox.Show(Application.Current.MainWindow, "请选择是否上传红色指针的读数", "提示",
+                    MessageBoxButton.OK, MessageBoxImage.Warning);
+                return;
+            }
+            runConfig.UploadRedindWRFlag = true;
+            runConfig.UploadRedind = (byte)cmbUploadRedind.SelectedValue;
+
             if (string.IsNullOrWhiteSpace(txtLastValue.Text) || 
                 !ulong.TryParse(txtLastValue.Text,out ulong latestValue))
             {
@@ -1340,6 +1357,11 @@ namespace MV485.uc
             }
         }
 
+        private void BtnUploadRedind_Click(object sender, RoutedEventArgs e)
+        {
+
+        }
+
         //------------------------------------------------------------------
     }
 }

+ 212 - 0
MV485/util/BinInspector.cs

@@ -0,0 +1,212 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MV485.util
+{
+    public class BinInspector
+    {
+        public static void Inspect2(string binFilePath)
+        {
+            Console.WriteLine($"检查文件: {binFilePath}");
+
+            using (var fs = new FileStream(binFilePath, FileMode.Open, FileAccess.Read))
+            using (var reader = new BinaryReader(fs))
+            {
+                // 1. 检查 Header
+                var header = ImageHeader.FromBinaryReader(reader);
+                if (header.Magic != 0x96f3b83d)
+                {
+                    Console.WriteLine("❌ Header Magic 错误!");
+                    return;
+                }
+
+                Console.WriteLine($"✅ Header Magic 正常: 0x{header.Magic:X8}");
+                Console.WriteLine($"版本: v{header.VersionMajor}.{header.VersionMinor}.{header.VersionRevision} (Build {header.VersionBuildNum})");
+                Console.WriteLine($"Header大小: 0x{header.HeaderSize:X} ({header.HeaderSize} 字节)");
+                Console.WriteLine($"正文大小: {header.ImageSize} 字节");
+
+                // 2. 读取 header + body
+                fs.Position = 0;
+                var fullHeader = reader.ReadBytes(header.HeaderSize);
+                var body = reader.ReadBytes((int)header.ImageSize);
+
+                // 3. 检查 TLV
+                var tlvInfo = TlvInfo.FromBinaryReader(reader);
+                if (tlvInfo.Magic != 0x6907)
+                {
+                    Console.WriteLine("❌ TLV Magic 错误!");
+                    return;
+                }
+                Console.WriteLine($"✅ TLV Magic 正常: 0x{tlvInfo.Magic:X4}");
+                Console.WriteLine($"TLV总长度: {tlvInfo.TotalLength} 字节");
+
+                int tlvRemaining = tlvInfo.TotalLength - 4;
+                Console.WriteLine("解析 TLV Entries:");
+                byte[] sha256Value = null;
+
+                while (tlvRemaining > 0)
+                {
+                    var entry = TlvEntry.FromBinaryReader(reader);
+                    Console.WriteLine($"- Type: 0x{entry.Type:X2}, Length: {entry.Length} 字节");
+
+                    if (entry.Type == 0x10 && entry.Length == 32) // SHA256
+                    {
+                        sha256Value = entry.Value;
+                    }
+
+                    tlvRemaining -= 4 + entry.Length;
+                }
+
+                if (sha256Value == null)
+                {
+                    Console.WriteLine("❌ 没有找到 SHA256 校验值!");
+                    return;
+                }
+
+                string expectedHash = BitConverter.ToString(sha256Value).Replace("-", "").ToLowerInvariant();
+
+                // 4. 验证 SHA256
+                using (var sha = SHA256.Create())
+                {
+                    var actualHash = sha.ComputeHash(fullHeader.Concat(body).ToArray());
+                    string actualHashHex = BitConverter.ToString(actualHash).Replace("-", "").ToLowerInvariant();
+
+                    Console.WriteLine($"期望SHA256: {expectedHash}");
+                    Console.WriteLine($"实际SHA256:  {actualHashHex}");
+
+                    if (expectedHash == actualHashHex)
+                        Console.WriteLine("✅ SHA256 校验成功!");
+                    else
+                        Console.WriteLine("❌ SHA256 校验失败!");
+                }
+            }
+        }
+
+
+        public static bool Inspect(string binFilePath)
+        {
+            Console.WriteLine($"检查文件: {binFilePath}");
+
+            using (var fs = new FileStream(binFilePath, FileMode.Open, FileAccess.Read))
+            using (var reader = new BinaryReader(fs))
+            {
+                try
+                {
+                    // 1. 检查 Header
+                    var header = ImageHeader.FromBinaryReader(reader);
+                    if (header.Magic != 0x96f3b83d)
+                    {
+                        Console.WriteLine("❌ Header Magic 错误!");
+                        return false;
+                    }
+
+                    Console.WriteLine($"✅ Header Magic 正常: 0x{header.Magic:X8}");
+                    Console.WriteLine($"版本: v{header.VersionMajor}.{header.VersionMinor}.{header.VersionRevision} (Build {header.VersionBuildNum})");
+                    Console.WriteLine($"Header大小: 0x{header.HeaderSize:X} ({header.HeaderSize} 字节)");
+                    Console.WriteLine($"正文大小: {header.ImageSize} 字节");
+
+                    // 2. 验证 文件名最后6位数字
+                    var fileName = Path.GetFileNameWithoutExtension(binFilePath);
+                    if (fileName.Length < 6)
+                    {
+                        Console.WriteLine("❌ 文件名太短,无法解析版本号!");
+                        return false;
+                    }
+
+                    string last6Digits = fileName.Substring(fileName.Length - 6); // 最后6位
+                    if (!int.TryParse(last6Digits, out _))
+                    {
+                        Console.WriteLine("❌ 文件名最后6位不是纯数字!");
+                        return false;
+                    }
+
+                    int major = int.Parse(last6Digits.Substring(0, 2));
+                    int minor = int.Parse(last6Digits.Substring(2, 2));
+                    int revision = int.Parse(last6Digits.Substring(4, 2));
+
+                    if (header.VersionMajor != major || header.VersionMinor != minor || header.VersionRevision != revision)
+                    {
+                        Console.WriteLine($"❌ 版本号不匹配!文件名版本: {major}.{minor}.{revision},Header版本: {header.VersionMajor}.{header.VersionMinor}.{header.VersionRevision}");
+                        return false;
+                    }
+
+                    Console.WriteLine($"✅ 文件名版本与Header版本一致: v{major}.{minor}.{revision}");
+
+                    // 3. 读取 header + body
+                    fs.Position = 0;
+                    var fullHeader = reader.ReadBytes(header.HeaderSize);
+                    var body = reader.ReadBytes((int)header.ImageSize);
+
+                    // 4. 检查 TLV
+                    var tlvInfo = TlvInfo.FromBinaryReader(reader);
+                    if (tlvInfo.Magic != 0x6907)
+                    {
+                        Console.WriteLine("❌ TLV Magic 错误!");
+                        return false;
+                    }
+                    Console.WriteLine($"✅ TLV Magic 正常: 0x{tlvInfo.Magic:X4}");
+                    Console.WriteLine($"TLV总长度: {tlvInfo.TotalLength} 字节");
+
+                    int tlvRemaining = tlvInfo.TotalLength - 4;
+                    Console.WriteLine("解析 TLV Entries:");
+                    byte[] sha256Value = null;
+
+                    while (tlvRemaining > 0)
+                    {
+                        var entry = TlvEntry.FromBinaryReader(reader);
+                        Console.WriteLine($"- Type: 0x{entry.Type:X2}, Length: {entry.Length} 字节");
+
+                        if (entry.Type == 0x10 && entry.Length == 32) // SHA256
+                        {
+                            sha256Value = entry.Value;
+                        }
+
+                        tlvRemaining -= 4 + entry.Length;
+                    }
+
+                    if (sha256Value == null)
+                    {
+                        Console.WriteLine("❌ 没有找到 SHA256 校验值!");
+                        return false;
+                    }
+
+                    string expectedHash = BitConverter.ToString(sha256Value).Replace("-", "").ToLowerInvariant();
+
+                    // 5. 验证 SHA256
+                    using (var sha = SHA256.Create())
+                    {
+                        var actualHash = sha.ComputeHash(fullHeader.Concat(body).ToArray());
+                        string actualHashHex = BitConverter.ToString(actualHash).Replace("-", "").ToLowerInvariant();
+
+                        Console.WriteLine($"期望SHA256: {expectedHash}");
+                        Console.WriteLine($"实际SHA256:  {actualHashHex}");
+
+                        if (expectedHash != actualHashHex)
+                        {
+                            Console.WriteLine("❌ SHA256 校验失败!");
+                            return false;
+                        }
+
+                        Console.WriteLine("✅ SHA256 校验成功!");
+                    }
+
+                    Console.WriteLine("✅ 文件检查全部通过!");
+                    return true;
+                }
+                catch (Exception ex)
+                {
+                    Console.WriteLine($"❌ 检查出错: {ex.Message}");
+                    return false;
+                }
+            }
+        }
+
+
+    }
+    ///////////////////////////////////////
+}

+ 176 - 0
MV485/util/BinVerifier.cs

@@ -0,0 +1,176 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MV485.util
+{
+    //+------------------------+  ← Offset 0x000000
+    //|      Image Header      |  固定32字节
+    //+------------------------+  ← Offset 0x000020
+    //|        Padding         |  填充到 header-size 对齐(例如 0x400)
+    //+------------------------+  ← Offset 0x000400
+    //|      Image Payload     |  实际的 bin 内容(运行代码)
+    //+------------------------+
+    //|        TLV Info        |  包括哈希、签名等(SHA256、ECDSA 等)
+    //+------------------------+
+    //|       TLV Entries      |  一个或多个 TLV 条目
+    //+------------------------+
+
+
+    //偏移 | 长度 | 字段名 | 描述
+    //0x00 | 4 | ih_magic | 魔数 0x96F3B83D
+    //0x04 | 4 | ih_load_addr | 加载地址(对于 RAM Load 有用)
+    //0x08 | 2 | ih_hdr_size | 头部大小,一般为 0x400
+    //0x0A | 2 | _pad1 | 保留字段
+    //0x0C | 4 | ih_img_size | 镜像内容长度(不含头部)
+    //0x10 | 4 | ih_flags | 标志位,如是否加密
+    //0x14 | 1 | iv_major | 主版本号
+    //0x15 | 1 | iv_minor | 次版本号
+    //0x16 | 2 | iv_revision | 修订号
+    //0x18 | 4 | iv_build_num | 构建号
+    //0x1C | 4 | _pad2 | 保留字段
+
+    //3D B8 F3 96    ←  ih_magic = 0x96F3B83D
+    //00 00 00 00    ←  ih_load_addr = 0x00000000
+    //04 00          ←  ih_hdr_size = 0x0004(注意实际是 0x400)
+    //00 00
+    //A0 69 0A 00    ←  ih_img_size = 0x000A69A0(约680KB)
+    //00 00 00 00    ←  ih_flags = 0
+    //5C 5B 5A 00    ←  version:iv_major=0x5C, iv_minor=0x5B, iv_revision=0x005A
+    //...
+
+    public class ImageHeader
+    {
+        public uint Magic;              // 0x00 固定 magic: 0x96f3b83d
+        public uint LoadAddr;           //     // 0x04 固件加载地址
+        public ushort HeaderSize;       // 0x08 Header 大小(如 0x400)
+        public ushort Pad1;             // 0x0A 对齐用
+        public uint ImageSize;          // 0x0C 固件正文大小(不含 Header)
+        public uint Flags;               // 0x10 标志位
+        public byte VersionMajor;       // 0x14~0x1B 版本号(major/minor/revision/build)
+        public byte VersionMinor;
+        public ushort VersionRevision;
+        public uint VersionBuildNum;
+        public uint Pad2;
+
+        public static ImageHeader FromBinaryReader(BinaryReader reader)
+        {
+            return new ImageHeader
+            {
+                Magic = reader.ReadUInt32(),
+                LoadAddr = reader.ReadUInt32(),
+                HeaderSize = reader.ReadUInt16(),
+                Pad1 = reader.ReadUInt16(),
+                ImageSize = reader.ReadUInt32(),
+                Flags = reader.ReadUInt32(),
+                VersionMajor = reader.ReadByte(),
+                VersionMinor = reader.ReadByte(),
+                VersionRevision = reader.ReadUInt16(),
+                VersionBuildNum = reader.ReadUInt32(),
+                Pad2 = reader.ReadUInt32()
+            };
+        }
+    }
+
+
+    public class TlvInfo
+    {
+        public ushort Magic;                // 0x6907
+        public ushort TotalLength;          // TLV区总长度(包括这4字节)
+
+        public static TlvInfo FromBinaryReader(BinaryReader reader)
+        {
+            return new TlvInfo
+            {
+                Magic = reader.ReadUInt16(),
+                TotalLength = reader.ReadUInt16()
+            };
+        }
+    }
+
+    //尾部 TLV 区域结构
+    //每项4字节头 + 数据
+    //类型(it_type) | 含义
+    //0x01 | 公钥哈希
+    //0x10 | 镜像 SHA256
+    //0x22 | ECDSA256 签名
+    //0x20 | RSA2048 签名(PSS)
+    public class TlvEntry
+    {
+        public byte Type;           // 类型,如 0x10 表示 SHA256,0x22 表示 ECDSA256
+        public byte Pad;            // 保留
+        public ushort Length;        // 后续数据长度
+        public byte[] Value;
+
+        public static TlvEntry FromBinaryReader(BinaryReader reader)
+        {
+            var entry = new TlvEntry
+            {
+                Type = reader.ReadByte(),
+                Pad = reader.ReadByte(),
+                Length = reader.ReadUInt16()
+            };
+            entry.Value = reader.ReadBytes(entry.Length);
+            return entry;
+        }
+    }
+
+
+    public class BinVerifier
+    {
+        public static bool VerifyBin(string binFilePath, out string expectedHashHex, out string actualHashHex)
+        {
+            expectedHashHex = "";
+            actualHashHex = "";
+
+            using (var fs = new FileStream(binFilePath, FileMode.Open, FileAccess.Read))
+            using (var reader = new BinaryReader(fs))
+            {
+                // Step 1: 读取 image_header
+                var header = ImageHeader.FromBinaryReader(reader);
+                fs.Position = 0;
+                var fullHeader = reader.ReadBytes(header.HeaderSize);
+
+                // Step 2: 读取 image body
+                var body = reader.ReadBytes((int)header.ImageSize);
+
+                // Step 3: 读取 TLV info
+                var tlvInfo = TlvInfo.FromBinaryReader(reader);
+                if (tlvInfo.Magic != 0x6907)
+                    throw new InvalidDataException("Invalid TLV magic number.");
+
+                int tlvRemaining = tlvInfo.TotalLength - 4;
+                byte[] hashValue = null;
+                while (tlvRemaining > 0)
+                {
+                    var entry = TlvEntry.FromBinaryReader(reader);
+                    if (entry.Type == 0x10 && entry.Length == 32) // SHA256 type
+                    {
+                        hashValue = entry.Value;
+                        break;
+                    }
+                    tlvRemaining -= 4 + entry.Length;
+                }
+
+                if (hashValue == null)
+                    throw new InvalidDataException("SHA256 hash not found in TLV.");
+
+                expectedHashHex = BitConverter.ToString(hashValue).Replace("-", "").ToLowerInvariant();
+
+                // Step 4: 计算 SHA256(header + body)
+                using (var sha = SHA256.Create())
+                {
+                    var calculatedHash = sha.ComputeHash(fullHeader.Concat(body).ToArray());
+                    actualHashHex = BitConverter.ToString(calculatedHash).Replace("-", "").ToLowerInvariant();
+                }
+
+                return expectedHashHex == actualHashHex;
+            }
+        }
+    }
+    //------------------------------
+}