Pārlūkot izejas kodu

1. 测试写入运行参数基本OK
2. 增加Modbus读取图像文件大小的逻辑
3. 增加了使用xModem接收图像文件的逻辑

djs 4 mēneši atpakaļ
vecāks
revīzija
ffcc5b2b14

+ 153 - 63
MV485/helper/RWRunConfig.cs

@@ -57,55 +57,73 @@ namespace MV485.helper
 
         public RunConfig GetRunConfig(byte devId)
         {
-            RunConfig runConfig = null;
-
             string readName = "";
             try
             {
+                RunConfig runConfig = new RunConfig();
+
                 ushort[] readRegisters;
 
                 readName = "水表类型";
                 readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
                     Constant.MB_REGISTER_ADD_METER_TYPE, Constant.MB_REGISTER_NUM_METER_TYPE);
                 byte meterType = (byte)(readRegisters[0] & 0xFF);
-                GenerateSendAndRecvHexLog(readName);
+                runConfig.MeterTypeWRFlag = true;
+                runConfig.MeterType = meterType;
+                GenerateSendAndRecvHexLog(true,readName);
                 _message = $"{readName}: {meterType}";
                 RWLog?.Invoke(_message);
 
                 readName = "每小时最大流量";
                 readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
-                    Constant.MB_REGISTER_ADD_DN_VALUE, Constant.MB_REGISTER_NUM_DN_VALUE);
+                    Constant.MB_REGISTER_ADD_DN_VALUE, Constant.MB_REGISTER_NUM_DN_VALUE);                
                 ushort dnValue = readRegisters[0];
-                GenerateSendAndRecvHexLog(readName);
+                runConfig.DnValueWRFlag = true;
+                runConfig.DnValue = dnValue;
+                GenerateSendAndRecvHexLog(true, readName);
                 _message = $"{readName}: {dnValue}";
                 RWLog?.Invoke(_message);
 
-                readName = "数字个数";
-                readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
-                    Constant.MB_REGISTER_ADD_DIGIT_COUNT, Constant.MB_REGISTER_NUM_DIGIT_COUNT);
-                byte digitCount = (byte)(readRegisters[0] & 0xFF);
-                GenerateSendAndRecvHexLog(readName);
-                GenerateValueLog(readName, digitCount);
+                if (meterType == Constant.METER_TYPE_NUM_IND || meterType == Constant.METER_TYPE_NUM)
+                {
+                    readName = "数字个数";
+                    readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
+                        Constant.MB_REGISTER_ADD_DIGIT_COUNT, Constant.MB_REGISTER_NUM_DIGIT_COUNT);
+                    byte digitCount = (byte)(readRegisters[0] & 0xFF);
+                    runConfig.DigitCountWRFlag = true;
+                    runConfig.DigitCount = digitCount;
+                    GenerateSendAndRecvHexLog(true, readName);
+                    GenerateValueLog(readName, digitCount);
+                }
 
-                readName = "指针个数";
-                readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
-                    Constant.MB_REGISTER_ADD_INDICATOR_COUNT, Constant.MB_REGISTER_NUM_INDICATOR_COUNT);
-                byte indCount = (byte)(readRegisters[0] & 0xFF);
-                GenerateSendAndRecvHexLog(readName);
-                GenerateValueLog(readName, indCount);
+                if (meterType == Constant.METER_TYPE_NUM_IND || meterType == Constant.METER_TYPE_IND)
+                {
+                    readName = "指针个数";
+                    readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
+                        Constant.MB_REGISTER_ADD_INDICATOR_COUNT, Constant.MB_REGISTER_NUM_INDICATOR_COUNT);
+                    byte indCount = (byte)(readRegisters[0] & 0xFF);
+                    runConfig.IndCountWRFlag = true;
+                    runConfig.IndCount = indCount;
+                    GenerateSendAndRecvHexLog(true, readName);
+                    GenerateValueLog(readName, indCount);
+                }
 
                 readName = "照片亮度放大倍率";
                 readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
                     Constant.MB_REGISTER_ADD_BRIGHT_VALUE, Constant.MB_REGISTER_NUM_BRIGHT_VALUE);
                 byte birghtValue = (byte)(readRegisters[0] & 0xFF);  // 10f;
-                GenerateSendAndRecvHexLog(readName);
+                runConfig.BrightValueWRFlag = true;
+                runConfig.BrightValue = birghtValue / 10f;
+                GenerateSendAndRecvHexLog(true, readName);
                 GenerateValueLog(readName, birghtValue);
 
                 readName = "尾数单位等级";
                 readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
                     Constant.MB_REGISTER_ADD_LAST_UNIT_LEVEL, Constant.MB_REGISTER_NUM_LAST_UNIT_LEVEL);
                 byte lastUnitLevel = (byte)(readRegisters[0] & 0xFF);
-                GenerateSendAndRecvHexLog(readName);
+                runConfig.LastUnitLevelWRFlag = true;
+                runConfig.LastUnitLevel = lastUnitLevel;
+                GenerateSendAndRecvHexLog(true, readName);
                 GenerateValueLog(readName, lastUnitLevel);
 
                 readName = "表底读数";
@@ -114,7 +132,9 @@ namespace MV485.helper
                 //4个寄存器
                 ulong latestValue = ((ulong)readRegisters[0] << 48) |  ((ulong)readRegisters[1] << 32) | 
                     ((ulong)readRegisters[2] << 16) | readRegisters[3];
-                GenerateSendAndRecvHexLog(readName);
+                runConfig.LatestValueWRFlag = true;
+                runConfig.LatestValue = latestValue;
+                GenerateSendAndRecvHexLog(true, readName);
                 GenerateValueLog(readName, latestValue);
 
                 readName = "表底读数时间";
@@ -131,56 +151,118 @@ namespace MV485.helper
                 //string latestTime = dtLatestTime.ToString("yyyy-MM-dd HH:mm:ss");
                 string latestTime = $"{2000 + year}-{month.ToString("D2")}-{day.ToString("D2")} " +
                     $"{hour.ToString("D2")}:{minute.ToString("D2")}:{second.ToString("D2")}";
-                GenerateSendAndRecvHexLog(readName);
+                if(DateTime.TryParse(latestTime,out DateTime dtLatestTime))
+                {
+                    runConfig.LatestTimeWRFlag = true;
+                    runConfig.LatestTime = dtLatestTime;
+                }
+                GenerateSendAndRecvHexLog(true, readName);
                 GenerateValueLog(readName, latestTime);
+
                 
                 readName = "表盘区域坐标";
                 readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
                     Constant.MB_REGISTER_ADD_METER_REGION, Constant.MB_REGISTER_NUM_METER_REGION);
                 //4个寄存器
                 string meterRegion = $"{readRegisters[0]},{readRegisters[1]} {readRegisters[2]},{readRegisters[3]}";
-                GenerateSendAndRecvHexLog(readName);
+                runConfig.MeterRegionWRFlag = true;
+                runConfig.MeterRegion = meterRegion;
+                GenerateSendAndRecvHexLog(true, readName);
                 GenerateValueLog(readName, meterRegion);
 
-                readName = "数字区域坐标";
-                readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
-                    Constant.MB_REGISTER_ADD_DIGIT_REGION, Constant.MB_REGISTER_NUM_DIGIT_REGION);
-                //8个寄存器
-                string digitRegion = $"{readRegisters[0]},{readRegisters[1]} {readRegisters[2]},{readRegisters[3]} " +
-                    $"{readRegisters[4]},{readRegisters[5]} {readRegisters[6]},{readRegisters[7]}";
-                GenerateSendAndRecvHexLog(readName);
-                GenerateValueLog(readName, digitRegion);
-
-                readName = "首尾指针坐标";
-                readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
-                    Constant.MB_REGISTER_ADD_FTIND_REGION, Constant.MB_REGISTER_NUM_FTIND_REGION);
-                //4个寄存器
-                string ftIndRegion = $"{readRegisters[0]},{readRegisters[1]} {readRegisters[2]},{readRegisters[3]}";
-                GenerateSendAndRecvHexLog(readName);
-                GenerateValueLog(readName, ftIndRegion);
+
+                if (meterType == Constant.METER_TYPE_NUM_IND || meterType == Constant.METER_TYPE_NUM)
+                {
+                    readName = "数字区域坐标";
+                    readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
+                        Constant.MB_REGISTER_ADD_DIGIT_REGION, Constant.MB_REGISTER_NUM_DIGIT_REGION);
+                    //8个寄存器
+                    string digitRegion = $"{readRegisters[0]},{readRegisters[1]} {readRegisters[2]},{readRegisters[3]} " +
+                        $"{readRegisters[4]},{readRegisters[5]} {readRegisters[6]},{readRegisters[7]}";
+                    runConfig.DigitRegionWRFlag = true;
+                    runConfig.DigitRegion = digitRegion;
+                    GenerateSendAndRecvHexLog(true, readName);
+                    GenerateValueLog(readName, digitRegion);
+                }
+
+                if (meterType == Constant.METER_TYPE_IND)
+                {
+                    readName = "首尾指针坐标";
+                    readRegisters = _modbusMaster.ReadHoldingRegisters(devId,
+                        Constant.MB_REGISTER_ADD_FTIND_REGION, Constant.MB_REGISTER_NUM_FTIND_REGION);
+                    //4个寄存器
+                    string ftIndRegion = $"{readRegisters[0]},{readRegisters[1]} {readRegisters[2]},{readRegisters[3]}";
+                    runConfig.FTIndRegionWRFlag = true;
+                    runConfig.FTIndRegion = ftIndRegion;
+                    GenerateSendAndRecvHexLog(true, readName);
+                    GenerateValueLog(readName, ftIndRegion);
+                }
+
+                return runConfig;
             }
             catch (TimeoutException ex)
             {
                 GenerateSendHexLog(readName);
                 _message = $"读取{readName}超时,{ex.Message}";
                 RWLog?.Invoke(_message);
+                return null;
             }
             catch (SlaveException ex)
             {
                 GenerateSendHexLog(readName);
                 _message = $"读取{readName}Modbus错误,{ex.Message}";
                 RWLog?.Invoke(_message);
+                return null;
             }
             catch (Exception ex)
             {
                 GenerateSendHexLog(readName);
                 _message = $"读取{readName}错误,{ex.Message}";
                 RWLog?.Invoke(_message);
+                return null;
             }
+        }
 
-            return runConfig;
+        //读取照片字节数
+        public ushort ReadImageSize(string portName,int baudrate,byte devId)
+        {
+            ushort imageSize = 0;
+            if (OpenSerial(portName, baudrate))
+            {
+                string readName = "";    // = "照片文件大小";
+                try
+                {
+                    readName = "照片文件大小";
+                    //读取照片字节数
+                    ushort[] readRegisters = _modbusMaster.ReadHoldingRegisters(devId, Constant.MB_REGISTER_IMAGE_SIZE, 1);
+                    imageSize = readRegisters[0];
+                    GenerateSendAndRecvHexLog(true, readName);
+                    GenerateValueLog(readName, imageSize);
+                }
+                catch (TimeoutException ex)
+                {
+                    GenerateSendHexLog(readName);
+                    _message = $"读取{readName}超时,{ex.Message}";
+                    RWLog?.Invoke(_message);
+                }
+                catch(SlaveException ex)
+                {
+                    GenerateSendHexLog(readName);
+                    _message = $"读取{readName}Modbus错误,{ex.Message}";
+                    RWLog?.Invoke(_message);
+                }
+                catch(Exception ex)
+                {
+                    GenerateSendHexLog(readName);
+                    _message = $"读取{readName}错误,{ex.Message}";
+                    RWLog?.Invoke(_message);
+                }
+            }
+            CloseSerial();
+            return imageSize;
         }
 
+
         public bool WriteRunConfig(string portName,int baudrate,byte devId,RunConfig runConfig)
         {
             bool blWrite = false;
@@ -203,95 +285,103 @@ namespace MV485.helper
             string writeName = "";
             try
             {
-                if (runConfig.MeterTypeWriteFlag)
+                if (runConfig.MeterTypeWRFlag)
                 {
                     writeName = "水表类型";
                     _modbusMaster.WriteSingleRegister(devId, Constant.MB_REGISTER_ADD_METER_TYPE, runConfig.MeterType);
                     GenerateValueLog(writeName, runConfig.LatestValue);
-                    GenerateSendAndRecvHexLog(writeName);
+                    GenerateSendAndRecvHexLog(false, writeName);
                 }
 
 
-                if (runConfig.DnValueWriteFlag)
+                if (runConfig.DnValueWRFlag)
                 {
                     writeName = "每小时最大水流";
                     _modbusMaster.WriteSingleRegister(devId, Constant.MB_REGISTER_ADD_DN_VALUE, runConfig.DnValue);
                     GenerateValueLog(writeName, runConfig.DnValue);
-                    GenerateSendAndRecvHexLog(writeName);
+                    GenerateSendAndRecvHexLog(false, writeName);
                 }
 
-                if (runConfig.DigitCountWriteFlag)
+                if (runConfig.DigitCountWRFlag)
                 {
                     writeName = "数字个数";
                     _modbusMaster.WriteSingleRegister(devId, Constant.MB_REGISTER_ADD_DIGIT_COUNT, runConfig.DigitCount);
                     GenerateValueLog(writeName, runConfig.DigitCount);
-                    GenerateSendAndRecvHexLog(writeName);
+                    GenerateSendAndRecvHexLog(false, writeName);
                 }
 
-                if (runConfig.IndCountWriteFlag)
+                if (runConfig.IndCountWRFlag)
                 {
                     writeName = "指针个数";
                     _modbusMaster.WriteSingleRegister(devId, Constant.MB_REGISTER_ADD_INDICATOR_COUNT, runConfig.IndCount);
                     GenerateValueLog(writeName, runConfig.IndCount);
-                    GenerateSendAndRecvHexLog(writeName);
+                    GenerateSendAndRecvHexLog(false, writeName);
                 }
 
-                if (runConfig.BrightValueWriteFlag)
+                if (runConfig.BrightValueWRFlag)
                 {
                     writeName = "照片亮度系数";
                     ushort brightValue = (ushort)(runConfig.BrightValue * 10);
                     _modbusMaster.WriteSingleRegister(devId, Constant.MB_REGISTER_ADD_BRIGHT_VALUE, brightValue);
                     GenerateValueLog(writeName, runConfig.BrightValue);
-                    GenerateSendAndRecvHexLog(writeName);
+                    GenerateSendAndRecvHexLog(false, writeName);
                 }
 
-                if (runConfig.LastUnitLevelWriteFlag)
+                if (runConfig.LastUnitLevelWRFlag)
                 {
                     writeName = "尾数单位等级";
                     _modbusMaster.WriteSingleRegister(devId, Constant.MB_REGISTER_ADD_LAST_UNIT_LEVEL, runConfig.LastUnitLevel);
                     GenerateValueLog(writeName, runConfig.LastUnitLevel);
-                    GenerateSendAndRecvHexLog(writeName);
+                    GenerateSendAndRecvHexLog(false, writeName);
                 }
 
-                if (runConfig.LatestValueWriteFlag)
+                if (runConfig.LatestValueWRFlag)
                 {
                     writeName = "表底读数";
                     _modbusMaster.WriteMultipleRegisters(devId, Constant.MB_REGISTER_ADD_LATEST_VALUE, runConfig.LatestValues);
                     GenerateValueLog(writeName, runConfig.LatestValue);
-                    GenerateSendAndRecvHexLog(writeName);
+                    GenerateSendAndRecvHexLog(false, writeName);
                 }
 
-                if (runConfig.LatestTImeWriteFlag && runConfig.LatestTimes != null)
+                if (runConfig.LatestTimeWRFlag && runConfig.LatestTimes != null)
                 {
                     writeName = "表底读数时间";
                     _modbusMaster.WriteMultipleRegisters(devId, Constant.MB_REGISTER_ADD_LATEST_TIME, runConfig.LatestTimes);
                     GenerateValueLog(writeName, runConfig.LatestTime.ToString("yyyy-MM-dd HH:mm:ss"));
-                    GenerateSendAndRecvHexLog(writeName);
+                    GenerateSendAndRecvHexLog(false, writeName);
                 }
 
-                if (runConfig.MeterRegionWriteFlag && runConfig.MeterRegions != null)
+                if (runConfig.MeterRegionWRFlag && runConfig.MeterRegions != null)
                 {
                     writeName = "表盘区域坐标";
                     _modbusMaster.WriteMultipleRegisters(devId, Constant.MB_REGISTER_ADD_METER_REGION, runConfig.MeterRegions);
                     GenerateValueLog(writeName, runConfig.MeterRegion);
-                    GenerateSendAndRecvHexLog(writeName);
+                    GenerateSendAndRecvHexLog(false, writeName);
                 }
 
-                if (runConfig.DigitRegionWriteFlag && runConfig.DigitRegions != null)
+                if (runConfig.DigitRegionWRFlag && runConfig.DigitRegions != null)
                 {
                     writeName = "数字区域坐标";
                     _modbusMaster.WriteMultipleRegisters(devId, Constant.MB_REGISTER_ADD_DIGIT_REGION, runConfig.DigitRegions);
                     GenerateValueLog(writeName, runConfig.DigitRegion);
-                    GenerateSendAndRecvHexLog(writeName);
+                    GenerateSendAndRecvHexLog(false, writeName);
                 }
 
-                if (runConfig.FTIndRegionWriteFlag && runConfig.FTIndRegions != null)
+                if (runConfig.FTIndRegionWRFlag && runConfig.FTIndRegions != null)
                 {
                     writeName = "指针水表首尾同刻度坐标";
                     _modbusMaster.WriteMultipleRegisters(devId, Constant.MB_REGISTER_ADD_FTIND_REGION, runConfig.FTIndRegions);
                     GenerateValueLog(writeName, runConfig.FTIndRegion);
-                    GenerateSendAndRecvHexLog(writeName);
+                    GenerateSendAndRecvHexLog(false, writeName);
                 }
+
+                writeName = "配置确认";
+                _modbusMaster.WriteSingleRegister(devId, Constant.MB_REGISTER_ADD_EFFECT_FLAG, 1);
+                GenerateValueLog(writeName, runConfig.LatestValue);
+                GenerateSendAndRecvHexLog(false, writeName);
+
+
+                blWrite = true;
             }
             catch (TimeoutException ex)
             {
@@ -393,9 +483,9 @@ namespace MV485.helper
         }
 
         //发送和接收数据的日志
-        public void GenerateSendAndRecvHexLog(string readName)
+        public void GenerateSendAndRecvHexLog(bool blRead,string wrName)
         {
-            _message = $"读取{readName},发送: {GetSendDataString()}, 接收: {GetRecvDataString()}";
+            _message = $"{(blRead?"读取":"写入")}{wrName},发送: {GetSendDataString()}, 接收: {GetRecvDataString()}";
             RWLog?.Invoke(_message);
         }
 

+ 29 - 25
MV485/helper/Tools.cs

@@ -29,42 +29,46 @@ namespace MV485.helper
         };
 
         //水表类型
-        public static List<KeyValuePair<int, string>> MeterTypeList =
-            new List<KeyValuePair<int, string>>()
+        public static List<KeyValuePair<byte, string>> MeterTypeList =
+            new List<KeyValuePair<byte, string>>()
             {
                         //new KeyValuePair<int, string>(0,"0 - 未知"),
-                        new KeyValuePair<int, string>(1, "1 - 数字+指针"),
-                        new KeyValuePair<int, string>(3, "3 - 全数字"),
-                        new KeyValuePair<int, string>(2, "2 - 全指针")
+                        new KeyValuePair<byte, string>(1, "1 - 数字+指针"),
+                        new KeyValuePair<byte, string>(3, "3 - 全数字"),
+                        new KeyValuePair<byte, string>(2, "2 - 全指针")
             };
 
-        public static List<KeyValuePair<int, string>> FlowRateList =
-            new List<KeyValuePair<int, string>>()
+        public static List<KeyValuePair<ushort, string>> FlowRateList =
+            new List<KeyValuePair<ushort, string>>()
             {
-                        new KeyValuePair<int, string>(3, "DN15 - 3"),
-                        new KeyValuePair<int, string>(5, "DN20 - 5"),
-                        new KeyValuePair<int, string>(8, "DN25 - 8"),
-                        new KeyValuePair<int, string>(12, "DN32 - 12"),
-                        new KeyValuePair<int, string>(20, "DN40 - 20"),
-                        new KeyValuePair<int, string>(32, "DN50 - 32"),
-                        new KeyValuePair<int, string>(60, "DN65 - 60"),
-                        new KeyValuePair<int, string>(79, "DN80 - 79"),
-                        new KeyValuePair<int, string>(125, "DN100 - 125"),
-                        new KeyValuePair<int, string>(200, "DN125 - 200"),
-                        new KeyValuePair<int, string>(312, "DN150 - 312"),
-                        new KeyValuePair<int, string>(500, "DN200 - 500"),
-                        new KeyValuePair<int, string>(787, "DN250 - 787"),
-                        new KeyValuePair<int, string>(1250, "DN300 - 1250"),
-                        new KeyValuePair<int, string>(2000, "DN400 - 2000"),
-                        new KeyValuePair<int, string>(3125, "DN500 - 3125"),
-                        new KeyValuePair<int, string>(5000, "DN600 - 5000"),
-                        new KeyValuePair<int, string>(7875, "DN800 - 7875"),
+                        new KeyValuePair<ushort, string>(3, "DN15 - 3"),
+                        new KeyValuePair<ushort, string>(5, "DN20 - 5"),
+                        new KeyValuePair<ushort, string>(8, "DN25 - 8"),
+                        new KeyValuePair<ushort, string>(12, "DN32 - 12"),
+                        new KeyValuePair<ushort, string>(20, "DN40 - 20"),
+                        new KeyValuePair<ushort, string>(32, "DN50 - 32"),
+                        new KeyValuePair<ushort, string>(60, "DN65 - 60"),
+                        new KeyValuePair<ushort, string>(79, "DN80 - 79"),
+                        new KeyValuePair<ushort, string>(125, "DN100 - 125"),
+                        new KeyValuePair<ushort, string>(200, "DN125 - 200"),
+                        new KeyValuePair<ushort, string>(312, "DN150 - 312"),
+                        new KeyValuePair<ushort, string>(500, "DN200 - 500"),
+                        new KeyValuePair<ushort, string>(787, "DN250 - 787"),
+                        new KeyValuePair<ushort, string>(1250, "DN300 - 1250"),
+                        new KeyValuePair<ushort, string>(2000, "DN400 - 2000"),
+                        new KeyValuePair<ushort, string>(3125, "DN500 - 3125"),
+                        new KeyValuePair<ushort, string>(5000, "DN600 - 5000"),
+                        new KeyValuePair<ushort, string>(7875, "DN800 - 7875"),
             };
 
         public static List<double> UnitList = new List<double>
         {
             1000,100,10,1,0.1,0.01,0.001,0.0001
         };
+        public static Dictionary<byte, double> LastUnitMap = new Dictionary<byte, double>
+        {
+            { 0,0.0001},{1,0.001},{2,0.01},{3,0.1},{4,1},{5,10},{6,100},{7,1000},{8,10000}
+        };
 
         public static List<int> NumList = new List<int>
         {

+ 332 - 0
MV485/helper/XModemReceiver.cs

@@ -0,0 +1,332 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Ports;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MV485.helper
+{
+    //发送方:
+    //等待接收方发送 ‘C’(表示使用CRC模式)或 NAK(使用Checksum)。
+    //发送文件数据,每次128字节,并计算 Checksum 或 CRC。
+    //发送 EOT(表示传输完成)。
+    //接收方:
+    //发送 ‘C’ 或 NAK 以请求数据。
+    //解析数据包并校验。
+    //发送 ACK 确认正确接收。
+
+        //每一帧的结构如下,共 132 字节:
+        //字节位置 名称  值 / 范围 描述
+        //1	SOH	0x01	起始标志,表示是 128 字节数据包
+        //2	Block Number    0x01 ~ 0xFF	当前包编号(从1开始)
+        //3	Block Number ^	255 - Block Number  包编号的反码(用于校验)
+        //4-131	Data[128] 任意数据    实际数据,末尾不足用 0x1A 填充
+        //132	Checksum 所有数据字节的累加和  校验和:Data[0] + ... + Data[127](8-bit)
+
+
+    public class XModemReceiver
+    {
+        private SerialPort _serialPort;
+        private string _portName;
+        private const int FRAME_MAX_MILLISECONDS = 2000;    //每一帧最大超时时间
+        private const int PACKET_SIZE = 128;
+        private const byte SOH = 0x01;      //128字节的头标志
+        private const byte EOT = 0x04;      //发送结束的标志
+        private const byte ACK = 0x06;
+        private const byte NAK = 0x15;      //
+        private const byte CAN = 0x18;
+        private const byte C = 0x43;
+
+        private string _message;
+        public event Action<string> RWLog;
+        public event Action<bool> SerialConnected;      //串口是否已连接
+        public event Action<bool> SerialStatusChanged;  //串口连接状态变化
+
+        public event Action<ushort,ushort> OnProgress;
+        private bool _isReceiving = false;
+
+        public XModemReceiver()
+        {
+
+        }
+
+
+        //开始接收文件
+        public bool StartReceiveFile(string portName,int baudRate,string savePath,ushort fileSize)
+        {
+            bool blResult = false;
+            _portName = portName;
+
+            if (OpenSerial(portName, baudRate))
+            {
+                _isReceiving = true;
+                blResult = ReceiveFile(savePath,fileSize);
+            }
+
+            return blResult;
+        }
+
+        private bool ReceiveFile(string savePath,ushort fileSize)
+        {
+            bool blResult = false;
+            List<byte> dataByteList = new List<byte>();
+            int blockNumber = 1;
+
+            try
+            {
+                _serialPort.DiscardInBuffer();
+                //发送NAK请求数据(使用校验和)
+                _serialPort.Write(new byte[] { NAK }, 0, 1);
+
+                while (_isReceiving)
+                {
+                    //---开始接收一帧数据---
+                    DateTime frameStartTime = DateTime.Now;
+                    bool frameReceived = false;
+
+                    // 在内部循环中尝试接收完整一帧,2秒内必须完成
+                    while (!frameReceived)
+                    {
+                        if ((DateTime.Now - frameStartTime).TotalMilliseconds > 2000)
+                        {
+                            RWLog?.Invoke("接收当前帧超时(超过2秒),终止文件接收。");
+                            return false;
+                        }
+
+                        //10ms内读取串口中的第一个字节
+                        int b = ReadByte(100);
+                        if (b == -1)
+                        {
+                            // 未读取到数据,发送 NAK 重试
+                            _serialPort.Write(new byte[] { NAK }, 0, 1);
+                            continue;
+                        }
+
+                        //接收到EOT,表示文件传输完成
+                        if (b == EOT)
+                        {
+                            _serialPort.Write(new byte[] { ACK }, 0, 1);
+                            RWLog?.Invoke("文件传输完成");
+                            blResult = true;
+                            frameReceived = true; // 退出当前帧循环
+                            break;
+                        }
+
+                        //头标志不是SOH(128字节的xModem的头标志)
+                        if (b != SOH)
+                        {
+                            _serialPort.Write(new byte[] { NAK }, 0, 1);
+                            RWLog?.Invoke($"数据帧头标志错误: {b:X2}");
+                            continue;
+                        }
+
+                        // 读取包号与包号补码
+                        int blockNum = ReadByte(100);
+                        int blockNumComp = ReadByte(100);
+                        if ((blockNum + blockNumComp) != 0xFF)
+                        {
+                            _serialPort.Write(new byte[] { NAK }, 0, 1);
+                            RWLog?.Invoke("包号补码错误");
+                            continue;
+                        }
+
+                        // 检查是否为重复帧(重复帧直接 ACK 后跳过)
+                        if (blockNum == ((blockNumber - 1) & 0xFF))
+                        {
+                            RWLog?.Invoke($"重复数据包 #{blockNum},重新发送 ACK");
+                            _serialPort.Write(new byte[] { ACK }, 0, 1);
+                            frameReceived = true;
+                            break;
+                        }
+
+                        // 读取 128 字节数据,整个读取也必须在2秒内完成
+                        if (blockNum != (blockNumber & 0xFF))
+                        {
+                            _serialPort.Write(new byte[] { NAK }, 0, 1);
+                            RWLog?.Invoke("包号不符合预期");
+                            continue;
+                        }
+
+                        //读取128字节的数据
+                        byte[] data = new byte[PACKET_SIZE];
+                        int read = 0;                        
+                        while (read < PACKET_SIZE && (DateTime.Now - frameStartTime).TotalMilliseconds < 2000)
+                        {
+                            if (_serialPort.BytesToRead > 0)
+                            {
+                                int n = _serialPort.Read(data, read, PACKET_SIZE - read);
+                                read += n;
+                            }
+                            else
+                            {
+                                Thread.Sleep(10);
+                            }
+                        }
+
+                        if (read < PACKET_SIZE)
+                        {
+                            RWLog?.Invoke("数据包接收超时或不完整");
+                            _serialPort.Write(new byte[] { NAK }, 0, 1);
+                            continue;
+                        }
+
+                        //读取校验和
+                        int checksum = ReadByte(100);
+                        int sum = 0;
+                        foreach (byte bt in data)
+                        {
+                            sum = (sum + bt) % 256;
+                        }
+
+                        //校验和不通过
+                        if (checksum != sum)
+                        {
+                            _serialPort.Write(new byte[] { NAK }, 0, 1);
+                            RWLog?.Invoke($"校验和验证不通过");
+                            continue;
+                        }
+
+                        // 当前帧数据接收成功
+                        _serialPort.Write(new byte[] { ACK }, 0, 1);
+                        dataByteList.AddRange(data);
+                        RWLog?.Invoke($"已接受第{blockNumber}包数据");
+                        blockNumber++;
+                        frameReceived = true;
+                    }// end of while(!frameReceived)
+
+                    //double progress = ((double)dataByteList.Count) / fileSize;
+                    //progress = progress > 1 ? 1.0 : progress;
+                    OnProgress?.Invoke((ushort)dataByteList.Count,fileSize);
+
+                    // 如果文件数据已经足够,则退出整体接收循环
+                    if (dataByteList.Count >= fileSize || blResult)
+                        break;
+
+                }// end of while(_isReceiving)
+
+                //保存文件
+                if (blResult && dataByteList.Count >= fileSize)
+                {
+                    //移除填充的0x1A,因为已经知道文件大小了
+                    byte[] fileBytes = new byte[fileSize];
+                    Array.Copy(dataByteList.ToArray(), fileBytes, fileSize);
+
+                    string directory = Path.GetDirectoryName(savePath);
+                    if (!Directory.Exists(directory))
+                    {
+                        Directory.CreateDirectory(directory);
+                    }
+                    File.WriteAllBytes(savePath, fileBytes);
+                    RWLog?.Invoke($"已保存文件{savePath}");                    
+                }
+            }
+            catch(IOException ex)
+            {
+                _message = $"接收文件IO错误:{ex.Message}";
+                RWLog?.Invoke(_message);
+                blResult = false;
+            }
+            catch(Exception ex)
+            {
+                _message = $"接收文件错误:{ex.Message}";
+                RWLog?.Invoke(_message);
+                blResult = false;
+            }
+
+            return blResult;
+        }
+
+
+
+        public void StopReceiveFile()
+        {
+            _isReceiving = false;
+            if (_serialPort?.IsOpen == true)
+            {
+                _serialPort.Write(new byte[] { CAN, CAN }, 0, 2);
+                Thread.Sleep(100);
+            }
+        }
+
+
+        private int ReadByte(int timeoutMilliseconds)
+        {
+            DateTime deadline = DateTime.Now.AddMilliseconds(timeoutMilliseconds);
+            while (DateTime.Now < deadline)
+            {
+                if (_serialPort.BytesToRead > 0)
+                    return _serialPort.ReadByte();
+                Thread.Sleep(10); // 睡眠 10 毫秒,避免过于频繁地轮询
+            }
+            return -1; // 超时未读取到数据
+        }
+
+        private byte[] TrimTrailingPadding(byte[] data, int maxLength)
+        {
+            if (data.Length > maxLength)
+                Array.Resize(ref data, maxLength);
+            return data;
+        }
+
+        private bool OpenSerial(string portName, int baudRate)
+        {
+            CloseSerial();
+            try
+            {
+                //除去波特率外,其它参数默认为不会被修改
+                SerialPort serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);
+                serialPort.ReadTimeout = 1000;      //读取的超时时间应该短一些
+                serialPort.WriteTimeout = 1000;
+                serialPort.Open();
+
+                _serialPort = serialPort;
+                _message = $"串口{portName}连接成功";
+                RWLog?.Invoke(_message);
+                Logger.Info(_message);
+                SerialConnected?.Invoke(true);
+                return true;
+            }
+            catch (Exception ex)
+            {
+                _message = $"串口{portName}连接失败";
+                RWLog?.Invoke(_message);
+                SerialConnected?.Invoke(false);
+                Logger.Info(_message);
+                Logger.Error(ex.Message);
+                return false;
+            }
+        }
+
+        private void CloseSerial()
+        {
+            try
+            {
+                if (_serialPort != null)
+                {
+                    if (_serialPort.IsOpen)
+                    {
+                        _serialPort.Close();
+                        _message = $"串口{_portName}已关闭";
+                        SerialStatusChanged?.Invoke(false);     //串口关闭
+                        RWLog?.Invoke(_message);
+                        Logger.Info(_message);
+                    }
+                    _serialPort.Dispose();
+                    _serialPort = null;
+                }
+            }
+            catch (Exception ex)
+            {
+                _message = $"关闭串口{_portName}失败: {ex.Message}";
+                RWLog?.Invoke(_message);
+                Logger.Error(_message);
+            }
+        }
+
+        //----------------------------------------------------------
+    }
+
+}

+ 8 - 0
MV485/model/Constant.cs

@@ -8,7 +8,15 @@ namespace MV485.model
 {
     public class Constant
     {
+        public const byte METER_TYPE_NUM_IND = 1;
+        public const byte METER_TYPE_IND = 2;
+        public const byte METER_TYPE_NUM = 3;
+
+        public const ulong CUBE_VALUE = 10000;
+
         //生效标志,接收此标志后写入配置并重启设备
+        public const ushort MB_REGISTER_IMAGE_SIZE = 0x0061;        //1个字节
+
         public const ushort MB_REGISTER_ADD_EFFECT_FLAG = 0x0063;
         //寄存器数量
         public const ushort MB_REGISTER_NUM_EFFECT_FLAG = 1;

+ 37 - 12
MV485/model/RunConfig.cs

@@ -9,37 +9,52 @@ namespace MV485.model
     public class RunConfig
     {
         public byte MeterType { get; set; }
-        public bool MeterTypeWriteFlag { get; set; }
+        public bool MeterTypeWRFlag { get; set; }
 
         public ushort DnValue { get; set; }
-        public bool DnValueWriteFlag { get; set; }
+        public bool DnValueWRFlag { get; set; }
 
         public byte DigitCount { get; set; }
-        public bool DigitCountWriteFlag { get; set; }
+        public bool DigitCountWRFlag { get; set; }
 
         public byte IndCount { get; set; }
-        public bool IndCountWriteFlag { get; set; }
+        public bool IndCountWRFlag { get; set; }
 
         public float BrightValue { get; set; }
-        public bool BrightValueWriteFlag { get; set; }
+        public bool BrightValueWRFlag { get; set; }
 
         public byte LastUnitLevel { get; set; }
-        public bool LastUnitLevelWriteFlag { get; set; }
+        public bool LastUnitLevelWRFlag { get; set; }
 
         public ulong LatestValue { get; set; }
-        public bool LatestValueWriteFlag { get; set; }
+        public bool LatestValueWRFlag { get; set; }
 
         public DateTime LatestTime { get; set; }
-        public bool LatestTImeWriteFlag { get; set; }
+
+        public string LatestTimeStr
+        {
+            get
+            {
+                if(LatestTime == null)
+                {
+                    return "";
+                }
+                else
+                {
+                    return LatestTime.ToString("yyyy-MM-dd HH:mm:ss");
+                }
+            }
+        }
+        public bool LatestTimeWRFlag { get; set; }
 
         public string MeterRegion { get; set; }
-        public bool MeterRegionWriteFlag { get; set; }
+        public bool MeterRegionWRFlag { get; set; }
 
         public string DigitRegion { get; set; }
-        public bool DigitRegionWriteFlag { get; set; }
+        public bool DigitRegionWRFlag { get; set; }
 
         public string FTIndRegion { get; set; }
-        public bool FTIndRegionWriteFlag { get; set; }
+        public bool FTIndRegionWRFlag { get; set; }
 
 
         // 新增属性:按大端序将 LatestValue 转换为 ushort[4]
@@ -200,7 +215,17 @@ namespace MV485.model
 
         public RunConfig()
         {
-
+            MeterTypeWRFlag = false;
+            DnValueWRFlag = false;
+            DigitCountWRFlag = false;
+            IndCountWRFlag = false;
+            BrightValueWRFlag = false;
+            LastUnitLevelWRFlag = false;
+            LatestValueWRFlag = false;
+            LatestTimeWRFlag = false;
+            MeterRegionWRFlag = false;
+            DigitRegionWRFlag = false;
+            FTIndRegionWRFlag = false;
         }
         //-----------------------------------------------
     }

+ 3 - 2
MV485/uc/UCRunConfig.xaml

@@ -236,7 +236,8 @@
                             </Grid.ColumnDefinitions>
                             <TextBlock Grid.Column="0" Text="亮度系数" VerticalAlignment="Center" FontSize="14" />
                             <ComboBox Grid.Column="1" x:Name="cmbBrightVal" Height="26" FontSize="14px"
-                                      IsEditable="True" PreviewTextInput="TextBox_PreviewTextInput"/>
+                                      IsEditable="True" PreviewTextInput="CmbBrightVal_PreviewTextInput"
+                                      DataObject.Pasting="CmbBrightVal_Pasting"/>
                             <Button x:Name="btnBrightVal" Click="BtnBrightVal_Click" 
                                     Grid.Column="2" Content="说明示例" BorderThickness="0 0 0 1" BorderBrush="Blue"
                                         Height="20" Width="60" Background="Transparent" Foreground="Blue" FontSize="14" />
@@ -250,7 +251,7 @@
                                 <ColumnDefinition Width="80" />
                             </Grid.ColumnDefinitions>
                             <TextBlock x:Name="txtLastUnitTitle" Grid.Column="0" Text="尾数单位" VerticalAlignment="Center" FontSize="14" TextWrapping="Wrap" />
-                            <ComboBox Grid.Column="1" x:Name="cmbLastUnit" Height="26" FontSize="14px"/>
+                            <ComboBox Grid.Column="1" x:Name="cmbLastUnit" SelectedValuePath="Key" DisplayMemberPath="Value" Height="26" FontSize="14px"/>
                             <TextBlock Grid.Column="2" Text="m³" FontSize="14" Foreground="Black" Margin="5 0 0 0"
                                            VerticalAlignment="Center" />
                             <Button x:Name="btnLastUnit" Click="BtnLastUnit_Click" 

+ 428 - 10
MV485/uc/UCRunConfig.xaml.cs

@@ -6,6 +6,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Text;
+using System.Text.RegularExpressions;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
@@ -28,6 +29,10 @@ namespace MV485.uc
         private static readonly string LogFilePath =
             Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Documents", "runConfig_log.txt");
 
+        private static readonly string ImageFilePath =
+            Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Documents", "runConfig_read.jpg");
+
+
         private RWRunConfig _rwRunConfig;   // = new RWRunConfig();
         private List<LogEntry> _logEntries = new List<LogEntry>();
 
@@ -103,7 +108,7 @@ namespace MV485.uc
         {
             cmbMeterType.ItemsSource = Tools.MeterTypeList;
             cmbFlowRate.ItemsSource = Tools.FlowRateList;
-            cmbLastUnit.ItemsSource = Tools.UnitList;
+            cmbLastUnit.ItemsSource = Tools.LastUnitMap; //.UnitList;
             cmbNumCount.ItemsSource = Tools.NumList;
             cmbIndCount.ItemsSource = Tools.NumList;
             cmbBrightVal.ItemsSource = Tools.BrightList;
@@ -162,11 +167,65 @@ namespace MV485.uc
         }
 
         //读取水表图像
-        private void BtnReadImage_Click(object sender, RoutedEventArgs e)
+        private async void BtnReadImage_Click(object sender, RoutedEventArgs e)
         {
+            if (!GetSerialParameter(out string portName, out byte devId, out int baudrate))
+            {
+                return;
+            }
 
-        }
+            ushort imageSize = _rwRunConfig.ReadImageSize(portName, baudrate, devId);
 
+            if(imageSize == 0)
+            {
+                MessageBox.Show(Application.Current.MainWindow, "没有获取到照片文件的大小", "提示",
+                    MessageBoxButton.OK, MessageBoxImage.Warning);
+                return;
+            }
+
+            //开始通过xModem协议读取照片
+            string titleInfo = "开始接收采样时照片";
+            WaitWindow waitWindow = new WaitWindow(titleInfo)
+            {
+                Owner = Application.Current.MainWindow,
+                WindowStartupLocation = WindowStartupLocation.CenterOwner
+            };
+            waitWindow.Show();
+
+            XModemReceiver receiver = new XModemReceiver();
+            receiver.OnProgress += (receiveSize, fileSize) =>
+            {
+                waitWindow.Dispatcher.Invoke(() =>
+                {
+                    waitWindow.TitleInfo = $"已接受{receiveSize}/{fileSize}";
+                });
+            };
+            receiver.RWLog += message =>
+            {
+                AppendLog(message);
+            };
+
+            receiver.SerialConnected += blConnected =>
+            {
+
+            };
+            receiver.SerialStatusChanged += blStatusChanged =>
+            {
+
+            };
+            try
+            {
+                bool blRecv = await Task.Run(() =>
+                {
+                    return receiver.StartReceiveFile(portName, baudrate, ImageFilePath, imageSize);
+                });
+            }
+            catch { }
+            finally
+            {
+                waitWindow.Close();
+            }
+        }
 
         //---配置
         //选择水表类型
@@ -174,17 +233,17 @@ namespace MV485.uc
         {
             if(cmbMeterType.SelectedValue != null)
             {
-                int meterType = (int)cmbMeterType.SelectedValue;
+                byte meterType = (byte)cmbMeterType.SelectedValue;
                 ChangeInfoByMeterType(meterType);
             }
         }
 
-        private void ChangeInfoByMeterType(int meterType)
+        private void ChangeInfoByMeterType(byte meterType)
         {
-            if (meterType == 1 || meterType == 3)
+            if (meterType == Constant.METER_TYPE_NUM_IND || meterType == Constant.METER_TYPE_NUM)
             {
                 txtLastUnitTitle.Text = "数字区\n最小单位";
-                txtFeatureRegionTitle.Text = "数字区范围";
+                txtFeatureRegionTitle.Text = "数字区域\n范围坐标";
                 if (meterType == 1)
                 {
                     grdIndCount.Visibility = Visibility.Visible;
@@ -196,7 +255,7 @@ namespace MV485.uc
                     grdNumCount.Visibility = Visibility.Visible;
                 }
             }
-            else if (meterType == 2)
+            else if (meterType == Constant.METER_TYPE_IND)
             {
                 txtLastUnitTitle.Text = "尾指针的\n单位";
                 txtFeatureRegionTitle.Text = "首位指针\n同刻度坐标";
@@ -264,6 +323,25 @@ namespace MV485.uc
         //图片标注按照
         private void BtnMarkMeter_Click(object sender, RoutedEventArgs e)
         {
+            //做测试
+            if (!string.IsNullOrWhiteSpace(txtMeterRegion.Text))
+            {
+                txtMeterRegion.Text = "";
+                txtFeatureRegion.Text = "";
+            }
+            else if(cmbMeterType.SelectedValue != null)
+            {
+                byte meterType = (byte)cmbMeterType.SelectedValue;
+                txtMeterRegion.Text = "0,0 319,239";
+                if (meterType == Constant.METER_TYPE_NUM_IND || meterType == Constant.METER_TYPE_NUM)
+                {
+                    txtFeatureRegion.Text = "122,23 122,53 249,55 249,25";
+                }
+                else
+                {
+                    txtFeatureRegion.Text = "132,74 129,186";
+                }
+            }
 
         }
 
@@ -320,6 +398,7 @@ namespace MV485.uc
                 if(runConfig != null)
                 {
                     //加载数据到页面视图
+                    LoadRunConfigView(runConfig);
                     AppendLog("已成功读取设备运行参数");
                 }
                 else
@@ -334,12 +413,251 @@ namespace MV485.uc
             }
         }
 
-        //设置配置按钮
-        private void BtnSetConfig_Click(object sender, RoutedEventArgs e)
+
+        private void LoadRunConfigView(RunConfig runConfig)
         {
+            grdIndCount.Visibility = Visibility.Collapsed;
+            grdNumCount.Visibility = Visibility.Collapsed;
+
+            if (runConfig.MeterTypeWRFlag)
+            {
+                cmbMeterType.SelectedValue = runConfig.MeterType;
+            }
+            if (runConfig.DnValueWRFlag)
+            {
+                cmbFlowRate.Text = runConfig.DnValue.ToString();
+            }
+
+            if (runConfig.DigitCountWRFlag && 
+                (runConfig.MeterType == Constant.METER_TYPE_NUM_IND || runConfig.MeterType == Constant.METER_TYPE_NUM))
+            {
+                cmbNumCount.Text = runConfig.DigitCount.ToString();
+                grdNumCount.Visibility = Visibility.Visible;
+            }
 
+            if (runConfig.IndCountWRFlag && 
+                (runConfig.MeterType == Constant.METER_TYPE_NUM_IND || runConfig.MeterType == Constant.METER_TYPE_IND ))
+            {
+                cmbIndCount.Text = runConfig.IndCount.ToString();
+                grdIndCount.Visibility = Visibility.Visible;
+            }
+
+            if (runConfig.BrightValueWRFlag)
+            {
+                cmbBrightVal.Text = runConfig.BrightValue.ToString();
+            }
+            if (runConfig.LastUnitLevelWRFlag)
+            {
+                cmbLastUnit.SelectedValue = runConfig.LastUnitLevel;
+            }
+
+            if(runConfig.LatestValueWRFlag && runConfig.LatestTimeWRFlag && runConfig.LatestTime != null)
+            {
+                txtLastValue.Text = ((int)(runConfig.LatestValue / Constant.CUBE_VALUE)).ToString();
+                dtpLastValueTime.Text = runConfig.LatestTime.ToString("yyyy-MM-dd HH:mm");
+                    //runConfig.LatestTimeStr;
+            }
+
+            if (runConfig.MeterRegion != null)
+            {
+                txtMeterRegion.Text = runConfig.MeterRegion;
+            }
+
+            if (runConfig.DigitRegionWRFlag && 
+                (runConfig.MeterType == Constant.METER_TYPE_NUM_IND || runConfig.MeterType == Constant.METER_TYPE_NUM ))
+            {
+                txtFeatureRegionTitle.Text = "数字区域\n坐标";
+                txtFeatureRegion.Text = runConfig.DigitRegion;
+            }
+            else if (runConfig.FTIndRegionWRFlag && (runConfig.MeterType == Constant.METER_TYPE_IND ))
+            {
+                txtFeatureRegionTitle.Text = "首位指针\n同刻度坐标";
+                txtFeatureRegion.Text = runConfig.FTIndRegion;
+            }
         }
 
+        //设置配置按钮
+        private async void BtnSetConfig_Click(object sender, RoutedEventArgs e)
+        {
+            //串口号
+            if (cmbPortNames.SelectedItem == null)
+            {
+                MessageBox.Show(Application.Current.MainWindow, "请选择串口", "提示",
+                    MessageBoxButton.OK, MessageBoxImage.Warning);
+                return;
+            }
+            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;
+            }
+            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;
+            }
+            ConfigManager.Instance.UpdateConfig(ConfigKey.ConfigDevId, devid.ToString());
+
+            int baudrate = int.Parse(sBaudrate);
+
+            RunConfig runConfig = new RunConfig();
+
+            //判断输入项的正确性
+            if(cmbMeterType.SelectedItem == null)
+            {
+                MessageBox.Show(Application.Current.MainWindow, "请选择表类型", "提示",
+                    MessageBoxButton.OK, MessageBoxImage.Warning);
+                return;
+            }
+            runConfig.MeterTypeWRFlag = true;
+            runConfig.MeterType = (byte)cmbMeterType.SelectedValue;
+            
+            if(cmbFlowRate.SelectedItem != null)
+            {
+                runConfig.DnValueWRFlag = true;
+                runConfig.DnValue = (ushort)cmbFlowRate.SelectedValue;
+            }
+            else if(ushort.TryParse(cmbFlowRate.Text.Trim(),out ushort dnValue))
+            {
+                runConfig.DnValueWRFlag = true;
+                runConfig.DnValue = dnValue;
+            }
+            else
+            {
+                MessageBox.Show(Application.Current.MainWindow, "请输入水表口径对应的每小时最大水流", "提示",
+                    MessageBoxButton.OK, MessageBoxImage.Warning);
+                return;
+            }
+
+            if (runConfig.MeterType == Constant.METER_TYPE_NUM_IND || runConfig.MeterType == Constant.METER_TYPE_NUM)
+            {
+                if (string.IsNullOrWhiteSpace(cmbNumCount.Text) ||
+                    !byte.TryParse(cmbNumCount.Text, out byte digitCount) ||
+                    !(digitCount >0 && digitCount <= 10))
+                {
+                    MessageBox.Show(Application.Current.MainWindow, "请输入数字个数", "提示",
+                        MessageBoxButton.OK, MessageBoxImage.Warning);
+                    return;
+                }
+                runConfig.DigitCountWRFlag = true;
+                runConfig.DigitCount = digitCount;
+            }
+
+            if(runConfig.MeterType == Constant.METER_TYPE_NUM_IND || runConfig.MeterType == Constant.METER_TYPE_IND)
+            {
+                if(string.IsNullOrWhiteSpace(cmbIndCount.Text) ||
+                    !byte.TryParse(cmbIndCount.Text,out byte indCount) ||
+                    !(indCount >0 && indCount <=10))
+                {
+                    MessageBox.Show(Application.Current.MainWindow, "请输入指针个数", "提示",
+                        MessageBoxButton.OK, MessageBoxImage.Warning);
+                    return;
+                }
+                runConfig.IndCountWRFlag = true;
+                runConfig.IndCount = indCount;
+            }
+
+            if (string.IsNullOrEmpty(cmbBrightVal.Text.Trim()) || 
+                !float.TryParse(cmbBrightVal.Text.Trim(),out float brightValue) ||
+                !(brightValue >= 0.8 && brightValue <= 2.0))
+            {
+                MessageBox.Show(Application.Current.MainWindow, "请输入正确的亮度系数", "提示",
+                    MessageBoxButton.OK, MessageBoxImage.Warning);
+                return;
+            }
+            runConfig.BrightValueWRFlag = true;
+            runConfig.BrightValue = brightValue;
+
+            if(cmbLastUnit.SelectedItem == null)
+            {
+                MessageBox.Show(Application.Current.MainWindow, "请选择正确的尾数单位", "提示",
+                    MessageBoxButton.OK, MessageBoxImage.Warning);
+                return;
+            }
+            runConfig.LastUnitLevelWRFlag = true;
+            runConfig.LastUnitLevel = (byte)cmbLastUnit.SelectedValue;
+
+            if (string.IsNullOrWhiteSpace(txtLastValue.Text) || 
+                !ulong.TryParse(txtLastValue.Text,out ulong latestValue))
+            {
+                MessageBox.Show(Application.Current.MainWindow, "请输入正确的表底读数", "提示",
+                    MessageBoxButton.OK, MessageBoxImage.Warning);
+                return;
+            }
+            runConfig.LatestValueWRFlag = true;
+            runConfig.LatestValue = latestValue * Constant.CUBE_VALUE;
+            
+            if(string.IsNullOrWhiteSpace(dtpLastValueTime.Text) ||
+                !DateTime.TryParse(dtpLastValueTime.Text,out DateTime dtLatestTime))
+            {
+                MessageBox.Show(Application.Current.MainWindow, "请输入表底读数对应的时间", "提示",
+                    MessageBoxButton.OK, MessageBoxImage.Warning);
+                return;
+            }
+            runConfig.LatestTimeWRFlag = true;
+            runConfig.LatestTime = dtLatestTime;
+
+            if (string.IsNullOrWhiteSpace(txtMeterRegion.Text))
+            {
+                MessageBox.Show(Application.Current.MainWindow, "请输入表盘范围坐标", "提示",
+                    MessageBoxButton.OK, MessageBoxImage.Warning);
+                return;
+            }
+            runConfig.MeterRegionWRFlag = true;
+            runConfig.MeterRegion = txtMeterRegion.Text;
+
+            string featureTitle = (runConfig.MeterType == Constant.METER_TYPE_IND) ?
+                "首尾指针同刻度坐标" : "数字区域范围坐标";
+            if (string.IsNullOrEmpty(txtFeatureRegion.Text))
+            {
+                MessageBox.Show(Application.Current.MainWindow, $"请输入{featureTitle}", "提示",
+                    MessageBoxButton.OK, MessageBoxImage.Warning);
+                return;
+            }
+            if(runConfig.MeterType == Constant.METER_TYPE_IND)
+            {
+                runConfig.FTIndRegionWRFlag = true;
+                runConfig.FTIndRegion = txtFeatureRegion.Text.Trim();
+            }
+            else
+            {
+                runConfig.DigitRegionWRFlag = true;
+                runConfig.DigitRegion = txtFeatureRegion.Text.Trim();
+            }
+
+            //显示等待串口
+            string titleInfo = "正在设置设备的运行参数";
+            WaitWindow waitWindow = new WaitWindow(titleInfo)
+            {
+                Owner = Application.Current.MainWindow,
+                WindowStartupLocation = WindowStartupLocation.CenterOwner
+            };
+            waitWindow.Show();
+
+            try
+            {
+                bool blResult = await Task.Run(() =>
+                {
+                    return _rwRunConfig.WriteRunConfig(portName, baudrate, devid, runConfig);
+                });
+                MessageBox.Show(Application.Current.MainWindow, $"设置设备运行参数{(blResult ? "成功" : "失败")}","提示",
+                    MessageBoxButton.OK, blResult ? MessageBoxImage.Information: MessageBoxImage.Warning);
+            }
+            catch { }
+            finally
+            {
+                waitWindow.Close();
+            }
+        }
 
         private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
         {
@@ -354,6 +672,62 @@ namespace MV485.uc
             }
         }
 
+        // 正则:匹配正整数或正小数(可只输入数字或带一个小数点)
+        private static readonly Regex _numberRegex = new Regex(@"^\d*\.?\d*$");
+        private void CmbBrightVal_PreviewTextInput(object sender, TextCompositionEventArgs e)
+        {
+            //ComboBox comboBox = sender as ComboBox;
+
+            //// 构造输入后的完整字符串
+            //string newText = comboBox.Text
+            //    .Remove(comboBox.SelectionStart, comboBox.SelectionLength)
+            //    .Insert(comboBox.SelectionStart, e.Text);
+
+            //// 验证规则:允许数字、最多一个小数点,且不能以多个小数点开头
+            //bool isValid = Regex.IsMatch(newText, @"^(\d+\.?\d*|\.\d+)$")
+            //            && newText.Count(c => c == '.') <= 1;
+
+            //// 特殊处理:允许删除操作(空字符串)
+            //if (string.IsNullOrEmpty(newText))
+            //{
+            //    e.Handled = false;
+            //    return;
+            //}
+
+            //e.Handled = !isValid;
+            string fullText = GetProposedText((ComboBox)sender, e.Text);
+            e.Handled = !_numberRegex.IsMatch(fullText);
+        }
+        // 组合当前文本和新输入文本
+        private string GetProposedText(ComboBox comboBox, string newText)
+        {
+            var textBox = (comboBox.Template.FindName("PART_EditableTextBox", comboBox) as TextBox);
+            if (textBox == null) return newText;
+
+            string currentText = textBox.Text;
+            int selectionStart = textBox.SelectionStart;
+            int selectionLength = textBox.SelectionLength;
+
+            return currentText.Remove(selectionStart, selectionLength).Insert(selectionStart, newText);
+        }
+
+        private void CmbBrightVal_Pasting(object sender, DataObjectPastingEventArgs e)
+        {
+            if (e.DataObject.GetDataPresent(typeof(string)))
+            {
+                string pasteText = (string)e.DataObject.GetData(typeof(string));
+                string fullText = GetProposedText((ComboBox)sender, pasteText);
+                if (!_numberRegex.IsMatch(fullText))
+                {
+                    e.CancelCommand();
+                }
+            }
+            else
+            {
+                e.CancelCommand();
+            }
+        }
+
         private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
         {
             double totalWidth = lvLogs.ActualWidth;
@@ -383,6 +757,50 @@ namespace MV485.uc
                 File.AppendAllText(LogFilePath, $"{logEntry.Time} - {logEntry.Message}\n");
             });
         }
+
+ 
+        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;
+        }
+
         //------------------------------------------------------------------
     }
 }