Bläddra i källkod

1. 修改Mqtt服务端 验证机制
2. 删除OPCUA服务端 匿名登录机制
3. OPCDAHTTP 工具改造完成

Shun 2 år sedan
förälder
incheckning
04fc1f760d

+ 1 - 0
README.md

@@ -569,4 +569,5 @@ while(true)
 #### 2023-12-25
 1. 修改Mqtt服务端 验证机制
 2. 删除OPCUA服务端 匿名登录机制
+3. OPCDAHTTP 工具改造完成
 

+ 2 - 6
src/YSAI.Core/subscribe/SubscribeOperate.cs

@@ -130,13 +130,9 @@ namespace YSAI.Core.subscription
                 List<string> FillMessage = new List<string>();
                 lock (basics.Address)  //锁住不让其他操作
                 {
-                    for (int i = 0; i < address.AddressArray.Count; i++)
+                    if (basics.Address.AddressArray.RemoveAll(c => c.Equals(address.AddressArray.FirstOrDefault(x => x.Comparer(c).result))) != address.AddressArray.Count)
                     {
-                        //移除不需要的节点订阅
-                        if (basics.Address.AddressArray.RemoveAll(e => e.Equals(address.AddressArray[i])).Equals(0))
-                        {
-                            FillMessage.Add($"[ {address.AddressArray[i].AddressName} ]取消订阅失败");
-                        }
+                        FillMessage.Add($"取消订阅失败");
                     }
                 }
                 GoOn = true;

+ 4 - 1
src/YSAI.Opc/YSAI.Opc.csproj

@@ -17,6 +17,9 @@
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="OPCFoundation.NetStandard.Opc.Ua" Version="1.4.372.106" />
-    <PackageReference Include="YSAI.Core" Version="23.353.7484" />
+    <!--<PackageReference Include="YSAI.Core" Version="23.353.7484" />-->
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\YSAI.Core\YSAI.Core.csproj" />
   </ItemGroup>
 </Project>

+ 6 - 13
src/YSAI.Opc/da/http/OpcDaHttpData.cs

@@ -40,13 +40,6 @@ namespace YSAI.Opc.da.http
             [Display(true, true, true, ParamStructure.dataCate.unmber)]
             public int Port { get; set; } = 6688;
 
-            /// <summary>
-            /// 解密密钥
-            /// </summary>
-            [Description("解密密钥")]
-            [Display(true, true, true, ParamStructure.dataCate.text)]
-            public string? Key { get; set; }
-
             /// <summary>
             /// 请求类型
             /// </summary>
@@ -73,6 +66,12 @@ namespace YSAI.Opc.da.http
             [Description("连接OPC服务")]
             ReqConnectToOPC,
 
+            /// <summary>
+            /// 请求获取指定OPC下已添加的组名清单
+            /// </summary>
+            [Description("获取已添加的组名清单")]
+            ReqGetGroupsOfOPC,
+
             /// <summary>
             /// 请求浏览某个opc服务下的所有标签列表
             /// </summary>
@@ -109,12 +108,6 @@ namespace YSAI.Opc.da.http
             [Description("删除组(该组下已经添加的点位都会被删除)")]
             ReqDelGroupOfOPC,
 
-            /// <summary>
-            /// 请求获取指定OPC下已添加的组名清单
-            /// </summary>
-            [Description("获取已添加的组名清单")]
-            ReqGetGroupsOfOPC,
-
             /// <summary>
             /// 请求批量在某个组下添加标签
             /// </summary>

+ 3 - 1
src/YSAI.Opc/da/http/OpcDaHttpOperate.cs

@@ -372,8 +372,10 @@ namespace YSAI.Opc.da.http
                                 ReqReadItemsValueOfGroup.Response response = param.RData as ReqReadItemsValueOfGroup.Response;
                                 foreach (var itemvalues in response.itemvalues)
                                 {
+                                    //得到值
                                     object? Value = itemvalues.value;
-
+                                    //地址名
+                                    addressDetails.AddressName = itemvalues.itemname;
                                     if (read.ReadGroup)  //直接读取组下面的数据
                                     {
                                         //处理

+ 1 - 1
src/YSAI.Tool.Wpf/Main.xaml.cs

@@ -33,7 +33,7 @@ namespace YSAI.Tool.Wpf
                     if (navigationViews.Count > 0)
                     {
                         //第一次打开显示的界面
-                        navigationViews[0].Navigate(typeof(UaClient));
+                        navigationViews[0].Navigate(typeof(DaHttp));
                     }
                 });
             });

+ 1 - 1
src/YSAI.Tool.Wpf/YSAI.Tool.Wpf.csproj

@@ -35,7 +35,7 @@
 	</ItemGroup>
 	<ItemGroup>
 		<ProjectReference Include="..\YSAI.Core.Wpf\YSAI.Core.Wpf.csproj" />
-		<ProjectReference Include="..\YSAI.Core\YSAI.Core.csproj" />
+		<ProjectReference Include="..\YSAI.Opc\YSAI.Opc.csproj" />
 	</ItemGroup>
 
 	<ItemGroup>

+ 740 - 0
src/YSAI.Tool.Wpf/controllers/DaHttpController.cs

@@ -0,0 +1,740 @@
+using CommunityToolkit.Mvvm.Input;
+using System.Collections.Concurrent;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using YSAI.Core.Wpf.mvvm;
+using YSAI.Model.data;
+using YSAI.Opc.da.http;
+using YSAI.Opc.da.http.structure;
+using YSAI.Tool.Wpf.data;
+using YSAI.Unility;
+using static YSAI.Opc.da.http.OpcDaHttpData;
+
+namespace YSAI.Tool.Wpf.controllers
+{
+    public class DaHttpController : NotifyObject
+    {
+        public DaHttpController()
+        {
+            //加载请求对象
+            if (ComboBoxData == null)
+            {
+                ComboBoxData = new ObservableCollection<ComboBoxDataStructuralBody>();
+            }
+            foreach (var item in EnumTool.EnumToList<OpcDaHttpData.RequestMethod>())
+            {
+                ComboBoxData.Add(new ComboBoxDataStructuralBody() { Key = item.Describe, Value = item.Name });
+            }
+            if (NodeMessage == null)
+            {
+                NodeMessage = new ObservableCollection<NodeMessageStructuralBody>();
+            }
+
+            ComboBoxSelectedData = ComboBoxData[0];
+            //初始化基础数据
+            BasicsData = new Basics();
+            //工具标题
+            ToolTitle = "OpcDa Http工具";
+            //配置文件名
+            FileName = typeof(OpcDaHttpData).Name;
+            //Ascii是否显示
+            AsciiVisibility = Visibility.Collapsed;
+            //Hex是否显示
+            HexVisibility = Visibility.Collapsed;
+            //信息格式;  0 ASCII ; 1 HEX
+            InfoFormat = 0;
+        }
+        private void WindowHelper_OnSkinSwitchEvent(object? sender, EventArgs e)
+        {
+
+        }
+
+        #region 统一需要的数据
+        /// <summary>
+        /// 导出的文件名
+        /// </summary>
+        private string FileName { get; set; }
+        /// <summary>
+        /// 工具标题
+        /// </summary>
+        public string ToolTitle
+        {
+            get => GetProperty(() => ToolTitle);
+            set => SetProperty(() => ToolTitle, value);
+        }
+        /// <summary>
+        /// 数据源
+        /// </summary>
+        public Basics BasicsData
+        {
+            get => GetProperty(() => BasicsData);
+            set => SetProperty(() => BasicsData, value);
+        }
+
+        /// <summary>
+        /// 导入基础数据命令
+        /// </summary>
+        public ICommand IncBasics { get => new RelayCommand(OnIncBasics); }
+
+        /// <summary>
+        /// 导入基础数据
+        /// </summary>
+        public void OnIncBasics()
+        {
+            (string RetFilePath, string RetFileName) Data = Unility.Windows.FileTool.SelectFiles("json");
+            if (!string.IsNullOrEmpty(Data.RetFilePath))
+            {
+                Basics? basics = FileTool.FileToString(Data.RetFilePath).ToJsonEntity<Basics>();
+                if (basics == null)
+                {
+                    Output("导入失败");
+                }
+                else
+                {
+                    Output("导入成功");
+                    BasicsData = basics;
+                }
+            }
+        }
+
+        /// <summary>
+        /// 导出基础数据命令
+        /// </summary>
+        public ICommand ExpBasics { get => new RelayCommand(OnExpBasics); }
+
+        /// <summary>
+        /// 导出基础数据
+        /// </summary>
+        public void OnExpBasics()
+        {
+            string path = Unility.Windows.FileTool.SelectFolder();
+            if (!string.IsNullOrEmpty(path))
+            {
+                FileTool.StringToFile(Path.Combine(path, $"{FileName}[{DateTime.Now.ToString("yyyyMMddHHmmss")}].json"), BasicsData.ToJson());
+                Output("导出成功");
+            }
+        }
+
+        private string _info = string.Empty;
+        /// <summary>
+        /// 信息
+        /// </summary>
+        public string Info
+        {
+            get => _info;
+            set => SetProperty(ref _info, value);
+        }
+
+        /// <summary>
+        /// 信息框事件
+        /// </summary>
+        public ICommand Info_TextChanged { get => new RelayCommand<TextChangedEventArgs>(OnInfo_TextChanged); }
+
+        /// <summary>
+        /// 信息框事件
+        /// 让滚动条一直处在最下方
+        /// </summary>
+        public void OnInfo_TextChanged(System.Windows.Controls.TextChangedEventArgs e)
+        {
+            System.Windows.Controls.TextBox textBox = (System.Windows.Controls.TextBox)e.Source;
+            textBox.SelectionStart = textBox.Text.Length;
+            textBox.SelectionLength = 0;
+            textBox.ScrollToEnd();
+        }
+
+        /// <summary>
+        /// 输出标准消息
+        /// </summary>
+        /// <param name="Data">数据</param>
+        /// <param name="IsDate">需要时间</param>
+        private Task Output(string Data, bool IsDate = true)
+        {
+            return Task.Run(() =>
+            {
+                System.Windows.Application.Current?.Dispatcher.Invoke(delegate ()
+                {
+                    if (Info?.Length > 10000)
+                    {
+                        Info = string.Empty;
+                    }
+                    if (IsDate)
+                    {
+                        Info += $" {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} : {Data}\r\n";
+                    }
+                    else
+                    {
+                        Info += $"{Data}\r\n";
+                    }
+                });
+            });
+        }
+        /// <summary>
+        /// ascii
+        /// </summary>
+        public Visibility AsciiVisibility
+        {
+            get => GetProperty(() => AsciiVisibility);
+            set => SetProperty(() => AsciiVisibility, value);
+        }
+        /// <summary>
+        /// hex
+        /// </summary>
+        public Visibility HexVisibility
+        {
+            get => GetProperty(() => HexVisibility);
+            set => SetProperty(() => HexVisibility, value);
+        }
+
+        /// <summary>
+        /// 接收数据格式
+        /// 0 ASCII
+        /// 1 HEX
+        /// </summary>
+        public int InfoFormat
+        {
+            get => GetProperty(() => InfoFormat);
+            set => SetProperty(() => InfoFormat, value);
+        }
+
+        /// <summary>
+        /// 清空信息命令
+        /// </summary>
+        public ICommand Clear { get => new RelayCommand(OnClear); }
+
+        /// <summary>
+        /// 清空信息
+        /// </summary>
+        public void OnClear()
+        {
+            Info = string.Empty;
+        }
+
+        /// <summary>
+        /// 基础数据的显示隐藏
+        /// </summary>
+        public bool BasicsData_ToggleButtonIsChecked
+        {
+            get => GetProperty(() => BasicsData_ToggleButtonIsChecked);
+            set => SetProperty(() => BasicsData_ToggleButtonIsChecked, value);
+        }
+
+
+        #endregion 统一需要的数据
+
+
+        /// <summary>
+        /// 通信
+        /// </summary>
+        private OpcDaHttpOperate communication;
+
+        /// <summary>
+        /// 节点浏览是否点击
+        /// </summary>
+        public bool NodeBrowseIsChecked
+        {
+            get => GetProperty(() => NodeBrowseIsChecked);
+            set => SetProperty(() => NodeBrowseIsChecked, value);
+        }
+
+        /// <summary>
+        /// 服务名
+        /// </summary>
+        public string ServerName
+        {
+            get => ComboBoxSelectedServerName.Key ?? string.Empty;
+        }
+
+        /// <summary>
+        /// 服务名集合
+        /// </summary>
+        public ObservableCollection<ComboBoxDataStructuralBody> ComboBoxServerName
+        {
+            get => GetProperty(() => ComboBoxServerName);
+            set => SetProperty(() => ComboBoxServerName, value);
+        }
+
+        /// <summary>
+        /// 选中的服务名
+        /// </summary>
+        public ComboBoxDataStructuralBody ComboBoxSelectedServerName
+        {
+            get => GetProperty(() => ComboBoxSelectedServerName);
+            set => SetProperty(() => ComboBoxSelectedServerName, value);
+        }
+
+
+
+        /// <summary>
+        /// 组名
+        /// </summary>
+        public string GroupName
+        {
+            get => GetProperty(() => GroupName);
+            set => SetProperty(() => GroupName, value);
+        }
+
+        /// <summary>
+        /// 节点地址
+        /// </summary>
+        public string DotAddress
+        {
+            get => GetProperty(() => DotAddress);
+            set => SetProperty(() => DotAddress, value);
+        }
+
+        /// <summary>
+        /// 写入数据
+        /// </summary>
+        public string WriteInData
+        {
+            get => GetProperty(() => WriteInData);
+            set => SetProperty(() => WriteInData, value);
+        }
+
+        /// <summary>
+        /// 节点信息集合
+        /// </summary>
+        public ObservableCollection<NodeMessageStructuralBody> NodeMessage
+        {
+            get => GetProperty(() => NodeMessage);
+            set => SetProperty(() => NodeMessage, value);
+        }
+
+        /// <summary>
+        /// 选中的节点信息
+        /// </summary>
+        public NodeMessageStructuralBody NodeMessageSelectedItem
+        {
+            get => GetProperty(() => NodeMessageSelectedItem);
+            set => SetProperty(() => NodeMessageSelectedItem, value);
+        }
+
+        /// <summary>
+        /// 下拉框数据
+        /// </summary>
+        public ObservableCollection<ComboBoxDataStructuralBody> ComboBoxData
+        {
+            get => GetProperty(() => ComboBoxData);
+            set => SetProperty(() => ComboBoxData, value);
+        }
+
+        /// <summary>
+        /// 下拉框选中的数据
+        /// </summary>
+        public ComboBoxDataStructuralBody ComboBoxSelectedData
+        {
+            get => GetProperty(() => ComboBoxSelectedData);
+            set => SetProperty(() => ComboBoxSelectedData, value);
+        }
+
+        /// <summary>
+        /// 表格列被选中
+        /// </summary>
+        public ICommand DataGrid_SelectedCellsChanged { get => new RelayCommand<object>(OnDataGrid_SelectedCellsChanged); }
+        public void OnDataGrid_SelectedCellsChanged(object? e)
+        {
+            if (NodeMessageSelectedItem?.Address == null)
+            {
+                return;
+            }
+        }
+
+        /// <summary>
+        /// 请求
+        /// </summary>
+        public ICommand Request { get => new RelayCommand(OnRequest); }
+        public void OnRequest()
+        {
+            communication = OpcDaHttpOperate.Instance(BasicsData);
+            communication.OnEvent -= Communication_OnEvent;
+            communication.OnEvent += Communication_OnEvent;
+            RequestMethod requestMethod = (RequestMethod)Enum.Parse(typeof(RequestMethod), ComboBoxSelectedData.Value);
+            OperateResult operateResult = null;
+            switch (requestMethod)
+            {
+                case RequestMethod.ReqOPCServerNameList:
+                    operateResult = communication.Request(requestMethod);
+                    if (operateResult.State)
+                    {
+                        ReqOPCServerNameList.Response response = operateResult.RData as ReqOPCServerNameList.Response;
+                        if (response.opcnamelist.Count > 0)
+                        {
+                            if (ComboBoxServerName == null)
+                            {
+                                ComboBoxServerName = new ObservableCollection<ComboBoxDataStructuralBody>();
+                            }
+
+                            foreach (var item in response.opcnamelist)
+                            {
+                                if (ComboBoxServerName.FirstOrDefault(c => c.Key == item.opcsvrname) == null)
+                                {
+                                    ComboBoxServerName.Add(new ComboBoxDataStructuralBody { Key = item.opcsvrname, Value = item.opcsvrname });
+                                }
+                            }
+                            if (ComboBoxSelectedServerName == null)
+                            {
+                                ComboBoxSelectedServerName = ComboBoxServerName[ComboBoxServerName.Count - 1];
+                            }
+                        }
+                    }
+                    break;
+
+                case RequestMethod.ReqConnectToOPC:
+                    if (string.IsNullOrEmpty(ServerName))
+                    {
+                        Output($"[ {requestMethod.ToDescription} ] 服务名不能为空");
+                        return;
+                    }
+                    operateResult = communication.Request(requestMethod, new ReqConnectToOPC.Request() { opcname = ServerName });
+                    break;
+
+                case RequestMethod.ReqGetALLItemsOfOPC:
+                    if (string.IsNullOrEmpty(ServerName))
+                    {
+                        Output($"[ {requestMethod.ToDescription} ] 服务名不能为空");
+                        return;
+                    }
+                    operateResult = communication.Request(requestMethod, new ReqGetALLItemsOfOPC.Request() { opcname = ServerName });
+                    if (operateResult.RData != null && operateResult.RData is YSAI.Opc.da.http.structure.ReqGetALLItemsOfOPC.Response)
+                    {
+                        YSAI.Opc.da.http.structure.ReqGetALLItemsOfOPC.Response? response = operateResult.GetRData<YSAI.Opc.da.http.structure.ReqGetALLItemsOfOPC.Response>();
+                        int index = 1;
+                        NodeMessage.Clear();
+                        foreach (var item in response.itemlist)
+                        {
+                            NodeMessage.Add(new NodeMessageStructuralBody { Index = index++, Address = item.itemname });
+                        }
+                        OnToggleButton_Checked(null);
+                    }
+                    break;
+
+                case RequestMethod.ReqReConnectOfOPC:
+                    if (string.IsNullOrEmpty(ServerName))
+                    {
+                        Output($"[ {requestMethod.ToDescription} ] 服务名不能为空");
+                        return;
+                    }
+                    operateResult = communication.Request(requestMethod, new ReqReConnectOfOPC.Request() { opcname = ServerName });
+                    break;
+
+                case RequestMethod.ReqDisConnectOfOPC:
+                    if (string.IsNullOrEmpty(ServerName))
+                    {
+                        Output($"[ {requestMethod.ToDescription} ] 服务名不能为空");
+                        return;
+                    }
+                    operateResult = communication.Request(requestMethod, new ReqDisConnectOfOPC.Request() { opcname = ServerName });
+                    break;
+
+                case RequestMethod.ReqDeleteConnectOfOPC:
+                    if (string.IsNullOrEmpty(ServerName))
+                    {
+                        Output($"[ {requestMethod.ToDescription} ] 服务名不能为空");
+                        return;
+                    }
+                    operateResult = communication.Request(requestMethod, new ReqDeleteConnectOfOPC.Request() { opcname = ServerName });
+                    break;
+
+                case RequestMethod.ReqAddGroupOfOPC:
+                    if (string.IsNullOrEmpty(ServerName) || string.IsNullOrEmpty(GroupName))
+                    {
+                        Output($"[ {requestMethod.ToDescription} ] 服务名组名不能为空");
+                        return;
+                    }
+                    operateResult = communication.Request(requestMethod, new ReqAddGroupOfOPC.Request() { opcname = ServerName, groupname = GroupName });
+                    break;
+
+                case RequestMethod.ReqDelGroupOfOPC:
+                    if (string.IsNullOrEmpty(ServerName) || string.IsNullOrEmpty(GroupName))
+                    {
+                        Output($"[ {requestMethod.ToDescription} ] 服务名、组名不能为空");
+                        return;
+                    }
+                    operateResult = communication.Request(requestMethod, new ReqDelGroupOfOPC.Request() { opcname = ServerName, groupname = GroupName });
+                    break;
+
+                case RequestMethod.ReqGetGroupsOfOPC:
+                    if (string.IsNullOrEmpty(ServerName))
+                    {
+                        Output($"[ {requestMethod.ToDescription} ] 服务名不能为空");
+                        return;
+                    }
+                    operateResult = communication.Request(requestMethod, new ReqGetGroupsOfOPC.Request() { opcname = ServerName });
+                    if (operateResult.State)
+                    {
+                        ReqGetGroupsOfOPC.Response response = operateResult.RData as ReqGetGroupsOfOPC.Response;
+                        if (response.grouplist.Count > 0)
+                        {
+                            GroupName = response.grouplist[0].groupname;
+                        }
+                    }
+                    break;
+
+                case RequestMethod.ReqAddItemsOfGroup:
+                    if (string.IsNullOrEmpty(ServerName) || string.IsNullOrEmpty(GroupName) || string.IsNullOrEmpty(DotAddress))
+                    {
+                        Output($"[ {requestMethod.ToDescription} ] 服务名、组名、节点名不能为空");
+                        return;
+                    }
+                    operateResult = communication.Request(requestMethod, new ReqAddItemsOfGroup.Request() { opcname = ServerName, groupname = GroupName, itemlist = new List<ReqAddItemsOfGroup.Request._itemlist>() { new ReqAddItemsOfGroup.Request._itemlist() { itemname = DotAddress } } });
+                    break;
+
+                case RequestMethod.ReqGetItemsOfGroup:
+                    if (string.IsNullOrEmpty(ServerName) || string.IsNullOrEmpty(GroupName))
+                    {
+                        Output($"[ {requestMethod.ToDescription} ] 服务名、组名不能为空");
+                        return;
+                    }
+                    operateResult = communication.Request(requestMethod, new ReqGetItemsOfGroup.Request() { opcname = ServerName, groupname = GroupName });
+                    break;
+
+                case RequestMethod.ReqDelItemsOfGroup:
+                    if (string.IsNullOrEmpty(ServerName) || string.IsNullOrEmpty(GroupName) || string.IsNullOrEmpty(DotAddress))
+                    {
+                        Output($"[ {requestMethod.ToDescription} ] 服务名、组名、节点名不能为空");
+                        return;
+                    }
+                    operateResult = communication.Request(requestMethod, new ReqDelItemsOfGroup.Request() { opcname = ServerName, groupname = GroupName, itemlist = new List<ReqDelItemsOfGroup.Request._itemlist>() { new ReqDelItemsOfGroup.Request._itemlist() { itemname = DotAddress } } });
+                    break;
+
+                case RequestMethod.ReqReadItemsValueOfGroup:
+                    if (string.IsNullOrEmpty(ServerName) || string.IsNullOrEmpty(GroupName))
+                    {
+                        Output($"[ {requestMethod.ToDescription} ] 服务名、组名不能为空");
+                        return;
+                    }
+                    operateResult = communication.Request(requestMethod, new ReqReadItemsValueOfGroup.Request() { opcname = ServerName, groupname = GroupName });
+                    break;
+
+                case RequestMethod.ReqGetConnectStatusOfOPC:
+                    if (string.IsNullOrEmpty(ServerName))
+                    {
+                        Output($"[ {requestMethod.ToDescription} ] 服务名、不能为空");
+                        return;
+                    }
+                    operateResult = communication.Request(requestMethod, new ReqGetConnectStatusOfOPC.Request() { opcname = ServerName });
+                    break;
+
+                case RequestMethod.ReqWriteItemValue:
+                    if (string.IsNullOrEmpty(ServerName) || string.IsNullOrEmpty(GroupName) || string.IsNullOrEmpty(DotAddress) || string.IsNullOrEmpty(DotAddress))
+                    {
+                        Output($"[ {requestMethod.ToDescription} ] 服务名、组名、节点名、节点值不能为空");
+                        return;
+                    }
+                    operateResult = communication.Request(requestMethod, new ReqWriteItemValue.Request() { opcname = ServerName, groupname = GroupName, itemname = DotAddress, itemvalue = WriteInData });
+                    break;
+            }
+            Output($"{operateResult.Message},用时:{operateResult.RunTime} ms");
+            Output($"{operateResult.RData.ToJson().JsonFormatting()}");
+        }
+
+
+        /// <summary>
+        /// 批量添加节点
+        /// </summary>
+        public ICommand AddNodes { get => new RelayCommand(OnAddNodes); }
+        public void OnAddNodes()
+        {
+            if (YSAI.Core.Wpf.MessageBox.Show("此功能为批量添加节点,请确保服务名与组名存在并且不为空", "提示", YSAI.Core.Wpf.MessageBoxButton.OKCancel, YSAI.Core.Wpf.MessageBoxImage.Information))
+            {
+                if (communication == null)
+                {
+                    Output($"操作对象为空,请检查连接是否正常");
+                    return;
+                }
+                if (string.IsNullOrEmpty(ServerName) || string.IsNullOrEmpty(GroupName))
+                {
+                    Output($"服务名、组名不能为空");
+                    return;
+                }
+                //先请求服务名下的所有节点数据
+                OperateResult operateResult = communication.Request(RequestMethod.ReqGetALLItemsOfOPC, new ReqGetALLItemsOfOPC.Request() { opcname = ServerName });
+                Output(operateResult.Message);
+                if (operateResult.State)
+                {
+                    ReqGetALLItemsOfOPC.Response? response = operateResult.GetRData<ReqGetALLItemsOfOPC.Response>();
+                    if (response.itemlist.Count > 0)
+                    {
+                        //这是批量添加节点的数据
+                        List<ReqAddItemsOfGroup.Request._itemlist> itemlists = new List<ReqAddItemsOfGroup.Request._itemlist>();
+
+                        //检索所有节点
+                        foreach (var item in response.itemlist)
+                        {
+                            itemlists.Add(new ReqAddItemsOfGroup.Request._itemlist() { itemname = item.itemname });
+                        }
+                        if (itemlists.Count > 0)
+                        {
+                            operateResult = communication.Request(RequestMethod.ReqAddItemsOfGroup, new ReqAddItemsOfGroup.Request() { opcname = ServerName, groupname = GroupName, itemlist = itemlists });
+                            Output(operateResult.Message);
+                        }
+                        else
+                        {
+                            Output($"通过条件比对后,未存在条件成立的节点");
+                        }
+                    }
+                    else
+                    {
+                        Output($"此服务下未查询到节点数据");
+                    }
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// 显示节点浏览命令
+        /// </summary>
+        public ICommand ToggleButton_Checked { get => new RelayCommand<RoutedEventArgs>(OnToggleButton_Checked); }
+        private void OnToggleButton_Checked(RoutedEventArgs e)
+        {
+            if (communication != null && communication.GetStatus().State)
+            {
+                NodeBrowseIsChecked = true;
+                BasicsData_ToggleButtonIsChecked = true;
+            }
+            else
+            {
+                Output("请先打开连接");
+                NodeBrowseIsChecked = false;
+            }
+        }
+
+        /// <summary>
+        /// 隐藏节点浏览命令
+        /// </summary>
+        public ICommand ToggleButton_Unchecked { get => new RelayCommand<RoutedEventArgs>(OnToggleButton_Unchecked); }
+        private void OnToggleButton_Unchecked(RoutedEventArgs e)
+        {
+            BasicsData_ToggleButtonIsChecked = false;
+            NodeMessage?.Clear();
+        }
+
+
+        /// <summary>
+        /// 批量订阅
+        /// </summary>
+        public ICommand ContextMenu_Subscribe { get => new RelayCommand<object>(OnContextMenu_Subscribe); }
+        public async void OnContextMenu_Subscribe(object? e)
+        {
+            if (NodeMessage == null)
+            {
+                Output("请先请求【浏览OPC服务下的所有标签列表】");
+                return;
+            }
+
+            Address address = new Address();
+            address.AddressArray = new List<AddressDetails>();
+            foreach (var item in NodeMessage)
+            {
+                address.AddressArray.Add(new AddressDetails()
+                {
+                    AddressType = Model.@enum.AddressType.Reality,
+                    AddressName = item.Address,
+                    AddressExtendParam = new OpcDaHttpData.ExtendParam.Read
+                    {
+                        ReadInterval = 1000,
+                        ServerName = ServerName,
+                        GroupName = GroupName,
+                    }
+                });
+            }
+
+            //订阅
+            OperateResult operateResult = communication.Subscribe(address);
+            Output(operateResult.ToJson().JsonFormatting());
+        }
+
+        /// <summary>
+        /// 取消批量订阅
+        /// </summary>
+        public ICommand ContextMenu_UnSubscribe { get => new RelayCommand<Object>(OnContextMenu_UnSubscribe); }
+        public void OnContextMenu_UnSubscribe(object? e)
+        {
+            if (NodeMessage == null)
+            {
+                Output("请先请求【浏览OPC服务下的所有标签列表】");
+                return;
+            }
+            Address address = new Address();
+            address.AddressArray = new List<AddressDetails>();
+            foreach (var item in NodeMessage)
+            {
+                address.AddressArray.Add(new AddressDetails()
+                {
+                    AddressType = Model.@enum.AddressType.Reality,
+                    AddressName = item.Address,
+                    AddressExtendParam = new OpcDaHttpData.ExtendParam.Read
+                    {
+                        ReadInterval = 1000,
+                        ServerName = ServerName,
+                        GroupName = GroupName,
+                    }
+                });
+            }
+
+            //取消订阅
+            OperateResult operateResult = communication.UnSubscribe(address);
+            Output(operateResult.ToJson().JsonFormatting());
+        }
+
+
+
+
+        private void Communication_OnEvent(object? sender, EventResult e)
+        {
+            if (e.RData != null && e.RData is ConcurrentDictionary<string, AddressValue>)
+            {
+                ConcurrentDictionary<string, AddressValue>? pairs = e.GetRData<ConcurrentDictionary<string, AddressValue>>();
+                if (pairs != null)
+                {
+                    foreach (var itemc in pairs)
+                    {
+                        ShowMessage(itemc);
+                    }
+                }
+            }
+        }
+        private int indexCount = 1;
+        private void ShowMessage(KeyValuePair<string, AddressValue> param)
+        {
+            try
+            {
+                Task.Run(() =>
+                {
+                    System.Windows.Application.Current?.Dispatcher.Invoke(delegate ()
+                    {
+                        if (NodeMessage.Count > 0)
+                        {
+                            int index = -1;
+                            foreach (var item in NodeMessage)
+                            {
+                                if (item.Address.Equals(param.Key))
+                                {
+                                    index = NodeMessage.IndexOf(item);
+                                    break;
+                                }
+                            }
+                            if (index > -1)
+                            {
+                                NodeMessage[index].Value = param.Value.Value?.ToString();
+                            }
+                            else
+                            {
+                                NodeMessage.Add(new NodeMessageStructuralBody() { Address = param.Key, Value = param.Value.Value?.ToString(), Index = indexCount += 1 });
+                            }
+                        }
+                        else
+                        {
+                            NodeMessage.Add(new NodeMessageStructuralBody() { Address = param.Key, Value = param.Value.Value?.ToString(), Index = indexCount = 1 });
+                        }
+                    });
+                });
+            }
+            catch { }
+        }
+    }
+}

+ 1 - 1
src/YSAI.Tool.Wpf/controllers/MainController.cs

@@ -35,7 +35,7 @@ namespace YSAI.Tool.Wpf.controllers
                             CreationControl(string.Format("{0,-8}{1}", "[ 客户端 ]", "UA"), SymbolRegular.DocumentTextToolbox24, typeof(UaClient)),
                             CreationControl(string.Format("{0,-8}{1}", "[ 服务端 ]", "UA"), SymbolRegular.DesktopToolbox24, typeof(UaService)),
                             CreationControl(string.Format("{0,-8}{1}", "[ 客户端 ]", "DA ( x86 run )"), SymbolRegular.ClockToolbox24, typeof(AboutUs)),
-                            CreationControl(string.Format("{0,-8}{1}", "[ HTTP ]", "DA"), SymbolRegular.BookToolbox24, typeof(AboutUs))
+                            CreationControl(string.Format("{0,-8}{1}", "[ HTTP ]", "DA"), SymbolRegular.BookToolbox24, typeof(DaHttp))
                         }
                   },
                   new NavigationViewItem()

+ 6 - 4
src/YSAI.Tool.Wpf/controllers/UaClientController.cs

@@ -1,4 +1,5 @@
 using CommunityToolkit.Mvvm.Input;
+using Newtonsoft.Json;
 using Opc.Ua;
 using System.Collections.Concurrent;
 using System.Collections.ObjectModel;
@@ -11,6 +12,7 @@ using YSAI.Core.Wpf;
 using YSAI.Core.Wpf.mvvm;
 using YSAI.Model.data;
 using YSAI.Opc.ua.client;
+using YSAI.Opc.ua.service.core;
 using YSAI.Tool.Wpf.data;
 using YSAI.Unility;
 using static YSAI.Opc.ua.client.OpcUaClientData;
@@ -725,8 +727,8 @@ namespace YSAI.Tool.Wpf.controllers
             }
         }
 
-        #region 暂不使用
-        /*
+        #region 右键菜单功能
+
         /// <summary>
         /// 导出节点
         /// </summary>
@@ -860,7 +862,7 @@ namespace YSAI.Tool.Wpf.controllers
                 });
             }
             //取消订阅
-            OperateResult operateResult = communication.RemoveSubscribe(address);
+            OperateResult operateResult = communication.UnSubscribe(address);
             Output(operateResult.ToJson().JsonFormatting());
         }
 
@@ -968,7 +970,7 @@ namespace YSAI.Tool.Wpf.controllers
 
                 Output($"节点成功导出至:{Path}");
             }
-        }*/
+        }
         #endregion
     }
 }

+ 18 - 0
src/YSAI.Tool.Wpf/data/ComboBoxDataStructuralBody.cs

@@ -0,0 +1,18 @@
+namespace YSAI.Tool.Wpf.data
+{
+    /// <summary>
+    /// 下拉框设备数据
+    /// </summary>
+    public class ComboBoxDataStructuralBody
+    {
+        /// <summary>
+        /// 键
+        /// </summary>
+        public string? Key { set; get; }
+
+        /// <summary>
+        /// 值
+        /// </summary>
+        public string? Value { set; get; }
+    }
+}

+ 9 - 0
src/YSAI.Tool.Wpf/data/NodeMessageStructuralBody.cs

@@ -9,6 +9,15 @@ namespace YSAI.Tool.Wpf.data
     /// </summary>
     public class NodeMessageStructuralBody : NotifyObject
     {
+        /// <summary>
+        /// 序号
+        /// </summary>
+        public int Index
+        {
+            get => GetProperty(() => Index);
+            set => SetProperty(() => Index, value);
+        }
+
         /// <summary>
         /// 名称
         /// </summary>

+ 470 - 0
src/YSAI.Tool.Wpf/views/DaHttp.xaml

@@ -0,0 +1,470 @@
+<UserControl x:Class="YSAI.Tool.Wpf.views.DaHttp"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:YSAI.Tool.Wpf.views"
+                       xmlns:c="clr-namespace:YSAI.Tool.Wpf.controllers"
+             xmlns:txt="clr-namespace:YSAI.Core.Wpf.controls.textbox;assembly=YSAI.Core.Wpf"
+             xmlns:btn="clr-namespace:YSAI.Core.Wpf.controls.button;assembly=YSAI.Core.Wpf"
+             xmlns:pt="http://propertytools.org/wpf"
+             xmlns:helpers="clr-namespace:YSAI.Core.Wpf.style;assembly=YSAI.Core.Wpf"
+             xmlns:cs="clr-namespace:YSAI.Core.Wpf.converters;assembly=YSAI.Core.Wpf"
+             xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
+             xmlns:mvvm="clr-namespace:YSAI.Core.Wpf.mvvm;assembly=YSAI.Core.Wpf"
+             Background="{DynamicResource ContentBackgroundPicture}">
+    <UserControl.DataContext>
+        <c:DaHttpController />
+    </UserControl.DataContext>
+
+
+
+    <!--资源加载-->
+    <UserControl.Resources>
+        <ResourceDictionary>
+            <Style TargetType="ToolTip">
+                <Setter Property="Foreground" Value="{DynamicResource Font.Content.Foreground}"/>
+                <Setter Property="BorderBrush" Value="{DynamicResource Control.Border.Color}"/>
+            </Style>
+            <cs:CheckConverter x:Key="CheckConverter" />
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="pack://application:,,,/YSAI.Core.Wpf;component/resources/style/Style_ComboBox.xaml" />
+                <ResourceDictionary Source="pack://application:,,,/YSAI.Core.Wpf;component/resources/style/Style_Button.xaml" />
+                <ResourceDictionary Source="pack://application:,,,/YSAI.Core.Wpf;component/resources/style/Style_DataGrid.xaml" />
+                <ResourceDictionary Source="pack://application:,,,/YSAI.Core.Wpf;component/resources/style/Style_Border.xaml" />
+                <ResourceDictionary Source="pack://application:,,,/YSAI.Core.Wpf;component/resources/style/Style_TbaControl.xaml" />
+                <ResourceDictionary Source="pack://application:,,,/YSAI.Core.Wpf;component/resources/style/Style_ScrollViewer.xaml" />
+                <ResourceDictionary Source="pack://application:,,,/YSAI.Core.Wpf;component/resources/style/Style_GroupBox.xaml" />
+                <ResourceDictionary Source="pack://application:,,,/YSAI.Core.Wpf;component/resources/style/Style_TextBox.xaml" />
+                <ResourceDictionary Source="pack://application:,,,/YSAI.Core.Wpf;component/resources/style/Style_CheckBox.xaml" />
+                <ResourceDictionary Source="pack://application:,,,/YSAI.Core.Wpf;component/resources/style/Style_RadioButton.xaml" />
+                <ResourceDictionary Source="pack://application:,,,/YSAI.Core.Wpf;component/resources/style/Style_DatePicker.xaml" />
+                <ResourceDictionary Source="pack://application:,,,/YSAI.Core.Wpf;component/resources/style/Style_ContextMenu.xaml" />
+                <ResourceDictionary Source="pack://application:,,,/YSAI.Core.Wpf;component/resources/style/Style_TreeView.xaml" />
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </UserControl.Resources>
+
+    <Border Background="#F0F2F0" Width="auto" Height="auto" CornerRadius="{DynamicResource WindowCornerRadius}" Margin="50">
+        <GroupBox Style="{StaticResource GroupBoxTab}" Margin="0"  >
+            <GroupBox.Header>
+                <StackPanel Orientation="Horizontal">
+                    <Grid Background="Transparent">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="auto" />
+                            <ColumnDefinition Width="*" />
+                        </Grid.ColumnDefinitions>
+                        <Image Grid.Column="0" Margin="10,0,8,0" Source="{DynamicResource Tool}" Width="14" />
+                        <TextBlock Text="{Binding ToolTitle}" FontSize="13"  Grid.Column="1" Foreground="{DynamicResource Font.Content.Foreground}" />
+                    </Grid>
+                </StackPanel>
+            </GroupBox.Header>
+            <!--基础数据与功能区域-->
+            <Grid >
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="auto"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="auto"/>
+                </Grid.ColumnDefinitions>
+
+                <ToggleButton Grid.Column="0"  Grid.ColumnSpan="3" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,-28,3,0" ToolTip="节点信息浏览[隐藏/显示]" Panel.ZIndex="3" IsChecked="{Binding NodeBrowseIsChecked}">
+                    <i:Interaction.Triggers>
+                        <i:EventTrigger EventName="Unchecked">
+                            <mvvm:EventCommand  Command="{Binding ToggleButton_Unchecked}" />
+                        </i:EventTrigger>
+                        <i:EventTrigger EventName="Checked">
+                            <mvvm:EventCommand  Command="{Binding ToggleButton_Checked}" />
+                        </i:EventTrigger>
+                    </i:Interaction.Triggers>
+                    <ToggleButton.Template>
+                        <ControlTemplate TargetType="{x:Type ToggleButton}">
+                            <Image x:Name="image" Source="{DynamicResource ZommOff}" Stretch="Fill" Width="15" Height="15" />
+                            <ControlTemplate.Triggers>
+                                <Trigger Property="IsChecked" Value="True">
+                                    <Setter Property="Source" TargetName="image" Value="{DynamicResource ZommOn}"/>
+                                </Trigger>
+                            </ControlTemplate.Triggers>
+                        </ControlTemplate>
+                    </ToggleButton.Template>
+                    <ToggleButton.Triggers>
+                        <EventTrigger RoutedEvent="ToggleButton.Unchecked">
+                            <BeginStoryboard>
+                                <Storyboard>
+                                    <DoubleAnimation Storyboard.TargetName="nodeBrowse" Storyboard.TargetProperty="MinWidth" From="1150" To="0" Duration="0:0:0.7"/>
+                                </Storyboard>
+                            </BeginStoryboard>
+                        </EventTrigger>
+                        <EventTrigger RoutedEvent="ToggleButton.Checked">
+                            <BeginStoryboard>
+                                <Storyboard>
+                                    <DoubleAnimation Storyboard.TargetName="nodeBrowse" Storyboard.TargetProperty="MinWidth" From="0" To="1150" Duration="0:0:0.7"/>
+                                </Storyboard>
+                            </BeginStoryboard>
+                        </EventTrigger>
+                    </ToggleButton.Triggers>
+                </ToggleButton>
+
+                <!--基础数据-->
+                <Grid Grid.Column="0"  x:Name="propertyGrid" Margin="0,0,3,0" Width="550">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="auto"/>
+                        <RowDefinition Height="auto"/>
+                    </Grid.RowDefinitions>
+
+                    <!--导入导出-->
+                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,0,-30" Panel.ZIndex="2">
+                        <btn:ButtonControl  
+                                          Width="80"
+                                          Command="{Binding IncBasics}" 
+                                          HorizontalAlignment="Center" 
+                                          VerticalAlignment="Center" 
+                                          IsMouseOverBorderBrushColor="{DynamicResource Control.Border.One.Color}"
+                                          BorderBrush="{DynamicResource Control.Border.Color}"
+                                          IsPressedBorderBrushColor="{DynamicResource Control.Border.Two.Color}"
+                                          Foreground="{DynamicResource Font.Content.Foreground}"
+                                          Icon="{DynamicResource Inc}"
+                                          Content="导入"
+                                          BorderThickness="1,0,0,0" />
+                        <btn:ButtonControl  
+                                          Width="80"
+                                          Command="{Binding ExpBasics}" 
+                                          HorizontalAlignment="Center" 
+                                          VerticalAlignment="Center" 
+                                          IsMouseOverBorderBrushColor="{DynamicResource Control.Border.One.Color}"
+                                          BorderBrush="{DynamicResource Control.Border.Color}"
+                                          IsPressedBorderBrushColor="{DynamicResource Control.Border.Two.Color}"
+                                          Foreground="{DynamicResource Font.Content.Foreground}"
+                                          Icon="{DynamicResource Exp}"
+                                          Content="导出"
+                                          BorderThickness="1,0,0,0" />
+                    </StackPanel>
+                    <!--属性表格-->
+                    <Grid Grid.Row="1" Panel.ZIndex="1">
+                        <Grid.Resources>
+                            <ResourceDictionary>
+                                <Style TargetType="{x:Type Label}" >
+                                    <Setter Property="FontSize" Value="13"/>
+                                    <Setter Property="Foreground" Value="{DynamicResource Font.Content.Foreground}"/>
+                                    <Setter Property="HorizontalAlignment" Value="Left"/>
+                                </Style>
+                                <Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource DefaultComboBox}" >
+                                    <Setter Property="Height" Value="25"/>
+                                    <Setter Property="Background" Value="White"/>
+                                    <Setter Property="VerticalAlignment" Value="Center"/>
+                                    <Setter Property="helpers:Style_ComboBox.CornerRadius" Value="{DynamicResource WindowCornerRadius}"/>
+                                </Style>
+                                <Style TargetType="{x:Type pt:TextBoxEx}" BasedOn="{StaticResource TextBoxStyle2}">
+                                    <Setter Property="FontSize" Value="13"/>
+                                    <Setter Property="Foreground" Value="{DynamicResource Font.Content.Foreground}"/>
+                                    <Setter Property="BorderBrush" Value="{DynamicResource Control.Border.Color}"/>
+                                    <Setter Property="HorizontalContentAlignment" Value="Center"/>
+                                    <Setter Property="VerticalContentAlignment" Value="Center"/>
+                                </Style>
+
+                                <Style TargetType="CheckBox" BasedOn="{StaticResource CheckBoxStyle}"/>
+
+                                <Style TargetType="ContentControl" >
+                                    <Setter Property="Foreground" Value="{DynamicResource Font.Content.Foreground}"/>
+                                </Style>
+
+                                <Style TargetType="Expander">
+                                    <Setter Property="Foreground" Value="{DynamicResource Font.Content.Foreground}"/>
+                                </Style>
+
+                                <Style TargetType="RadioButton" BasedOn="{StaticResource RadioButtonStyle}"/>
+
+
+                                <Style TargetType="GroupBox" BasedOn="{StaticResource GroupBoxTab}"/>
+
+
+                                <DataTemplate x:Key="HeaderTemplate">
+                                    <StackPanel Orientation="Horizontal">
+                                        <Grid>
+                                            <Grid.ColumnDefinitions>
+                                                <ColumnDefinition Width="auto" />
+                                                <ColumnDefinition Width="*" />
+                                            </Grid.ColumnDefinitions>
+                                            <Image Grid.Column="0" Margin="10,0,8,0" Source="{DynamicResource ConfigBase}" Width="14" />
+                                            <TextBlock Text="{Binding}" FontSize="13"  Grid.Column="1" Foreground="{DynamicResource Font.Content.Foreground}" />
+                                        </Grid>
+                                    </StackPanel>
+                                </DataTemplate>
+
+                            </ResourceDictionary>
+                        </Grid.Resources>
+                        <pt:PropertyGrid  Margin="1,-4,1,0"
+SelectedObject="{Binding BasicsData}"
+TabVisibility="VisibleIfMoreThanOne" 
+TextBlock.Foreground="{DynamicResource Font.Content.Foreground}" 
+Foreground="{DynamicResource Font.Content.Foreground}"
+BorderBrush="{DynamicResource Control.Border.Color}"
+CategoryHeaderTemplate="{StaticResource HeaderTemplate}"/>
+                    </Grid>
+                </Grid>
+
+                <!--功能模块与信息-->
+                <Grid Grid.Column="1" >
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+
+                    <ToggleButton Grid.Row="0" Grid.RowSpan="2" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="8,8,0,0" ToolTip="基础数据[隐藏/显示]" Panel.ZIndex="3" IsChecked="{Binding BasicsData_ToggleButtonIsChecked}">
+                        <ToggleButton.Template>
+                            <ControlTemplate TargetType="{x:Type ToggleButton}">
+                                <Image x:Name="image" Source="{DynamicResource GoLeft_Two}" Stretch="Fill" Width="15" Height="15" />
+                                <ControlTemplate.Triggers>
+                                    <Trigger Property="IsChecked" Value="True">
+                                        <Setter Property="Source" TargetName="image" Value="{DynamicResource GoRight_Two}"/>
+                                    </Trigger>
+                                </ControlTemplate.Triggers>
+                            </ControlTemplate>
+                        </ToggleButton.Template>
+                        <ToggleButton.Triggers>
+                            <EventTrigger RoutedEvent="ToggleButton.Unchecked">
+                                <BeginStoryboard>
+                                    <Storyboard>
+                                        <DoubleAnimation Storyboard.TargetName="propertyGrid" Storyboard.TargetProperty="Width" From="0" To="550" Duration="0:0:0.7"/>
+                                        <ThicknessAnimationUsingKeyFrames Storyboard.TargetName="propertyGrid" Storyboard.TargetProperty="Margin" BeginTime="00:00:00">
+                                            <SplineThicknessKeyFrame KeyTime="00:00:00" Value="0" />
+                                            <SplineThicknessKeyFrame KeyTime="00:00:0.7" Value="0,0,3,0" />
+                                        </ThicknessAnimationUsingKeyFrames>
+                                    </Storyboard>
+                                </BeginStoryboard>
+                            </EventTrigger>
+                            <EventTrigger RoutedEvent="ToggleButton.Checked">
+                                <BeginStoryboard>
+                                    <Storyboard>
+                                        <DoubleAnimation Storyboard.TargetName="propertyGrid" Storyboard.TargetProperty="Width" From="550" To="0" Duration="0:0:0.7"/>
+                                        <ThicknessAnimationUsingKeyFrames Storyboard.TargetName="propertyGrid" Storyboard.TargetProperty="Margin" BeginTime="00:00:00">
+                                            <SplineThicknessKeyFrame KeyTime="00:00:00" Value="0,0,3,0" />
+                                            <SplineThicknessKeyFrame KeyTime="00:00:0.7" Value="0" />
+                                        </ThicknessAnimationUsingKeyFrames>
+                                    </Storyboard>
+                                </BeginStoryboard>
+                            </EventTrigger>
+                        </ToggleButton.Triggers>
+                    </ToggleButton>
+
+                    <!--功能模块-->
+                    <GroupBox Style="{StaticResource GroupBoxTab}" Margin="0" Grid.Row="0">
+                        <GroupBox.Header>
+                            <StackPanel Orientation="Horizontal">
+                                <Grid Background="transparent">
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition Width="auto" />
+                                        <ColumnDefinition Width="*" />
+                                    </Grid.ColumnDefinitions>
+                                    <Image Grid.Column="0" Margin="10,0,8,0" Source="{DynamicResource Function}" Width="14" />
+                                    <TextBlock Text="功能" FontSize="13" Margin="0,0,10,0"  Grid.Column="1" Foreground="{DynamicResource Font.Content.Foreground}" />
+                                </Grid>
+                            </StackPanel>
+                        </GroupBox.Header>
+                        <!--■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■-->
+
+                        <Grid>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="auto"/>
+                                <RowDefinition Height="*"/>
+                            </Grid.RowDefinitions>
+                            <!--Start / Stop-->
+                            <Grid Grid.Row="0" Grid.Column="1"  Margin="0,-40,0,0">
+                                <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" >
+                                    <btn:ButtonControl  Grid.Column="5"  Width="80"   Command="{Binding Request}" HorizontalAlignment="Right" VerticalAlignment="Center" BorderThickness="1,0,0,0"
+                                 IsMouseOverBorderBrushColor="{DynamicResource Control.Border.One.Color}"
+                                 BorderBrush="{DynamicResource Control.Border.Color}"
+                                 IsPressedBorderBrushColor="{DynamicResource Control.Border.Two.Color}"
+                                 Foreground="{DynamicResource Font.Content.Foreground}"
+                                 Icon="{DynamicResource Request}"
+                                 Content="请求"/>
+
+                                    <btn:ButtonControl  Grid.Column="6"  Width="80"  Command="{Binding AddNodes}" HorizontalAlignment="Right" VerticalAlignment="Center" BorderThickness="1,0,0,0"
+                                 IsMouseOverBorderBrushColor="{DynamicResource Control.Border.One.Color}"
+                                 BorderBrush="{DynamicResource Control.Border.Color}"
+                                 IsPressedBorderBrushColor="{DynamicResource Control.Border.Two.Color}"
+                                 Foreground="{DynamicResource Font.Content.Foreground}"
+                                 Icon="{DynamicResource Add}"
+                                 Content="添加"/>
+                                </StackPanel>
+                            </Grid>
+                            <!--发送区域-->
+                            <Grid Grid.Row="1" Grid.Column="1">
+                                <Grid Margin="7">
+                                    <Grid.RowDefinitions>
+                                        <RowDefinition/>
+                                        <RowDefinition/>
+                                        <RowDefinition/>
+                                    </Grid.RowDefinitions>
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition/>
+                                        <ColumnDefinition/>
+                                    </Grid.ColumnDefinitions>
+
+                                    <ComboBox Grid.ColumnSpan="2" Style="{StaticResource ComboBoxStyle}" Background="{DynamicResource WindowContentBackground}" VerticalAlignment="Center" Margin="0,0,0,10"
+                                                           ItemsSource="{Binding ComboBoxData}"
+                                                           DisplayMemberPath="Key"
+                                                           SelectedItem="{Binding ComboBoxSelectedData}"
+                                                           SelectedValue="Value"
+                                                           helpers:Style_ComboBox.CornerRadius="{DynamicResource WindowCornerRadius}" />
+
+                                    <ComboBox Grid.Column="0" Grid.Row="1" Style="{StaticResource ComboBoxStyle}" Background="{DynamicResource WindowContentBackground}" VerticalAlignment="Center"
+                                               ItemsSource="{Binding ComboBoxServerName}"
+                                               DisplayMemberPath="Key"
+                                               SelectedItem="{Binding ComboBoxSelectedServerName}"
+                                               SelectedValue="Value"
+                                               helpers:Style_ComboBox.CornerRadius="{DynamicResource WindowCornerRadius}" />        
+
+                                    <txt:TextBoxControl Grid.Column="0" Grid.Row="2" Margin="0,10,0,0" FontSize="13" HorizontalContentAlignment="Center" VerticalAlignment="Center" Text="{Binding DotAddress}"
+                                                        SelectedColor="{DynamicResource Control.Border.Two.Color}"
+                                                        BorderBrush="{DynamicResource Control.Border.Color}"
+                                                        Foreground="{DynamicResource Font.Content.Foreground}"
+                                                        Icon="{DynamicResource Address}"
+                                                        HintColor="{DynamicResource Control.Border.One.Color}"
+                                                        ShowIcon="True"
+                                                        Hint="地址名"
+                                                        CornerRadius="{DynamicResource WindowCornerRadius}" />
+
+
+                                    <txt:TextBoxControl Grid.Column="1" Grid.Row="1" FontSize="13" Margin="10,0,0,0" HorizontalContentAlignment="Center" Text="{Binding GroupName}"
+                                       SelectedColor="{DynamicResource Control.Border.Two.Color}"
+                                       BorderBrush="{DynamicResource Control.Border.Color}"
+                                       Foreground="{DynamicResource Font.Content.Foreground}"
+                                       Icon="{DynamicResource GroupName}"
+                                       HintColor="{DynamicResource Control.Border.One.Color}"
+                                       ShowIcon="True"
+                                       Hint="组名"
+                                       CornerRadius="{DynamicResource WindowCornerRadius}" />
+
+                                    <txt:TextBoxControl Grid.Column="1" Grid.Row="2" Margin="10,10,0,0" FontSize="13" HorizontalContentAlignment="Center" VerticalAlignment="Center" Text="{Binding WriteInData}"
+                                      SelectedColor="{DynamicResource Control.Border.Two.Color}"
+                                      BorderBrush="{DynamicResource Control.Border.Color}"
+                                      Foreground="{DynamicResource Font.Content.Foreground}"
+                                      Icon="{DynamicResource WriteIn}"
+                                      HintColor="{DynamicResource Control.Border.One.Color}"
+                                      ShowIcon="True"
+                                      Hint="写入数据"
+                                      CornerRadius="{DynamicResource WindowCornerRadius}" />
+                                </Grid>
+                            </Grid>
+                        </Grid>
+
+                        <!--■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■-->
+                    </GroupBox>
+                    <!--信息-->
+                    <GroupBox Style="{StaticResource GroupBoxTab}" Margin="0,5,0,0"  Grid.Row="1">
+                        <GroupBox.Header>
+                            <StackPanel Orientation="Horizontal">
+                                <Grid>
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition Width="auto"/>
+                                        <ColumnDefinition Width="*"/>
+                                    </Grid.ColumnDefinitions>
+                                    <Image Grid.Column="0" Margin="10,0,8,0" Source="{DynamicResource Info}" Width="14" />
+                                    <TextBlock Text="信息" FontSize="13" Margin="0,0,10,0"  Grid.Column="1" Foreground="{DynamicResource Font.Content.Foreground}" VerticalAlignment="Center"/>
+                                </Grid>
+                            </StackPanel>
+                        </GroupBox.Header>
+                        <Grid>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="auto" />
+                                <RowDefinition />
+                            </Grid.RowDefinitions>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition />
+                                <ColumnDefinition Width="auto" />
+                            </Grid.ColumnDefinitions>
+                            <StackPanel Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Right" Margin="0,-40,0,0">
+                                <RadioButton ToolTip="显示方式" Margin="0,0,15,0"  Visibility="{Binding AsciiVisibility}" IsChecked="{Binding InfoFormat,Mode=TwoWay,Converter={StaticResource CheckConverter},ConverterParameter=0}" Style="{StaticResource RadioButtonStyle}" Content="ASCII" VerticalAlignment="Center" HorizontalAlignment="Right" />
+                                <RadioButton ToolTip="显示方式" Margin="0,0,15,0" Visibility="{Binding HexVisibility}" IsChecked="{Binding InfoFormat,Mode=TwoWay,Converter={StaticResource CheckConverter},ConverterParameter=1}"  Style="{StaticResource RadioButtonStyle}" Content="Hex"  VerticalAlignment="Center" HorizontalAlignment="Right" />
+                                <btn:ButtonControl  Width="80"  Command="{Binding Clear}" HorizontalAlignment="Right" VerticalAlignment="Center"  BorderThickness="1,0,0,0"
+IsMouseOverBorderBrushColor="{DynamicResource Control.Border.One.Color}"
+BorderBrush="{DynamicResource Control.Border.Color}"
+IsPressedBorderBrushColor="{DynamicResource Control.Border.Two.Color}"
+Foreground="{DynamicResource Font.Content.Foreground}"
+Icon="{DynamicResource Clear}"
+Content="清空"/>
+                            </StackPanel>
+                            <TextBox Grid.Row="1" Padding="5" Grid.ColumnSpan="2" Style="{DynamicResource TextBoxStyle2}"  FontSize="13" VerticalContentAlignment="Top" HorizontalContentAlignment="Left" TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Visible"  Text="{Binding Info}" >
+                                <i:Interaction.Triggers>
+                                    <i:EventTrigger EventName="TextChanged">
+                                        <mvvm:EventCommand  Command="{Binding Info_TextChanged}" />
+                                    </i:EventTrigger>
+                                </i:Interaction.Triggers>
+                            </TextBox>
+                        </Grid>
+                    </GroupBox>
+                </Grid>
+
+
+
+
+                <!--■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■-->
+
+                <!--节点浏览-->
+                <Grid Grid.Column="2"  Width="0" x:Name="nodeBrowse">
+                    <GroupBox Style="{StaticResource GroupBoxTab}"  Margin="4,0,0,0">
+                        <GroupBox.Header>
+                            <StackPanel Orientation="Horizontal">
+                                <Grid Background="transparent">
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition Width="auto" />
+                                        <ColumnDefinition Width="*" />
+                                    </Grid.ColumnDefinitions>
+                                    <Image Grid.Column="0" Margin="10,0,8,0" Source="{DynamicResource Menu}" Width="14" />
+                                    <TextBlock Text="节点信息浏览" FontSize="13"  Grid.Column="1" Foreground="{DynamicResource Font.Content.Foreground}" />
+                                </Grid>
+                            </StackPanel>
+                        </GroupBox.Header>
+                        <DataGrid ColumnHeaderHeight="20" RowHeight="15" ItemsSource="{Binding NodeMessage}" Grid.Row="0" FontSize="13" SelectedItem="{Binding NodeMessageSelectedItem}" BorderThickness="0">
+                            <!--右键菜单-->
+                            <DataGrid.ContextMenu>
+                                <ContextMenu VerticalContentAlignment="Center" FontSize="13">
+                                    <MenuItem Header="订阅所有地址" Foreground="{DynamicResource Font.Content.Foreground}">
+                                        <i:Interaction.Triggers>
+                                            <i:EventTrigger EventName="Click">
+                                                <mvvm:EventCommand  Command="{Binding ContextMenu_Subscribe}" />
+                                            </i:EventTrigger>
+                                        </i:Interaction.Triggers>
+                                        <MenuItem.Icon>
+                                            <StackPanel Orientation="Horizontal">
+                                                <Image Source="{DynamicResource Subscribe}"  Width="15" Height="15" VerticalAlignment="Center" />
+                                            </StackPanel>
+                                        </MenuItem.Icon>
+                                    </MenuItem>
+                                    <MenuItem Header="取消订阅所有地址" Foreground="{DynamicResource Font.Content.Foreground}">
+                                        <i:Interaction.Triggers>
+                                            <i:EventTrigger EventName="Click">
+                                                <mvvm:EventCommand  Command="{Binding ContextMenu_UnSubscribe}" />
+                                            </i:EventTrigger>
+                                        </i:Interaction.Triggers>
+                                        <MenuItem.Icon>
+                                            <StackPanel Orientation="Horizontal">
+                                                <Image Source="{DynamicResource UnSubscribe}"  Width="15" Height="15" VerticalAlignment="Center" />
+                                            </StackPanel>
+                                        </MenuItem.Icon>
+                                    </MenuItem>
+                                </ContextMenu>
+                            </DataGrid.ContextMenu>
+                            <!--双击-->
+                            <i:Interaction.Triggers>
+                                <i:EventTrigger EventName="SelectedCellsChanged">
+                                    <mvvm:EventCommand  Command="{Binding DataGrid_SelectedCellsChanged}" />
+                                </i:EventTrigger>
+                            </i:Interaction.Triggers>
+                            <DataGrid.Columns>
+                                <DataGridTextColumn Header="序号"  Width="50"  Binding="{Binding Index}" />
+                                <DataGridTextColumn Header="地址"  Width="*"  Binding="{Binding Address}" />
+                                <DataGridTextColumn Header="值"  Width="150"  Binding="{Binding Value}" />
+                            </DataGrid.Columns>
+                        </DataGrid>
+                    </GroupBox>
+                </Grid>
+
+
+                <!--■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■-->
+
+
+            </Grid>
+        </GroupBox>
+    </Border>
+</UserControl>

+ 28 - 0
src/YSAI.Tool.Wpf/views/DaHttp.xaml.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace YSAI.Tool.Wpf.views
+{
+    /// <summary>
+    /// DaHttp.xaml 的交互逻辑
+    /// </summary>
+    public partial class DaHttp : UserControl
+    {
+        public DaHttp()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 8 - 8
src/YSAI.Tool.Wpf/views/UaClient.xaml

@@ -479,10 +479,10 @@ Content="清空"/>
                                         </StackPanel>
                                     </GroupBox.Header>
                                     <TreeView  Background="Transparent" Margin="5,5,0,5"  BorderBrush="{DynamicResource Control.Border.Color}" ItemsSource="{Binding Node}" Style="{StaticResource TreeViewStyle}" >
-                                        <!--右键菜单暂不使用-->
-                                        <!--<TreeView.ContextMenu>
+                                        <!--右键菜单-->
+                                        <TreeView.ContextMenu>
                                             <ContextMenu VerticalContentAlignment="Center" FontSize="13">
-                                                <MenuItem Header="批量订阅" Foreground="{DynamicResource Font.Content.Foreground}">
+                                                <MenuItem Header="订阅" Foreground="{DynamicResource Font.Content.Foreground}">
                                                     <i:Interaction.Triggers>
                                                         <i:EventTrigger EventName="Click">
                                                             <mvvm:EventCommand  Command="{Binding ContextMenu_Subscribe}" />
@@ -494,7 +494,7 @@ Content="清空"/>
                                                         </StackPanel>
                                                     </MenuItem.Icon>
                                                 </MenuItem>
-                                                <MenuItem Header="取消批量订阅" Foreground="{DynamicResource Font.Content.Foreground}">
+                                                <MenuItem Header="取消订阅" Foreground="{DynamicResource Font.Content.Foreground}">
                                                     <i:Interaction.Triggers>
                                                         <i:EventTrigger EventName="Click">
                                                             <mvvm:EventCommand  Command="{Binding ContextMenu_UnSubscribe}" />
@@ -519,16 +519,16 @@ Content="清空"/>
                                                     </MenuItem.Icon>
                                                 </MenuItem>
                                             </ContextMenu>
-                                        </TreeView.ContextMenu>-->
+                                        </TreeView.ContextMenu>
                                         <i:Interaction.Triggers>
                                             <!--选中-->
                                             <i:EventTrigger EventName="SelectedItemChanged">
                                                 <mvvm:EventCommand  Command="{Binding TreeView_SelectedItemChanged}" />
                                             </i:EventTrigger>
-                                            <!--右键按下暂不使用-->
-                                            <!--<i:EventTrigger EventName="PreviewMouseRightButtonDown">
+                                            <!--右键按下-->
+                                            <i:EventTrigger EventName="PreviewMouseRightButtonDown">
                                                 <mvvm:EventCommand  Command="{Binding TreeView_PreviewMouseRightButtonDown}" />
-                                            </i:EventTrigger>-->
+                                            </i:EventTrigger>
                                             <!--项被展开-->
                                             <mvvm:RoutedEventTrigger RoutedEvent="TreeViewItem.Expanded">
                                                 <mvvm:EventCommand  Command="{Binding TreeViewItem_Expanded}" />