Răsfoiți Sursa

新增设备信息配置及MES通信服务接口实现

本次提交包括:
- 新增 DeviceInfo 类,支持设备信息(线别、站别、机台号、MES参数)配置及持久化;
- IConfigService/ConfigService 增加设备信息的读写方法,自动管理 DeviceInfo.cfg;
- SettingViewModel 和 SettingView.xaml 增加设备信息Tab页,支持界面编辑与保存;
- 新增 IMesService 接口及 MesService 实现,支持基于设备信息的 MES GET 通信;
- 项目文件编译项同步更新;
- 优化部分界面绑定与显示细节。
孝锋 徐 7 luni în urmă
părinte
comite
933cdc609e

+ 11 - 0
TeamAAS-VM/Interfaces/IConfigService.cs

@@ -400,6 +400,17 @@ namespace TeamAAS_VP.Interfaces
         /// <param name="cfg">新的配置对象,不能为空。</param>
         void SetScrewDriverConfig(ScrewDriverConfig cfg);
 
+        // Device info
+        /// <summary>
+        /// 获取或创建设备信息配置
+        /// </summary>
+        DeviceInfo GetDeviceInfo();
+
+        /// <summary>
+        /// 保存设备信息配置
+        /// </summary>
+        void SaveDeviceInfo(DeviceInfo deviceInfo);
+
         /// <summary>
         /// 仅将当前内存中的电批配置保存到文件。
         /// </summary>

+ 25 - 0
TeamAAS-VM/Interfaces/IMesService.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.Interfaces
+{
+    /// <summary>
+    /// MES 通信服务接口,提供通过配置的 URL 执行过站及提交信息的 GET 请求。
+    /// </summary>
+    public interface IMesService : IDisposable
+    {
+        /// <summary>
+        /// 使用配置的过站 URL 发起 GET 请求。可传入额外的查询参数。
+        /// 返回 (IsSuccess, ResponseString)。
+        /// </summary>
+        Task<(bool IsSuccess, string Response)> StationGetAsync(IDictionary<string, string> queryParameters = null, CancellationToken cancellationToken = default);
+
+        /// <summary>
+        /// 使用配置的提交 URL 发起 GET 请求。可传入额外的查询参数。
+        /// 返回 (IsSuccess, ResponseString)。
+        /// </summary>
+        Task<(bool IsSuccess, string Response)> SubmitGetAsync(IDictionary<string, string> queryParameters = null, CancellationToken cancellationToken = default);
+    }
+}

+ 57 - 0
TeamAAS-VM/Models/DeviceInfo.cs

@@ -0,0 +1,57 @@
+using Prism.Mvvm;
+using System;
+
+namespace TeamAAS_VP.Models
+{
+    /// <summary>
+    /// 设备信息配置,用于保存线别/站别/机台号以及 MES 相关配置。
+    /// </summary>
+    public class DeviceInfo : BindableBase
+    {
+        private string _line;
+        /// <summary>
+        /// 当前作业的线别
+        /// </summary>
+        public string Line { get => _line; set => SetProperty(ref _line, value); }
+
+        private string _station;
+        /// <summary>
+        /// 当前作业的站别
+        /// </summary>
+        public string Station { get => _station; set => SetProperty(ref _station, value); }
+
+        private string _fixtureId;
+        /// <summary>
+        /// 当前作业的机台编号
+        /// </summary>
+        public string FixtureId { get => _fixtureId; set => SetProperty(ref _fixtureId, value); }
+
+        private bool _enableMes;
+        /// <summary>
+        /// 是否启用 MES 通信功能
+        /// </summary>
+        public bool EnableMES { get => _enableMes; set => SetProperty(ref _enableMes, value); }
+
+        private string _mesStationUrl;
+        /// <summary>
+        /// MES 过站 URL
+        /// </summary>
+        public string MesStationUrl { get => _mesStationUrl; set => SetProperty(ref _mesStationUrl, value); }
+
+        private string _mesSubmitUrl;
+        /// <summary>
+        /// MES 提交 URL
+        /// </summary>
+        public string MesSubmitUrl { get => _mesSubmitUrl; set => SetProperty(ref _mesSubmitUrl, value); }
+
+        public DeviceInfo()
+        {
+            Line = string.Empty;
+            Station = string.Empty;
+            FixtureId = string.Empty;
+            EnableMES = false;
+            MesStationUrl = string.Empty;
+            MesSubmitUrl = string.Empty;
+        }
+    }
+}

+ 54 - 0
TeamAAS-VM/Services/ConfigService.cs

@@ -37,6 +37,7 @@ namespace TeamAAS_VP.Services
             public static readonly string BgTcpIpConfigurationPath = "..//Config//BgTcpIpConfiguration.cfg";
             public static readonly string BgModbusTcpConfigurationPath = "..//Config//BgModbusTcpConfiguration.cfg";
             public static readonly string SystemConfigurationPath = "..//Config//SystemConfiguration.cfg";
+            public static readonly string DeviceInfoConfigurationPath = "..//Config//DeviceInfo.cfg";
             // Feeder清料任务配置路径
             public static readonly string FeederClearanceTasksConfigurationPath = "..//Config//FeederClearanceTasksConfiguration.cfg";
             // 光源控制器配置路径
@@ -60,6 +61,9 @@ namespace TeamAAS_VP.Services
         // 光源控制器配置
         private ObservableCollection<LightControllerConfig> LightControllers { get; set; }
 
+        // Device info
+        private DeviceInfo _deviceInfo;
+
         private PlcAddressConfig PlcAddressConfig { get; set; }
         // 单个电批配置
         private TeamAAS_VP.Models.ScrewDriver.ScrewDriverConfig _screwDriverConfig;
@@ -209,6 +213,15 @@ namespace TeamAAS_VP.Services
                     _screwDriverConfig = new TeamAAS_VP.Models.ScrewDriver.ScrewDriverConfig();
                     FileHelper.WriteJsonFile(_screwDriverConfig, ConfigPaths.ScrewDriverConfigurationPath);
                 }
+
+                // load device info
+                if (File.Exists(ConfigPaths.DeviceInfoConfigurationPath))
+                    _deviceInfo = FileHelper.ReadJsonFile<DeviceInfo>(ConfigPaths.DeviceInfoConfigurationPath);
+                else
+                {
+                    _deviceInfo = new DeviceInfo();
+                    FileHelper.WriteJsonFile(_deviceInfo, ConfigPaths.DeviceInfoConfigurationPath);
+                }
             }
         }
 
@@ -244,6 +257,9 @@ namespace TeamAAS_VP.Services
                     FileHelper.WriteJsonFile(ScrewFeeders, ConfigPaths.ScrewFeedersConfigurationPath);
                 // save plc addresses
                 //FileHelper.WriteJsonFile(PlcAddressConfig, "..//Config//PlcAddressConfiguration.cfg");
+                // save device info
+                if (_deviceInfo != null)
+                    FileHelper.WriteJsonFile(_deviceInfo, ConfigPaths.DeviceInfoConfigurationPath);
             }
         }
 
@@ -346,6 +362,44 @@ namespace TeamAAS_VP.Services
         public bool ContainsCamera(Guid id) { lock (_sync) { return Cameras.Any(c => c.Id == id); } }
         #endregion
 
+        #region DeviceInfo
+        /// <summary>
+        /// 获取设备信息配置
+        /// </summary>
+        /// <returns></returns>
+        public DeviceInfo GetDeviceInfo()
+        {
+            lock (_sync)
+            {
+                if (_deviceInfo == null)
+                {
+                    if (File.Exists(ConfigPaths.DeviceInfoConfigurationPath))
+                        _deviceInfo = FileHelper.ReadJsonFile<DeviceInfo>(ConfigPaths.DeviceInfoConfigurationPath);
+                    else
+                    {
+                        _deviceInfo = new DeviceInfo();
+                        FileHelper.WriteJsonFile(_deviceInfo, ConfigPaths.DeviceInfoConfigurationPath);
+                    }
+                }
+                return _deviceInfo;
+            }
+        }
+
+        /// <summary>
+        /// 保存设备信息
+        /// </summary>
+        /// <param name="deviceInfo"></param>
+        public void SaveDeviceInfo(DeviceInfo deviceInfo)
+        {
+            if (deviceInfo == null) return;
+            lock (_sync)
+            {
+                _deviceInfo = deviceInfo;
+                FileHelper.WriteJsonFile(_deviceInfo, ConfigPaths.DeviceInfoConfigurationPath);
+            }
+        }
+        #endregion
+
         #region Robots
         /// <summary>
         /// 获取所有机器人配置的只读集合。

+ 162 - 0
TeamAAS-VM/Services/MesService.cs

@@ -0,0 +1,162 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using TeamAAS_VP.Interfaces;
+using TeamAAS_VP.Models;
+
+namespace TeamAAS_VP.Services
+{
+    /// <summary>
+    /// 基于 <see cref="HttpClient"/> 的 MES 通信服务实现。
+    /// 该服务通过 <see cref="IConfigService"/> 获取设备配置中的 MES 接口地址,
+    /// 提供对站点查询(StationGetAsync)和提交(SubmitGetAsync)的 GET 请求封装。
+    /// </summary>
+    public class MesService : IMesService
+    {
+        /// <summary>
+        /// 用于执行 HTTP 请求的客户端实例。建议在服务生命周期内重用。
+        /// </summary>
+        private readonly HttpClient _httpClient;
+
+        /// <summary>
+        /// 配置服务,用于获取设备信息(包含 MES URL)。
+        /// </summary>
+        private readonly IConfigService _configService;
+
+        /// <summary>
+        /// 标记实例是否已释放,防止重复释放。
+        /// </summary>
+        private bool _disposed;
+
+        /// <summary>
+        /// 使用指定的配置服务创建 <see cref="MesService"/> 实例。
+        /// </summary>
+        /// <param name="configService">用于获取设备配置信息的实现,不能为空。</param>
+        /// <exception cref="ArgumentNullException">当 <paramref name="configService"/> 为 null 时抛出。</exception>
+        public MesService(IConfigService configService)
+        {
+            _configService = configService ?? throw new ArgumentNullException(nameof(configService));
+            _httpClient = new HttpClient();
+            _disposed = false;
+        }
+
+        /// <summary>
+        /// 根据基地址和查询参数构建完整 URL。
+        /// - 如果 <paramref name="baseUrl"/> 为空或空白,返回 null。
+        /// - 如果 <paramref name="queryParameters"/> 为空或不包含元素,返回原始 <paramref name="baseUrl"/>。
+        /// - 否则对键和值进行 URL 编码并拼接为查询字符串(以 '?' 或 '&amp;' 作为分隔)。
+        /// </summary>
+        /// <param name="baseUrl">基础 URL,例如 "http://example.com/api"。</param>
+        /// <param name="queryParameters">要附加的查询参数字典,可为 null。</param>
+        /// <returns>生成的完整 URL 字符串,或在无效 baseUrl 时返回 null。</returns>
+        private static string BuildUrl(string baseUrl, IDictionary<string, string> queryParameters)
+        {
+            if (string.IsNullOrWhiteSpace(baseUrl)) return null;
+            if (queryParameters == null || queryParameters.Count == 0) return baseUrl;
+            var sb = new StringBuilder();
+            sb.Append(baseUrl);
+            if (!baseUrl.Contains("?")) sb.Append('?');
+            else if (!baseUrl.EndsWith("?") && !baseUrl.EndsWith("&")) sb.Append('&');
+            foreach (var kv in queryParameters)
+            {
+                sb.Append(Uri.EscapeDataString(kv.Key)).Append('=').Append(Uri.EscapeDataString(kv.Value ?? string.Empty)).Append('&');
+            }
+            if (sb.Length > 0 && sb[sb.Length - 1] == '&') sb.Length--;
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// 向配置中指定的 MES 站点 URL 发起 GET 请求(用于过站查询)。
+        /// 返回一个元组,<c>IsSuccess</c> 表示 HTTP 状态码是否为成功,<c>Response</c> 包含响应文本或错误信息。
+        /// </summary>
+        /// <param name="queryParameters">可选的查询参数字典,将附加到 MES 站点 URL 上。</param>
+        /// <param name="cancellationToken">用于取消请求的令牌。</param>
+        /// <returns>
+        /// 异步任务,结果为 (<c>IsSuccess</c>, <c>Response</c>)。
+        /// - 当缺少配置或 URL 无效时返回 (false, 错误说明)。
+        /// - 当请求被取消时返回 (false, "Canceled")。
+        /// - 其他异常返回 (false, 异常消息)。
+        /// </returns>
+        public async Task<(bool IsSuccess, string Response)> StationGetAsync(IDictionary<string, string> queryParameters = null, CancellationToken cancellationToken = default)
+        {
+            var device = _configService.GetDeviceInfo();
+            if (device == null || string.IsNullOrWhiteSpace(device.MesStationUrl))
+                return (false, "Missing MES station URL");
+
+            var url = BuildUrl(device.MesStationUrl, queryParameters);
+            if (string.IsNullOrWhiteSpace(url)) return (false, "Invalid station URL");
+
+            try
+            {
+                using (var rsp = await _httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false))
+                {
+                    var text = await rsp.Content.ReadAsStringAsync().ConfigureAwait(false);
+                    return (rsp.IsSuccessStatusCode, text);
+                }
+            }
+            catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+            {
+                return (false, "Canceled");
+            }
+            catch (Exception ex)
+            {
+                return (false, ex.Message);
+            }
+        }
+
+        /// <summary>
+        /// 向配置中指定的 MES 提交 URL 发起 GET 请求(用于提交数据/记录)。
+        /// 返回一个元组,<c>IsSuccess</c> 表示 HTTP 状态码是否为成功,<c>Response</c> 包含响应文本或错误信息。
+        /// </summary>
+        /// <param name="queryParameters">可选的查询参数字典,将附加到 MES 提交 URL 上。</param>
+        /// <param name="cancellationToken">用于取消请求的令牌。</param>
+        /// <returns>
+        /// 异步任务,结果为 (<c>IsSuccess</c>, <c>Response</c>)。
+        /// - 当缺少配置或 URL 无效时返回 (false, 错误说明)。
+        /// - 当请求被取消时返回 (false, "Canceled")。
+        /// - 其他异常返回 (false, 异常消息)。
+        /// </returns>
+        public async Task<(bool IsSuccess, string Response)> SubmitGetAsync(IDictionary<string, string> queryParameters = null, CancellationToken cancellationToken = default)
+        {
+            var device = _configService.GetDeviceInfo();
+            if (device == null || string.IsNullOrWhiteSpace(device.MesSubmitUrl))
+                return (false, "Missing MES submit URL");
+
+            var url = BuildUrl(device.MesSubmitUrl, queryParameters);
+            if (string.IsNullOrWhiteSpace(url)) return (false, "Invalid submit URL");
+
+            try
+            {
+                using (var rsp = await _httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false))
+                {
+                    var text = await rsp.Content.ReadAsStringAsync().ConfigureAwait(false);
+                    return (rsp.IsSuccessStatusCode, text);
+                }
+            }
+            catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+            {
+                return (false, "Canceled");
+            }
+            catch (Exception ex)
+            {
+                return (false, ex.Message);
+            }
+        }
+
+        /// <summary>
+        /// 释放服务占用的托管资源(当前仅 <see cref="_httpClient"/>)。
+        /// 本方法为幂等操作,重复调用不会产生异常。
+        /// </summary>
+        public void Dispose()
+        {
+            if (!_disposed)
+            {
+                _httpClient.Dispose();
+                _disposed = true;
+            }
+        }
+    }
+}

+ 3 - 0
TeamAAS-VM/TeamAAS-VP.csproj

@@ -531,6 +531,8 @@
     </Compile>
     <Compile Include="Core\StabilityAnalyzer.cs" />
     <Compile Include="Events\MainTabSwitchNotification.cs" />
+    <Compile Include="Interfaces\IMesService.cs" />
+    <Compile Include="Models\DeviceInfo.cs" />
     <Compile Include="Models\Feeder\ScrewFeederInfo.cs" />
     <Compile Include="Models\NumberStatistics.cs" />
     <Compile Include="Models\ScrewFeederBatchRecord.cs" />
@@ -540,6 +542,7 @@
       <DesignTime>True</DesignTime>
       <DependentUpon>Lang.resx</DependentUpon>
     </Compile>
+    <Compile Include="Services\MesService.cs" />
     <Compile Include="ViewModels\Home\SetAlarmValueViewModel.cs" />
     <Compile Include="ViewModels\Home\TorqueCheckViewModel.cs" />
     <Compile Include="ViewModels\Product\ImageDisplayViewModel.cs" />

+ 4 - 1
TeamAAS-VM/ViewModels/Product/VisionDynamicAccuracyAnalyzerViewModel.cs

@@ -38,6 +38,7 @@ namespace TeamAAS_VP.ViewModels.Product
         ICameraService _cameraService;
         ISystemDatabaseService _systemDatabaseService;
         ICalibrationService _calibrationService;
+        IConfigService _configService;
 
         //测试过程中控制取消的CTS
         CancellationTokenSource _cts;
@@ -304,7 +305,8 @@ namespace TeamAAS_VP.ViewModels.Product
         #endregion
 
         public VisionDynamicAccuracyAnalyzerViewModel(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, IDialogService dialogService,
-            IFeederService feederService, IRobotService robotService, ICameraService cameraService, ISystemDatabaseService systemDatabaseService, ICalibrationService calibrationService)
+            IFeederService feederService, IRobotService robotService, ICameraService cameraService, ISystemDatabaseService systemDatabaseService, ICalibrationService calibrationService,
+            IConfigService configService)
         {
             _regionManager = regionManager;
             _eventAggregator = ea;
@@ -316,6 +318,7 @@ namespace TeamAAS_VP.ViewModels.Product
             _systemDatabaseService = systemDatabaseService;
             MessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(1));
             _calibrationService = calibrationService;
+            _configService = configService;
         }
 
         #region 方法

+ 24 - 1
TeamAAS-VM/ViewModels/Product/VisionStaticAccuracyAnalyzerViewModel.cs

@@ -42,6 +42,7 @@ namespace TeamAAS_VP.ViewModels.Product
         ICameraService _cameraService;
         ISystemDatabaseService _systemDatabaseService;
         ICalibrationService _calibrationService;
+        IConfigService _configService;
 
         //测试过程中控制取消的CTS
         CancellationTokenSource _cts;
@@ -252,7 +253,8 @@ namespace TeamAAS_VP.ViewModels.Product
         #endregion
 
         public VisionStaticAccuracyAnalyzerViewModel(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, IDialogService dialogService,
-            IFeederService feederService, IRobotService robotService, ICameraService cameraService, ISystemDatabaseService systemDatabaseService, ICalibrationService calibrationService)
+            IFeederService feederService, IRobotService robotService, ICameraService cameraService, ISystemDatabaseService systemDatabaseService, ICalibrationService calibrationService,
+            IConfigService configService)
         {
             _regionManager = regionManager;
             _eventAggregator = ea;
@@ -264,6 +266,7 @@ namespace TeamAAS_VP.ViewModels.Product
             _systemDatabaseService = systemDatabaseService;
             MessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(1));
             _calibrationService = calibrationService;
+            _configService = configService;
         }
 
         #region 方法
@@ -498,6 +501,15 @@ namespace TeamAAS_VP.ViewModels.Product
                         metaTable.AddCell(cellVal);
                     }
 
+                    // include device info (line, station, fixture id) but exclude MES flags/urls
+                    var device = _configService?.GetDeviceInfo() ?? _configService?.GetDeviceInfo();
+                    if (device != null)
+                    {
+                        AddMeta("线别", device.Line ?? string.Empty);
+                        AddMeta("站别", device.Station ?? string.Empty);
+                        AddMeta("机台号", device.FixtureId ?? string.Empty);
+                    }
+
                     AddMeta("导出时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                     AddMeta("测试次数(目标)", RepeatCount.ToString());
                     AddMeta("当前进度", $"{CurrentProgress} / {RepeatCount}");
@@ -666,6 +678,17 @@ namespace TeamAAS_VP.ViewModels.Product
                     return mustQuote ? $"\"{esc}\"" : esc;
                 };
 
+                // include device info at top of CSV
+                var device = _configService?.GetDeviceInfo();
+                if (device != null)
+                {
+                    sb.AppendLine("设备信息");
+                    sb.AppendLine($"线别,{EscapeCsv(device.Line)}");
+                    sb.AppendLine($"站别,{EscapeCsv(device.Station)}");
+                    sb.AppendLine($"机台号,{EscapeCsv(device.FixtureId)}");
+                    sb.AppendLine();
+                }
+
                 // 1. 写入表头
                 for (int c = 0; c < TestResult.Columns.Count; c++)
                 {

+ 37 - 0
TeamAAS-VM/ViewModels/SettingViewModel.cs

@@ -326,6 +326,13 @@ namespace TeamAAS_VP.ViewModels
             set { SetProperty(ref _SelectLightController, value); }
         }
 
+        private DeviceInfo _DeviceInfo;
+        public DeviceInfo DeviceInfo
+        {
+            get { return _DeviceInfo; }
+            set { SetProperty(ref _DeviceInfo, value); }
+        }
+
         #endregion
 
         #region 命令
@@ -402,6 +409,9 @@ namespace TeamAAS_VP.ViewModels
         private DelegateCommand _SaveLightControllersCommand;
         public DelegateCommand SaveLightControllersCommand => _SaveLightControllersCommand ?? (_SaveLightControllersCommand = new DelegateCommand(ExecuteSaveLightControllersCommand));
 
+        private DelegateCommand _SaveDeviceInfoCommand;
+        public DelegateCommand SaveDeviceInfoCommand => _SaveDeviceInfoCommand ?? (_SaveDeviceInfoCommand = new DelegateCommand(ExecuteSaveDeviceInfoCommand));
+
         private DelegateCommand _AddScrewFeederCommand;
         public DelegateCommand AddScrewFeederCommand =>
             _AddScrewFeederCommand ?? (_AddScrewFeederCommand = new DelegateCommand(ExecuteAddScrewFeederCommand));
@@ -1305,6 +1315,12 @@ namespace TeamAAS_VP.ViewModels
                 BgModbus = _configService.GetBgModbusTcp();
             }
 
+            if (DeviceInfo == null)
+            {
+                // load device info
+                DeviceInfo = _configService.GetDeviceInfo();
+            }
+
             CurrentLanguage = _configService.GetSystemConfiguration().CurrentLanguage;
 
             // load system parameters into UI
@@ -1517,5 +1533,26 @@ namespace TeamAAS_VP.ViewModels
             }
         }
         #endregion
+
+        #region 设备信息
+        /// <summary>
+        /// 保存设备信息
+        /// </summary>
+        void ExecuteSaveDeviceInfoCommand()
+        {
+            try
+            {
+                if (DeviceInfo != null)
+                {
+                    _configService.SaveDeviceInfo(DeviceInfo);
+                    _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
+                }
+            }
+            catch (Exception ex)
+            {
+                _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
+            }
+        }
+        #endregion
     }
 }

+ 109 - 7
TeamAAS-VM/Views/SettingView.xaml

@@ -213,6 +213,107 @@
                 </Grid>
             </TabItem>
 
+            <!--设备信息-->
+            <TabItem>
+                <TabItem.Header>
+                    <StackPanel HorizontalAlignment="Stretch">
+                        <materialDesign:PackIcon Width="24"
+                                                 Height="24"
+                                                 HorizontalAlignment="Center"
+                                                 Kind="RouterNetwork" />
+                        <TextBlock Style="{StaticResource TabHeaderTextBlockStyle}"
+                                   FontSize="{DynamicResource Font.Size.Body2}"
+                                   Text="设备信息"
+                                   HorizontalAlignment="Stretch" />
+                    </StackPanel>
+                </TabItem.Header>
+
+                <Grid Margin="16">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="Auto" />
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="auto" />
+                        <ColumnDefinition Width="*" />
+                    </Grid.ColumnDefinitions>
+
+                    <TextBlock Grid.Row="0"
+                               Grid.Column="0"
+                               Text="线别:"
+                               HorizontalAlignment="Right"
+                               VerticalAlignment="Center" />
+                    <TextBox Grid.Row="0"
+                             Grid.Column="1"
+                             Text="{Binding DeviceInfo.Line, Mode=TwoWay}"
+                             Margin="6" />
+
+                    <TextBlock Grid.Row="1"
+                               Grid.Column="0"
+                               Text="站别:"
+                               HorizontalAlignment="Right"
+                               VerticalAlignment="Center" />
+                    <TextBox Grid.Row="1"
+                             Grid.Column="1"
+                             Text="{Binding DeviceInfo.Station, Mode=TwoWay}"
+                             Margin="6" />
+
+                    <TextBlock Grid.Row="2"
+                               Grid.Column="0"
+                               Text="机台号:"
+                               HorizontalAlignment="Right"
+                               VerticalAlignment="Center" />
+                    <TextBox Grid.Row="2"
+                             Grid.Column="1"
+                             Text="{Binding DeviceInfo.FixtureId, Mode=TwoWay}"
+                             Margin="6" />
+
+                    <TextBlock Grid.Row="3"
+                               Grid.Column="0"
+                               Text="启用 MES:"
+                               HorizontalAlignment="Right"
+                               VerticalAlignment="Center" />
+                    <CheckBox Grid.Row="3"
+                              Grid.Column="1"
+                              IsChecked="{Binding DeviceInfo.EnableMES, Mode=TwoWay}"
+                              Margin="6" />
+
+                    <TextBlock Grid.Row="4"
+                               Grid.Column="0"
+                               Text="MES 过站 URL:"
+                               HorizontalAlignment="Right"
+                               VerticalAlignment="Center" />
+                    <TextBox Grid.Row="4"
+                             Grid.Column="1"
+                             Text="{Binding DeviceInfo.MesStationUrl, Mode=TwoWay}"
+                             Margin="6" />
+
+                    <TextBlock Grid.Row="5"
+                               Grid.Column="0"
+                               Text="MES 提交 URL:"
+                               HorizontalAlignment="Right"
+                               VerticalAlignment="Center" />
+                    <TextBox Grid.Row="5"
+                             Grid.Column="1"
+                             Text="{Binding DeviceInfo.MesSubmitUrl, Mode=TwoWay}"
+                             Margin="6" />
+
+                    <StackPanel Orientation="Horizontal"
+                                HorizontalAlignment="Right"
+                                Grid.Row="6"
+                                Grid.ColumnSpan="2"
+                                Margin="6">
+                        <Button Style="{StaticResource MaterialDesignRaisedButton}"
+                                Command="{Binding SaveDeviceInfoCommand}">保存</Button>
+                    </StackPanel>
+                </Grid>
+            </TabItem>
+
             <!--机器人-->
             <TabItem>
                 <TabItem.Header>
@@ -1378,7 +1479,8 @@
                             </Grid.RowDefinitions>
                             <Grid.ColumnDefinitions>
                                 <ColumnDefinition Width="auto" />
-                                <ColumnDefinition Width="*"  MinWidth="200"/>
+                                <ColumnDefinition Width="*"
+                                                  MinWidth="200" />
                             </Grid.ColumnDefinitions>
 
                             <!-- Id -->
@@ -1516,7 +1618,7 @@
                                     <ComboBox Grid.Row="2"
                                               Grid.Column="1"
                                               SelectedItem="{Binding SelectLightController.SerialPortConfig.Parity,Mode=TwoWay}"
-                                              ItemsSource="{Binding Source={StaticResource Parity}}"/>
+                                              ItemsSource="{Binding Source={StaticResource Parity}}" />
                                     <TextBlock Text="StopBits:"
                                                Grid.Row="3"
                                                Grid.Column="0"
@@ -1586,7 +1688,7 @@
                     </ScrollViewer>
                 </Grid>
             </TabItem>
-            
+
             <!--螺丝供料器-->
             <TabItem>
                 <TabItem.Header>
@@ -1678,7 +1780,7 @@
                                                        Margin="10,0,0,0" />
                                             <CheckBox IsChecked="{Binding LowLevelAlarmEnabled,Mode=TwoWay}"
                                                       IsEnabled="False"
-                                                       FontSize="{DynamicResource Font.Size.Captions3}" />
+                                                      FontSize="{DynamicResource Font.Size.Captions3}" />
                                         </StackPanel>
                                         <Border Height="2"
                                                 Margin="0"
@@ -1791,14 +1893,14 @@
                                        Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
                                        HorizontalAlignment="Right"
                                        VerticalAlignment="Center"
-                                       Text="是否启用低量报警:"/>
+                                       Text="是否启用低量报警:" />
                             <CheckBox Grid.Row="5"
                                       Grid.Column="1"
                                       Margin="10,0"
                                       IsChecked="{Binding SelectScrewFeeder.LowLevelAlarmEnabled,Mode=TwoWay}"
                                       VerticalAlignment="Center" />
 
-                            
+
                         </Grid>
                     </ScrollViewer>
                 </Grid>
@@ -2580,7 +2682,7 @@
                     </Button>
                 </StackPanel>
             </TabItem>
-            
+
         </TabControl>
 
     </Grid>