ソースを参照

新增信号预览

luo 1 日 前
コミット
49377dd035
82 ファイル変更41044 行追加60156 行削除
  1. 1 0
      Avalonia/IconResource/IconResource.axaml
  2. 1 0
      Avalonia/IconResourceSourceGenerator/IconResourceSourceGenerator.cs
  3. 57 0
      Client/Dynamicloadsimulationdevice/App.axaml
  4. 11 0
      Client/Dynamicloadsimulationdevice/App.axaml.cs
  5. 6 6
      Client/Dynamicloadsimulationdevice/Dynamicloadsimulationdevice.csproj
  6. 4 1
      Client/Dynamicloadsimulationdevice/MainWindow.axaml
  7. 16 0
      Client/Dynamicloadsimulationdevice/Resources/ResourceDictionary.axaml
  8. 37 56
      Client/Dynamicloadsimulationdevice/ViewModels/MainWindowViewModel.cs
  9. 119 0
      Client/Dynamicloadsimulationdevice/ViewModels/PlotConfig/PlotConfigViewModel.cs
  10. 48 0
      Client/Dynamicloadsimulationdevice/ViewModels/ServoConfig/PIViewModel.cs
  11. 172 0
      Client/Dynamicloadsimulationdevice/ViewModels/ServoConfig/ServoConfigViewModel.cs
  12. 88 0
      Client/Dynamicloadsimulationdevice/ViewModels/ShakerChannel/AIConfigViewModel.cs
  13. 93 0
      Client/Dynamicloadsimulationdevice/ViewModels/ShakerChannel/AOConfigViewModel.cs
  14. 53 0
      Client/Dynamicloadsimulationdevice/ViewModels/ShakerChannel/ResultChannelViewModel.cs
  15. 323 0
      Client/Dynamicloadsimulationdevice/ViewModels/ShakerChannel/ShakerChannelViewModel.cs
  16. 173 0
      Client/Dynamicloadsimulationdevice/ViewModels/ShakerConfig/ShakerConfigViewModel.cs
  17. 96 0
      Client/Dynamicloadsimulationdevice/ViewModels/ShakerData/ShakerDataViewModel.cs
  18. 41 0
      Client/Dynamicloadsimulationdevice/ViewModels/ShakerData/StatisticsViewModel.cs
  19. 227 0
      Client/Dynamicloadsimulationdevice/ViewModels/SignalPreview/AnalogSignalPreviewViewModel.cs
  20. 16 0
      Client/Dynamicloadsimulationdevice/ViewModels/SignalPreview/IDataPreview.cs
  21. 145 0
      Client/Dynamicloadsimulationdevice/ViewModels/SignalPreview/SignalPreviewViewModel.cs
  22. 21 0
      Client/Dynamicloadsimulationdevice/ViewModels/SignalPreview/TimeDomainMenuViewModel.cs
  23. 110 0
      Client/Dynamicloadsimulationdevice/Views/PlotConfig/PlotConfigView.axaml
  24. 13 0
      Client/Dynamicloadsimulationdevice/Views/PlotConfig/PlotConfigView.axaml.cs
  25. 210 0
      Client/Dynamicloadsimulationdevice/Views/ServoConfig/ServoConfigView.axaml
  26. 13 0
      Client/Dynamicloadsimulationdevice/Views/ServoConfig/ServoConfigView.axaml.cs
  27. 655 0
      Client/Dynamicloadsimulationdevice/Views/ShakerChannel/ShakerChannelView.axaml
  28. 13 0
      Client/Dynamicloadsimulationdevice/Views/ShakerChannel/ShakerChannelView.axaml.cs
  29. 202 0
      Client/Dynamicloadsimulationdevice/Views/SignalPreview/AnalogSignalPreviewView.axaml
  30. 13 0
      Client/Dynamicloadsimulationdevice/Views/SignalPreview/AnalogSignalPreviewView.axaml.cs
  31. 192 0
      Client/Dynamicloadsimulationdevice/Views/SignalPreview/SignalPreviewView.axaml
  32. 13 0
      Client/Dynamicloadsimulationdevice/Views/SignalPreview/SignalPreviewView.axaml.cs
  33. 108 0
      Client/IViewModel/Convert/EnumToDescription.cs
  34. 2 1
      Client/IViewModel/IViewModel.csproj
  35. 23 0
      Client/IViewModel/Models/StatisticsModel.cs
  36. 18 0
      Client/IViewModel/Tools/EnumHelper.cs
  37. 3 3
      Client/IViewModel/Tools/ResourceBinding.cs
  38. 2 1
      Client/IViewModel/ViewModels/DisplayViewModelBase.cs
  39. 11 3
      Client/IViewModel/ViewModels/DisplayViewModelBase{TModel}.cs
  40. 26 0
      Client/IViewModel/ViewModels/File/ExitViewModel.cs
  41. 78 0
      Client/IViewModel/ViewModels/File/FileLoadViewModel.cs
  42. 78 0
      Client/IViewModel/ViewModels/File/FileSaveViewModel.cs
  43. 1 0
      Client/IViewModel/ViewModels/IDisplayViewModel.cs
  44. 3 2
      Client/IViewModel/ViewModels/IndexValueItemViewModel.cs
  45. 1 1
      Client/IViewModel/ViewModels/Log/LogViewModel.cs
  46. 10 0
      Client/IViewModel/ViewModels/MenuViewModel.cs
  47. 4 4
      Client/IViewModel/ViewModels/ViewModelBase.cs
  48. 2 1
      Client/IViewModel/ViewModels/ViewModelBase{TModel}.cs
  49. 56 0
      Client/IViewModel/Views/File/LoadConfigView.axaml
  50. 13 0
      Client/IViewModel/Views/File/LoadConfigView.axaml.cs
  51. 58 0
      Client/IViewModel/Views/File/SaveConfigView.axaml
  52. 13 0
      Client/IViewModel/Views/File/SaveConfigView.axaml.cs
  53. 42 0
      Client/IViewModel/Views/SplashScreen.axaml
  54. 13 0
      Client/IViewModel/Views/SplashScreen.axaml.cs
  55. 1 0
      Client/Language/LanguageSourceGenerator/LanguageSourceGenerator.cs
  56. 39 3
      Client/Language/Zh-CN/Language.axaml
  57. 13 0
      Client/OilSourceControl/OilSourceControl/View/OilControlView.axaml
  58. 1 0
      Client/OilSourceControl/OilSourceControl/ViewModel/OilSourceStatusViewModel.cs
  59. 2 7
      Dynamicloadsimulationdevice.sln
  60. BIN
      Fpga/FPGA/Fpga_Main.vi
  61. 1 1
      Fpga/fpga.aliases
  62. 8 7
      Fpga/fpga.lvproj
  63. 4 4
      NIFPGA/Fifo.cs
  64. 1 0
      NIFPGA/NIFPGA.Interop.cs
  65. 62 0
      NIFPGA/ReadFifo.cs
  66. 1 0
      Service/ShakerFpga/ShakerFpga.csproj
  67. 1 1
      Service/ShakerService/Service.ReadFifo.cs
  68. 36778 60038
      Service/ShakerService/Shaker.lvbitx
  69. 1 1
      Service/ShakerService/ViewModel/ShakerConfigViewModel.cs
  70. 5 0
      Shaker.Model/AIChannelType.cs
  71. 7 0
      Shaker.Model/AOChannelType.cs
  72. 21 4
      Shaker.Model/Models/AIConfigModel.cs
  73. 39 1
      Shaker.Model/Models/AOConfigModel.cs
  74. 22 0
      Shaker.Model/Models/PIModel.cs
  75. 29 0
      Shaker.Model/Models/ResultChannelModel.cs
  76. 57 0
      Shaker.Model/Models/ServoConfigModel.cs
  77. 1 1
      Shaker.Model/Models/ShakerChannelConfigModel.cs
  78. 49 1
      Shaker.Model/Models/ShakerConfigModel.cs
  79. 106 0
      Shaker.Model/ResultChannel.cs
  80. 36 8
      Shaker.Model/ResultChannelType.cs
  81. 11 0
      Shaker.Model/Tools/Tools.cs
  82. 1 0
      Shaker.Model/Topic.cs

ファイルの差分が大きいため隠しています
+ 1 - 0
Avalonia/IconResource/IconResource.axaml


+ 1 - 0
Avalonia/IconResourceSourceGenerator/IconResourceSourceGenerator.cs

@@ -30,6 +30,7 @@ namespace IconResourceSourceGenerator
                     stringBuilder.AppendLine("{");
                     stringBuilder.AppendLine("    public sealed class IconResourceValueViewModel :ViewModelBase");
                     stringBuilder.AppendLine("    {");
+                    stringBuilder.AppendLine($"       public StreamGeometry this[string key] => (StreamGeometry)Avalonia.Application.Current?.FindResource(key);");
                     stringBuilder.AppendLine("        private IconResourceValueViewModel()");
                     stringBuilder.AppendLine("        {");
                     stringBuilder.AppendLine("        }");

+ 57 - 0
Client/Dynamicloadsimulationdevice/App.axaml

@@ -2,6 +2,7 @@
     x:Class="Dynamicloadsimulationdevice.App"
     xmlns="https://github.com/avaloniaui"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:oxy="http://oxyplot.org/avalonia"
     xmlns:suki="https://github.com/kikipoulet/SukiUI"
     RequestedThemeVariant="Default">
     <!--  "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options.  -->
@@ -10,10 +11,66 @@
         <ResourceDictionary>
             <ResourceDictionary.MergedDictionaries>
                 <ResourceInclude Source="avares://IconResource/IconResource.axaml" />
+                <ResourceInclude Source="avares://Dynamicloadsimulationdevice/Resources/ResourceDictionary.axaml" />
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
+        <ControlTemplate x:Key="DefaultTrackerTemplate">
+            <oxy:TrackerControl
+                Background="#6F999999"
+                BorderBrush="Transparent"
+                CornerRadius="6"
+                HorizontalLineVisibility="False"
+                LineExtents="{Binding PlotModel.PlotArea}"
+                LineStroke="Gray"
+                Position="{Binding Position}"
+                ShowPointer="True"
+                VerticalLineVisibility="True">
+                <oxy:TrackerControl.Content>
+                    <StackPanel Margin="10">
+                        <TextBlock Text="{DynamicResource Value}" />
+                        <TextBlock>
+                            <Run Text="{Binding XAxis.Title}" />
+                            <Run Text=":" />
+                            <Run Text="{Binding TrackerDatas[0].DataPoint.X, StringFormat='{}{0:F4}'}" />
+                            <Run Text="{Binding XAxis.Unit}" />
+                        </TextBlock>
+                        <ItemsControl ItemsSource="{Binding TrackerDatas}">
+                            <ItemsControl.ItemTemplate>
+                                <DataTemplate>
+                                    <TextBlock IsVisible="{Binding Series.IsVisible}">
+                                        <Run Text="{Binding Series.Title}" />
+                                        <Run Text=":" />
+                                        <Run Text="{Binding DataPoint.Y, StringFormat='{}{0:F4}'}" />
+                                        <Run Text="{Binding Series.Tag}" />
+                                    </TextBlock>
+                                </DataTemplate>
+                            </ItemsControl.ItemTemplate>
+                        </ItemsControl>
+                    </StackPanel>
+                </oxy:TrackerControl.Content>
+            </oxy:TrackerControl>
+        </ControlTemplate>
+
     </Application.Resources>
     <Application.Styles>
+        <Style Selector=":is(Button):pointerover">
+            <Setter Property="Cursor" Value="Hand" />
+        </Style>
+
+        <Style Selector="ComboBox:pointerover">
+            <Setter Property="Cursor" Value="Hand" />
+        </Style>
+        <Style Selector="MenuItem">
+            <Setter Property="Foreground" Value="Black" />
+        </Style>
+        <Style Selector="MenuItem:disabled">
+            <Setter Property="Foreground" Value="Gray" />
+        </Style>
+
+        <Style Selector="ToggleSwitch:disabled">
+            <Setter Property="Opacity" Value="0.8" />
+            <Setter Property="OpacityMask" Value="Gray" />
+        </Style>
         <StyleInclude Source="avares://OxyPlot.Avalonia/Themes/Default.axaml" />
         <StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml" />
         <suki:SukiTheme ThemeColor="Blue" />

+ 11 - 0
Client/Dynamicloadsimulationdevice/App.axaml.cs

@@ -3,6 +3,7 @@ using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Markup.Xaml;
 #if DEBUG
 using HotAvalonia;
+using System.Linq;
 using System.Runtime.InteropServices;
 
 #endif
@@ -23,10 +24,20 @@ namespace Dynamicloadsimulationdevice
 
         public override void OnFrameworkInitializationCompleted()
         {
+            IViewModel.SplashScreen splashScreen = new IViewModel.SplashScreen();
+            splashScreen.Show();
             ViewModels.MainWindowViewModel.Instance.Init();
             if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
             {
+                if (desktop.Args!.Contains("-debug"))
+                {
+                    ViewModels.MainWindowViewModel.Instance.Debug = true;
+                }
+#if DEBUG
+                ViewModels.MainWindowViewModel.Instance.Debug = true;
+#endif
                 desktop.MainWindow = new MainWindow();
+                desktop.MainWindow.Loaded += (_, _) => splashScreen.Close();
             }
 
             base.OnFrameworkInitializationCompleted();

+ 6 - 6
Client/Dynamicloadsimulationdevice/Dynamicloadsimulationdevice.csproj

@@ -5,7 +5,10 @@
     <Nullable>enable</Nullable>
     <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
     <ApplicationManifest>app.manifest</ApplicationManifest>
-    <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
+    <AvaloniaUseCompiledBindingsByDefault>false</AvaloniaUseCompiledBindingsByDefault>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'">
+    <DefineConstants>$(DefineConstants);ENABLE_XAML_HOT_RELOAD;NO_DEVICE</DefineConstants>
   </PropertyGroup>
 
   <ItemGroup>
@@ -18,16 +21,13 @@
     <PackageReference Include="Avalonia.Themes.Simple" Version="11.2.5" />
     <PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.5" />
     <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
-    <PackageReference Include="Avalonia.Diagnostics" Version="11.2.5">
-      <IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
-      <PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
-    </PackageReference>
+    <PackageReference Condition="'$(Configuration)'=='Debug'" Include="Avalonia.Diagnostics" Version="11.2.5" />
     <PackageReference Include="System.Xml.XDocument" Version="4.3.0" />
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\..\Avalonia\IconResourceSourceGenerator\IconResourceSourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
     <ProjectReference Include="..\..\Avalonia\IconResource\IconResource.csproj" />
+    <ProjectReference Include="..\..\Calc\SIMDFxpConvert\SIMDFxpConvert.csproj" />
     <ProjectReference Include="..\IViewModel\IViewModel.csproj" />
     <ProjectReference Include="..\OilSourceControl\IOilSourceControl\IOilSourceControl.csproj" />
     <ProjectReference Include="..\OxyPlot\OxyPlot.Avalonia\OxyPlot.Avalonia.csproj" />

+ 4 - 1
Client/Dynamicloadsimulationdevice/MainWindow.axaml

@@ -26,5 +26,8 @@
         <suki:SukiToastHost Manager="{Binding Source={x:Static mainvm:MainWindowViewModel.Instance}, Path=ToastManager}" />
         <suki:SukiDialogHost Manager="{Binding Source={x:Static mainvm:MainWindowViewModel.Instance}, Path=DialogManager}" />
     </suki:SukiWindow.Hosts>
-    Welcome to Avalonia!
+    <Grid>
+
+        <TextBlock Text="4325" />
+    </Grid>
 </suki:SukiWindow>

+ 16 - 0
Client/Dynamicloadsimulationdevice/Resources/ResourceDictionary.axaml

@@ -0,0 +1,16 @@
+<ResourceDictionary
+    xmlns="https://github.com/avaloniaui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:s="using:System">
+    <!--  Add Resources Here  -->
+    <s:Double x:Key="ItemFontSize">12</s:Double>
+    <s:Double x:Key="ControlButtonWidth">120</s:Double>
+    <s:Double x:Key="ControlButtonHeight">56</s:Double>
+    <s:Double x:Key="ItemHeight">32</s:Double>
+    <s:Double x:Key="TabItemHeight">38</s:Double>
+    <s:Double x:Key="TabItemFontSize">16</s:Double>
+    <SolidColorBrush x:Key="TableTitleBackColor" Color="LightGray" />
+    <SolidColorBrush x:Key="OilDefaultColor" Color="#0087FF" />
+    <SolidColorBrush x:Key="WarnColor" Color="Yellow" />
+    <SolidColorBrush x:Key="ErrorColor" Color="Red" />
+</ResourceDictionary>

+ 37 - 56
Client/Dynamicloadsimulationdevice/ViewModels/MainWindowViewModel.cs

@@ -22,7 +22,7 @@ namespace Dynamicloadsimulationdevice.ViewModels
     internal sealed class MainWindowViewModel:IViewModel.ViewModels.DisplayViewModelBase
     {
         private Dictionary<RuntimeTypeHandle, BaseDialogWindow> opendWindows = new Dictionary<RuntimeTypeHandle, BaseDialogWindow>();
-        private List<IDisplayViewModel> DisplayViewModels = new List<IDisplayViewModel>();
+        private List<ViewModelBase> AllViewModels = new List<ViewModelBase>();
         private readonly string PluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory+"Plugins");
         private MainWindowViewModel()
         {
@@ -38,10 +38,10 @@ namespace Dynamicloadsimulationdevice.ViewModels
                         break;
                     default:
                         {
-                            var v = DisplayViewModels.FirstOrDefault(x => x.MenuKey == args.Header);
+                            var v = AllViewModels.OfType<IDisplayViewModel>().FirstOrDefault(x => x.MenuKey == args.Header);
                             if(v==null)
                             {
-                                ShowToast("", Avalonia.Controls.Notifications.NotificationType.Error);
+                                ShowToast(LanguageValueViewModel.Instance.NotSupport, Avalonia.Controls.Notifications.NotificationType.Error);
                                 return;
                             }
                             if(v.ShowTop)
@@ -50,6 +50,7 @@ namespace Dynamicloadsimulationdevice.ViewModels
                                 {
                                     IViewModel.Views.BaseDialogWindow window = new IViewModel.Views.BaseDialogWindow();
                                     window.DataContext = v;
+                                    v.InitData();
                                     window.ShowDialog(desktop.MainWindow!);
                                 }
                             }
@@ -69,6 +70,7 @@ namespace Dynamicloadsimulationdevice.ViewModels
                                     {
                                         opendWindows.Remove(handle);
                                     };
+                                    v.InitData();
                                     window.Show();
                                 }
                             }
@@ -76,32 +78,46 @@ namespace Dynamicloadsimulationdevice.ViewModels
                         break;
                 }
             };
-            IViewModel.ViewModels.MenuViewModel.Instance.Menus.Add(GetFileMenu());
             OilSourceControl =IModel.Tools.PluginsLoader.Defalut.Load<IOilSourceControl.IOilSourceControl>(PluginPath).FirstOrDefault();
             if(OilSourceControl!=null)
             {
                 OilSourceControl.LocalCommunication = () => CommunicationViewModel.Instance.LocalCommunication;
                 OilSourceControl.RemoteCommunication = () => CommunicationViewModel.Instance.ServiceCommunication;
-                var v = GetAssemblyViewModels(OilSourceControl.GetType().Assembly);
-                DisplayViewModels.AddRange(v);
             }
-           DisplayViewModels.Where(x => x.ShowMenu && !string.IsNullOrEmpty(x.MenuParentKey))
+            AllViewModels.AddRange(System.Runtime.Loader.AssemblyLoadContext.Default.Assemblies.SelectMany(x => GetAssemblyViewModels(x)));
+            AllViewModels.OfType<IDisplayViewModel>().Where(x => x.ShowMenu && !string.IsNullOrEmpty(x.MenuParentKey))
                 .GroupBy(x => x.MenuParentKey)
                 .ToList()
                 .ForEach(x=>
                 {
-                    MenuItemViewModel menuItem = new MenuItemViewModel();
-                    menuItem.Header = x.Key;
-                    x.ToList().ForEach(y =>
+                    var menuItem = MenuViewModel.Instance[x.Key];
+                    if(menuItem ==null)
                     {
+                        menuItem = new MenuItemViewModel();
+                        menuItem.Header = x.Key;
+                        MenuViewModel.Instance.Menus.Add(menuItem);
+                    }
+                    x.Select(y=>
+                    {
+                        return(y.GetType().GetCustomAttribute<MenuOrderAttribute>()?.Order?? int.MinValue,y);
+                    })
+                    .OrderBy(x=>x.Item1)
+                    .ToList().ForEach(y =>
+                    {
+                        if(menuItem.Items.Count>0 && y.y.AppendSeparator)
+                        {
+                            menuItem.Items.Add(new MenuItemViewModel()
+                            {
+                                IsSeparator = true,
+                            });
+                        }
                         menuItem.Items.Add(new MenuItemViewModel()
                         {
-                            Header = y.MenuKey,
+                            Header = y.y.MenuKey,
+                            IconKey  = y.y.IconKey,
                         });
                     });
-                    MenuViewModel.Instance.Menus.Add(menuItem);
                 });
-            MenuViewModel.Instance.Menus.Add(GetAboutMenu());
         }
         static MainWindowViewModel()
         {
@@ -109,10 +125,10 @@ namespace Dynamicloadsimulationdevice.ViewModels
         }
         public override void Init()
         {
-
         }
 
-
+        private bool debug = false;
+        public bool Debug { get=>debug; set=> SetProperty(ref debug, value); }
         public IOilSourceControl.IOilSourceControl? OilSourceControl { get; private set; }
         public ICommand ClosingCommand =>new RelayCommand<WindowClosingEventArgs>(Closing);
         private void Closing(object? sender, WindowClosingEventArgs? args)
@@ -130,53 +146,18 @@ namespace Dynamicloadsimulationdevice.ViewModels
                 Environment.Exit(0);
             });
         }
-        private IViewModel.ViewModels.MenuItemViewModel GetFileMenu()
-        {
-            IViewModel.ViewModels.MenuItemViewModel item = new IViewModel.ViewModels.MenuItemViewModel();
-            item.Header = nameof(LanguageValueViewModel.MenuFile);
-            item.Items.Add(new MenuItemViewModel()
-            {
-                Header = nameof(LanguageValueViewModel.MenuLoadConfig),
-                IconKey = nameof(IconResourceValueViewModel.LoadConfigGeometry),
-            });
-            item.Items.Add(new MenuItemViewModel()
-            {
-                Header = nameof(LanguageValueViewModel.MenuSaveConfig),
-                IconKey = nameof(IconResourceValueViewModel.SaveConfigGeometry),
-            });
-            item.Items.Add(new MenuItemViewModel()
-            {
-                IsSeparator = true,
-            });
-            item.Items.Add(new MenuItemViewModel()
-            {
-                Header = nameof(LanguageValueViewModel.MenuExit),
-                IconKey = nameof(IconResourceValueViewModel.ExitGeometry),
-            });
-            return item;
-        }
 
-        private MenuItemViewModel GetAboutMenu()
-        {
-            MenuItemViewModel item = new MenuItemViewModel();
-            item.Header = nameof(LanguageValueViewModel.MenuAbout);
-            item.Items.Add(new MenuItemViewModel()
-            {
-                Header= nameof(LanguageValueViewModel.MenuAbout),
-                IconKey = nameof(IconResourceValueViewModel.AboutGeometry),
-            });
-            return item;
-        }
+
         public static MainWindowViewModel Instance { get; } = new MainWindowViewModel();
 
 
 
-        private List<IDisplayViewModel> GetAssemblyViewModels(Assembly assembly)
+        private List<ViewModelBase> GetAssemblyViewModels(Assembly assembly)
         {
-            List<IDisplayViewModel> vm = new List<IDisplayViewModel>();
-            if (assembly == null) return  new List<IDisplayViewModel>();
+            List<ViewModelBase> vm = new List<ViewModelBase>();
+            if (assembly == null) return  new List<ViewModelBase>();
             return assembly.GetTypes()
-                .Where(x => x.IsAssignableTo(typeof(IDisplayViewModel)) && x.IsAnsiClass && !x.IsAbstract)
+                .Where(x => x.IsAssignableTo(typeof(ViewModelBase)) && x.IsAnsiClass && !x.IsAbstract && x!= typeof(MainWindowViewModel))
                 .Where(x =>
                 {
                     var pro = x.GetProperty("Instance", BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public);
@@ -188,7 +169,7 @@ namespace Dynamicloadsimulationdevice.ViewModels
                     var pro = x.GetProperty("Instance", BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public);
 
                     var val = pro?.GetValue(null);
-                    if (val is IDisplayViewModel vm) return vm;
+                    if (val is ViewModelBase vm) return vm;
                     return null;
                 })
                 .Where(x => x != null)

+ 119 - 0
Client/Dynamicloadsimulationdevice/ViewModels/PlotConfig/PlotConfigViewModel.cs

@@ -0,0 +1,119 @@
+using Avalonia.Collections;
+using Avalonia.Controls;
+using Avalonia.Media;
+using CommunityToolkit.Mvvm.Input;
+using IViewModel.ViewModels;
+using IViewModel.Views;
+using OxyPlot;
+using OxyPlot.Series;
+using Shaker.Models;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace Dynamicloadsimulationdevice.ViewModels
+{
+    internal sealed class PlotConfigViewModel:DisplayViewModelBase
+    {
+        public override double Width => 750;
+        public override double Height => 600;
+        public override bool CanResize => false;
+        public bool NoSeries { get => noSeries; set =>SetProperty(ref noSeries , value); }
+        public AvaloniaList<SeriesConfigViewModel> SeriesConfigs { get; } = new AvaloniaList<SeriesConfigViewModel>();
+        private PlotConfigViewModel()
+        {
+            Content = typeof(Views.PlotConfigView);
+            Title = "PlotConfig";
+        }
+        static PlotConfigViewModel()
+        {
+
+        }
+
+        public ICommand PlotConfigCommand => new RelayCommand<PlotModel>(InitPlot);
+        [AllowNull]
+        private PlotModel _PlotModel;
+        private bool noSeries = false;
+
+        public async void InitPlot(object? sender, PlotModel? model)
+        {
+            if(model ==null || sender is not Control control)
+            {
+                NoSeries = true;
+                SaveIsEnabled = false;
+                return;
+            }
+            _PlotModel = model;
+            SeriesConfigs.Clear();
+            var lists = _PlotModel.Series.OfType<LineSeries>().ToList();
+            if (lists.Count == 0)
+            {
+                NoSeries = true;
+                SaveIsEnabled = false;
+                return;
+            }
+            NoSeries = false;
+            SaveIsEnabled = true;
+            lists.ForEach(x => SeriesConfigs.Add(new SeriesConfigViewModel(x)));
+            base.InitData();
+            BaseDialogWindow window = new BaseDialogWindow();
+            window.DataContext = this;
+            CloseWindowAction = () => window?.Close();
+            await window.ShowDialog((Window)TopLevel.GetTopLevel(control)!);
+        }
+        
+        protected override void Save()
+        {
+            base.Save();
+            var lists = _PlotModel.Series.OfType<LineSeries>().ToList();
+            if (lists.Count == 0) return;
+            _PlotModel.InvalidatePlot(false);
+            try
+            {
+                /*
+                 * 关联的曲线可能由于后台通信指令重新加载
+                 */
+                for(int i=0;i<Math.Min(lists.Count,SeriesConfigs.Count);i++)
+                {
+                    lists[i].Color = OxyColor.FromUInt32(SeriesConfigs[i].Color.ToUInt32());
+                    lists[i].StrokeThickness = SeriesConfigs[i].StrokeThickness;
+                    lists[i].LineStyle = SeriesConfigs[i].LineStyle;
+                    lists[i].MarkerType = SeriesConfigs[i].MarkerType;
+                }
+            }
+            catch
+            {
+
+            }
+            _PlotModel.InvalidatePlot(true);
+        }
+        public static PlotConfigViewModel Instance { get; } = new PlotConfigViewModel();
+    }
+
+    internal class SeriesConfigViewModel : ViewModelBase
+    {
+        private Color color;
+        private LineStyle lineStyle;
+        private MarkerType markerType;
+        private double strokeThickness;
+
+        public SeriesConfigViewModel(LineSeries series)
+        {
+            Color = new Color(series.ActualColor.A, series.ActualColor.R, series.ActualColor.G, series.ActualColor.B);
+            LineStyle = series.LineStyle;
+            MarkerType = series.MarkerType;
+            StrokeThickness = series.StrokeThickness;
+            Name = series.Title;
+        }
+
+        public string Name { get; }
+        public Color Color { get => color; set =>SetProperty(ref color , value); }
+        public LineStyle LineStyle { get => lineStyle; set =>SetProperty(ref lineStyle , value); }
+        public MarkerType MarkerType { get => markerType; set =>SetProperty(ref markerType , value); }
+        public double StrokeThickness { get => strokeThickness; set =>SetProperty(ref strokeThickness , value); }
+    }
+}

+ 48 - 0
Client/Dynamicloadsimulationdevice/ViewModels/ServoConfig/PIViewModel.cs

@@ -0,0 +1,48 @@
+using IViewModel;
+using IViewModel.ViewModels;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Dynamicloadsimulationdevice.ViewModels
+{
+    public sealed class PIViewModel:ViewModelBase<Shaker.Models.PIModel>
+    {
+        public PIViewModel():base()
+        {
+        }
+        public PIViewModel(Shaker.Models.PIModel model):base(model)
+        {
+
+        }
+        /// <summary>
+        /// P参数
+        /// </summary>
+        [PropertyAssociation(nameof(P))]
+        public double P { get => Model.P; set => SetProperty(ref Model.P, value); }
+        /// <summary>
+        /// I参数
+        /// </summary>
+        [PropertyAssociation(nameof(I))]
+        public double I { get => Model.I; set => SetProperty(ref Model.I, value); }
+
+        /// <summary>
+        /// 最大P值
+        /// </summary>
+        public double MaxP =>Model.MaxP;
+        /// <summary>
+        /// 最小P值
+        /// </summary>
+        public double MinP => Model.MinP;
+        /// <summary>
+        /// 最大I值
+        /// </summary>
+        public double MaxI => Model.MaxI;
+        /// <summary>
+        /// 最小I值
+        /// </summary>
+        public double MinI => Model.MinI;
+    }
+}

+ 172 - 0
Client/Dynamicloadsimulationdevice/ViewModels/ServoConfig/ServoConfigViewModel.cs

@@ -0,0 +1,172 @@
+using Avalonia.Collections;
+using IViewModel;
+using IViewModel.ViewModels;
+using MessagePack.Formatters;
+using Mono.Cecil;
+using Shaker.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Dynamicloadsimulationdevice.ViewModels
+{
+    public sealed class ServoConfigViewModel : DisplayViewModelBase<ServoConfigModel>
+    {
+        private ServoConfigViewModel() : base()
+        {
+            Title = nameof(LanguageValueViewModel.MenuDeviceConfig);
+            Content = typeof(Views.ServoConfigView);
+#if NO_DEVICE
+            UpDateModel(new ServoConfigModel()
+            {
+                PI = [.. Enumerable.Range(0,ShakerConfigViewModel.Instance.VerticalCount).Select(x=>new PIModel()),..Enumerable.Range(0,ShakerConfigViewModel.Instance.VerticalCount).Select(x=>new PIModel())]
+            });
+#endif
+        }
+        static ServoConfigViewModel()
+        {
+
+        }
+        public override double Width => 780;
+        public override double Height => 620;
+        public override bool CanResize => false;
+        public override string MenuKey => nameof(LanguageValueViewModel.MenuDeviceConfig);
+        public override string MenuParentKey => nameof(LanguageValueViewModel.MenuDevice);
+        public override string IconKey => nameof(IconResourceValueViewModel.DeviceConfigGeometry);
+
+        public override bool ShowTop => true;
+
+        public override void UpDateModel(ServoConfigModel model)
+        {
+            foreach (var item in PI)
+            {
+                item.Value.PropertyChanged -= (sender, args) => SetSaveClose(true);
+            }
+            PI.Clear();
+            if(model.PI.Count>0)
+            {
+                for (int i = 0; i < model.PI.Count; i++)
+                {
+                    PI.Add(new IndexValueItemViewModel<PIViewModel>(i + 1, new PIViewModel(model.PI[i])));
+                    PI[^1].Value.PropertyChanged += (sender, args) => SetSaveClose(true);
+                }
+            }
+            base.UpDateModel(model);
+        }
+        protected override void Save()
+        {
+            base.Save();
+            savecanclose = false;
+        }
+        public override string OKContent => nameof(LanguageValueViewModel.Apply);
+        public static ServoConfigViewModel Instance { get; } = new ServoConfigViewModel();
+        public AvaloniaList<IndexValueItemViewModel<PIViewModel>> PI { get; } = new AvaloniaList<IndexValueItemViewModel<PIViewModel>>();
+        /// <summary>
+        /// 水平阀死区电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.HorizontalBarrierPotential))]
+        public double HorizontalBarrierPotential { get => Model.HorizontalBarrierPotential; set => SetProperty(ref Model.HorizontalBarrierPotential, value); }
+        /// <summary>
+        /// 最大水平阀死区电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MaxHorizontalBarrierPotential))]
+        public double MaxHorizontalBarrierPotential { get => Model.MaxHorizontalBarrierPotential; }
+        /// <summary>
+        /// 最小水平阀死区电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MinHorizontalBarrierPotential))]
+        public double MinHorizontalBarrierPotential { get => Model.MinHorizontalBarrierPotential; }
+        /// <summary>
+        /// 竖直阀死区电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.VerticalBarrierPotential))]
+        public double VerticalBarrierPotential { get => Model.VerticalBarrierPotential; set => SetProperty(ref Model.VerticalBarrierPotential, value); }
+        /// <summary>
+        /// 最大竖直阀死区电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MaxVerticalBarrierPotential))]
+        public double MaxVerticalBarrierPotential { get => Model.MaxVerticalBarrierPotential; }
+        /// <summary>
+        /// 最小竖直阀死区电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MinVerticalBarrierPotential))]
+        public double MinVerticalBarrierPotential { get => Model.MinVerticalBarrierPotential; }
+        /// <summary>
+        /// 最大积分电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MaxIntegratedVoltage))]
+        public double MaxIntegratedVoltage { get => Model.MaxIntegratedVoltage; set => SetProperty(ref Model.MaxIntegratedVoltage, value); }
+        /// <summary>
+        /// 最大最大积分电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MaxMaxIntegratedVoltage))]
+        public double MaxMaxIntegratedVoltage { get => Model.MaxMaxIntegratedVoltage; }
+        /// <summary>
+        /// 最小最大积分电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MinMaxIntegratedVoltage))]
+        public double MinMaxIntegratedVoltage { get => Model.MinMaxIntegratedVoltage; }
+        /// <summary>
+        /// 位移前馈增益
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.DisplacementFeedforwardGain))]
+        public double DisplacementFeedforwardGain { get => Model.DisplacementFeedforwardGain; set => SetProperty(ref Model.DisplacementFeedforwardGain, value); }
+        /// <summary>
+        /// 最大位移前馈增益
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MaxDisplacementFeedforwardGain))]
+        public double MaxDisplacementFeedforwardGain { get => Model.MaxDisplacementFeedforwardGain; }
+        /// <summary>
+        /// 最小位移前馈增益
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MinDisplacementFeedforwardGain))]
+        public double MinDisplacementFeedforwardGain { get => Model.MinDisplacementFeedforwardGain; }
+        /// <summary>
+        /// 最大驱动电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MaxDriverVoltage))]
+        public double MaxDriverVoltage { get => Model.MaxDriverVoltage; set => SetProperty(ref Model.MaxDriverVoltage, value); }
+        /// <summary>
+        /// 最大最大驱动电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MaxMaxDriverVoltage))]
+        public double MaxMaxDriverVoltage { get => Model.MaxMaxDriverVoltage; }
+        /// <summary>
+        /// 最小最大驱动电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MinMaxDriverVoltage))]
+        public double MinMaxDriverVoltage { get => Model.MinMaxDriverVoltage; }
+        /// <summary>
+        /// 驱动超限电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.DriverOverLimitVoltage))]
+        public double DriverOverLimitVoltage { get => Model.DriverOverLimitVoltage; set => SetProperty(ref Model.DriverOverLimitVoltage, value); }
+        /// <summary>
+        /// 最大驱动超限电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MaxDriverOverLimitVoltage))]
+        public double MaxDriverOverLimitVoltage { get => Model.MaxDriverOverLimitVoltage; }
+        /// <summary>
+        /// 最小驱动超限电压(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MinDriverOverLimitVoltage))]
+        public double MinDriverOverLimitVoltage { get => Model.MinDriverOverLimitVoltage; }
+        /// <summary>
+        /// 急停后驱动限幅值(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.EmerhencyDriverLimitVoltage))]
+        public double EmerhencyDriverLimitVoltage { get => Model.EmerhencyDriverLimitVoltage; set => SetProperty(ref Model.EmerhencyDriverLimitVoltage, value); }
+        /// <summary>
+        /// 最大急停后驱动限幅值(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MaxEmerhencyDriverLimitVoltage))]
+        public double MaxEmerhencyDriverLimitVoltage { get => Model.MaxEmerhencyDriverLimitVoltage; }
+        /// <summary>
+        /// 最小急停后驱动限幅值(V)
+        /// </summary>
+        [PropertyAssociation(nameof(ServoConfigModel.MinEmerhencyDriverLimitVoltage))]
+        public double MinEmerhencyDriverLimitVoltage { get => Model.MinEmerhencyDriverLimitVoltage; }
+    }
+}

+ 88 - 0
Client/Dynamicloadsimulationdevice/ViewModels/ShakerChannel/AIConfigViewModel.cs

@@ -0,0 +1,88 @@
+using IViewModel;
+using IViewModel.ViewModels;
+using Shaker.Model;
+using Shaker.Models;
+using Shaker.Tools;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Dynamicloadsimulationdevice.ViewModels
+{
+    internal sealed class AIConfigViewModel:ViewModelBase<Shaker.Models.AIConfigModel>
+    {
+        private string unit=string.Empty;
+
+        public AIConfigViewModel() : base()
+        {
+
+        }
+        public AIConfigViewModel(Shaker.Models.AIConfigModel model):base(model)
+        {
+        }
+        public override void UpDateModel(AIConfigModel model)
+        {
+            base.UpDateModel(model);
+            Unit = ChannelType.GetUnit();
+        }
+        /// <summary>
+        /// 通道序号
+        /// </summary>
+        [PropertyAssociation(nameof(Model.Channel))]
+        public AIChannel Channel { get => Model.Channel; set => SetProperty(ref Model.Channel, value); }
+        /// <summary>
+        /// 灵敏度
+        /// </summary>
+        [PropertyAssociation(nameof(Sensitivity))]
+        public double Sensitivity { get => Model.Sensitivity; set => SetProperty(ref Model.Sensitivity, value); }
+        /// <summary>
+        /// 最大灵敏度
+        /// </summary>
+        [PropertyAssociation(nameof(MaxSensitivity))]
+        public double MaxSensitivity => Model.MaxSensitivity;
+        /// <summary>
+        /// 最小灵敏度
+        /// </summary>
+        [PropertyAssociation(nameof(MinSensitivity))]
+        public double MinSensitivity => Model.MinSensitivity;
+        /// <summary>
+        /// 偏置
+        ///<para> 当<see cref="ChannelType"/> ==<see cref="AIChannelType.Acceleration"/>和<see cref="AIChannelType.OutSignal"/>无效</para>
+        /// </summary>
+        [PropertyAssociation(nameof(Bias))]
+        public double Bias { get => Model.Bias; set => SetProperty(ref Model.Bias, value); }
+        /// <summary>
+        /// 最大偏置
+        /// </summary>
+        [PropertyAssociation(nameof(MaxBias))]
+        public double MaxBias => Model.MaxBias;
+        /// <summary>
+        /// 最小偏置
+        /// </summary>
+        [PropertyAssociation(nameof(MinBias))]
+        public double MinBias => Model.MinBias;
+        /// <summary>
+        /// 模拟通道类型
+        /// </summary>
+        [PropertyAssociation(nameof(ChannelType))]
+        public AIChannelType ChannelType =>Model.ChannelType;
+        /// <summary>
+        /// 单位
+        /// </summary>
+        [PropertyAssociation(nameof(Model.ChannelType))]
+        public string Unit { get => unit; private set =>SetProperty(ref unit , value); }
+
+
+        [PropertyAssociation(nameof(ChannelType))]
+        public bool BiasVisibily=>ChannelType == AIChannelType.DifferentialPressure || ChannelType == AIChannelType.Pressure || ChannelType == AIChannelType.Displacement ;
+        [PropertyAssociation(nameof(ChannelType))]
+        public bool SensitivityVisibily => ChannelType != AIChannelType.OutSignal;
+
+        public override string ToString()
+        {
+            return $"{ChannelType} {Channel}{(SensitivityVisibily?$" {nameof(Sensitivity)}:{Sensitivity}mV/{Unit}":"")}{(BiasVisibily?$" {nameof(Bias)}:{Bias}{Unit}":"")}";
+        }
+    }
+}

+ 93 - 0
Client/Dynamicloadsimulationdevice/ViewModels/ShakerChannel/AOConfigViewModel.cs

@@ -0,0 +1,93 @@
+using IViewModel;
+using IViewModel.ViewModels;
+using Shaker.Model;
+using Shaker.Models;
+using Shaker.Tools;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Dynamicloadsimulationdevice.ViewModels
+{
+    internal sealed class AOConfigViewModel:ViewModelBase<Shaker.Models.AOConfigModel>
+    {
+        private string unit=string.Empty;
+
+        public AOConfigViewModel():base()
+        {
+
+        }
+        public AOConfigViewModel(Shaker.Models.AOConfigModel model):base(model)
+        {
+
+        }
+        public override void UpDateModel(AOConfigModel model)
+        {
+            Unit = model.ChannelType.GetUnit();
+            base.UpDateModel(model);
+        }
+        /// <summary>
+        /// 极性
+        /// </summary>
+        [PropertyAssociation(nameof(Model.Polarity))]
+        public Shaker.Model.Polarity Polarity { get=> Model.Polarity; set=>SetProperty(ref Model.Polarity , value); }
+        /// <summary>
+        /// 开环
+        /// </summary>
+        [PropertyAssociation(nameof(Model.OpenLoop))]
+        public bool OpenLoop { get => Model.OpenLoop;set=>SetProperty(ref Model.OpenLoop, value); }
+        /// <summary>
+        /// 开环驱动
+        /// </summary>
+        [PropertyAssociation(nameof(Model.OpenLoopDriver))]
+        public double OpenLoopDriver { get=>Model.OpenLoopDriver; set=>SetProperty(ref Model.OpenLoopDriver, value); }
+        /// <summary>
+        /// 最大开环驱动
+        /// </summary>
+        [PropertyAssociation(nameof(Model.MaxOpenLoopDriver))]
+        public double MaxOpenLoopDriver =>Model.MaxOpenLoopDriver;
+        /// <summary>
+        /// 最小开环驱动
+        /// </summary>
+        [PropertyAssociation(nameof(Model.MinOpenLoopDriver))]
+        public double MinOpenLoopDriver =>Model.MinOpenLoopDriver;
+        /// <summary>
+        /// 通道序号
+        /// </summary>
+        [PropertyAssociation(nameof(Model.Channel))]
+        public AOChannel Channel { get=>Model.Channel; set=>SetProperty(ref Model.Channel, value); }
+        /// <summary>
+        /// 偏置
+        /// </summary>
+        [PropertyAssociation(nameof(Model.Bias))]
+        public double Bias { get=>Model.Bias; set=>SetProperty(ref Model.Bias, value); }
+        /// <summary>
+        /// 最大偏置
+        /// </summary>
+        [PropertyAssociation(nameof(Model.MaxBias))]
+        public double MaxBias => Model.MaxBias;
+        /// <summary>
+        /// 最小偏置
+        /// </summary>
+        [PropertyAssociation(nameof(Model.MinBias))]
+        public double MinBias => Model.MinBias;
+        /// <summary>
+        /// 通道类型
+        /// </summary>
+        [PropertyAssociation(nameof(Model.ChannelType))]
+        public AOChannelType ChannelType => Model.ChannelType;
+        /// <summary>
+        /// 单位
+        /// </summary>
+        [PropertyAssociation(nameof(Model.ChannelType))]
+        public string Unit { get => unit; private set =>SetProperty(ref unit , value); }
+        [PropertyAssociation(nameof(ChannelType))]
+        public bool CanOpenLoop => ChannelType != AOChannelType.Balancing;
+        public override string ToString()
+        {
+            return CanOpenLoop ? (OpenLoop? $"{ChannelType} {Channel} {Polarity} {nameof(Bias)}:{Bias} {nameof(OpenLoop)}{OpenLoop}" :$"{ChannelType} {Channel} {Polarity} {nameof(Bias)}:{Bias}") : $"{ChannelType} {Channel}";
+        }
+    }
+}

+ 53 - 0
Client/Dynamicloadsimulationdevice/ViewModels/ShakerChannel/ResultChannelViewModel.cs

@@ -0,0 +1,53 @@
+using IViewModel.ViewModels;
+using IViewModel.Tools;
+using Shaker.Models;
+using Shaker.Tools;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Dynamicloadsimulationdevice.ViewModels
+{
+    internal sealed class ResultChannelViewModel : ViewModelBase<ResultChannelModel>
+    {
+        public ResultChannelViewModel():base()
+        {
+
+        }
+        public ResultChannelViewModel(ResultChannelModel model):base(model)
+        {
+
+        }
+        public override void UpDateModel(ResultChannelModel model)
+        {
+            base.UpDateModel(model);
+            Unit = ChannelType.GetUnit();
+            Name = ChannelType.Description();
+        }
+        private string unit=string.Empty;
+        private string name = string.Empty;
+
+        /// <summary>
+        /// 通道定义
+        /// </summary>
+        public Shaker.Model.ResultChannel Channel { get => Model.Channel; set => Model.Channel = value; }
+        /// <summary>
+        /// 通道类型
+        /// </summary>
+        public Shaker.Model.ResultChannelType ChannelType => Model.ChannelType;
+        /// <summary>
+        /// 单位
+        /// </summary>
+        public string Unit { get => unit; private set =>SetProperty(ref unit, value); }
+        /// <summary>
+        /// 名称
+        /// </summary>
+        public string Name { get => name; private set => name = value; }
+        public override string ToString()
+        {
+            return $"{ChannelType} {Channel} {Unit}";
+        }
+    }
+}

+ 323 - 0
Client/Dynamicloadsimulationdevice/ViewModels/ShakerChannel/ShakerChannelViewModel.cs

@@ -0,0 +1,323 @@
+using Avalonia.Collections;
+using IViewModel.ViewModels;
+using Shaker.Model;
+using Shaker.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace Dynamicloadsimulationdevice.ViewModels
+{
+    internal sealed class ShakerChannelViewModel:DisplayViewModelBase<Shaker.Models.ShakerChannelConfigModel>
+    {
+        private ShakerChannelViewModel() : base()
+        {
+            Title = nameof(LanguageValueViewModel.MenuChannelSetting);
+            Content = typeof(Views.ShakerChannelView);
+
+#if NO_DEVICE
+            var model =new ShakerChannelConfigModel()
+            {
+                Displacement = [.. Enumerable.Range(0, ShakerConfigViewModel.Instance.DisplacementCount).Select(x => new AIConfigModel()
+                {
+                    ChannelType = AIChannelType.Displacement,
+                })],
+                Acceleration = [.. Enumerable.Range(0,ShakerConfigViewModel.Instance.AccelerationCount).Select(x=>new AIConfigModel()
+                {
+                    ChannelType = AIChannelType.Acceleration,
+                })],
+                OutSignal = [.. Enumerable.Range(0, ShakerConfigViewModel.Instance.OutSignalCount).Select(x => new AIConfigModel()
+                {
+                    ChannelType = AIChannelType.OutSignal,
+                })],
+                DifferentialPressure = [.. Enumerable.Range(0, ShakerConfigViewModel.Instance.DifferentialPressureCount).Select(x => new AIConfigModel()
+                {
+                    ChannelType = AIChannelType.DifferentialPressure,
+                })],
+                Pressure = [.. Enumerable.Range(0, ShakerConfigViewModel.Instance.PressureCount).Select(x => new AIConfigModel()
+                {
+                    ChannelType = AIChannelType.Pressure,
+                })],
+                Horizontal = [.. Enumerable.Range(0, ShakerConfigViewModel.Instance.HorizontalCount).Select(x => new AOConfigModel()
+                {
+                    ChannelType = AOChannelType.Horizontal,
+                })],
+                Vertical = [.. Enumerable.Range(0, ShakerConfigViewModel.Instance.VerticalCount).Select(x => new AOConfigModel()
+                {
+                    ChannelType = AOChannelType.Vertical,
+                })],
+                Balancing = [.. Enumerable.Repeat(AOChannel.Channel0, ShakerConfigViewModel.Instance.BalancingCount)],
+            };
+            ResultChannelType type = ResultChannelType.ActualDisplacement;
+            model.ResultChannels = [.. ShakerConfigViewModel.Instance.MaxResultChannels.SelectMany(x =>
+            {
+                var c = Enumerable.Range(0, x).Select(y => (new ResultChannelModel()
+                {
+                    ChannelType = type
+                }));
+                type++;
+                return c;
+            })];
+            for(ResultChannel i= ResultChannel.Channal0;i<ResultChannel.Channal0+model.ResultChannels.Count;i++)
+            {
+                model.ResultChannels[(int)i].Channel = i;
+            }
+            UpDateModel(model);
+#endif
+        }
+        static ShakerChannelViewModel()
+        {
+
+        }
+        
+        public override string OKContent => nameof(LanguageValueViewModel.Apply);
+        public override string IconKey => nameof(IconResourceValueViewModel.ChannelConfigGeometry);
+        public override string MenuKey => nameof(LanguageValueViewModel.MenuChannelSetting);
+        public override string MenuParentKey => nameof(LanguageValueViewModel.MenuDevice);
+        public static ShakerChannelViewModel Instance { get; } = new ShakerChannelViewModel();
+        /// <summary>
+        /// 位移通道
+        /// </summary>
+        public AvaloniaList<IndexValueItemViewModel<AIConfigViewModel>> Displacement { get; } = new AvaloniaList<IndexValueItemViewModel<AIConfigViewModel>>();
+        /// <summary>
+        /// 加速度通道
+        /// </summary>
+        public AvaloniaList<IndexValueItemViewModel<AIConfigViewModel>> Acceleration { get; } = new AvaloniaList<IndexValueItemViewModel<AIConfigViewModel>>();
+        /// <summary>
+        /// 外部输入通道
+        /// </summary>
+        public AvaloniaList<IndexValueItemViewModel<AIConfigViewModel>> OutSignal { get; } = new AvaloniaList<IndexValueItemViewModel<AIConfigViewModel>>();
+        /// <summary>
+        /// 压差通道
+        /// </summary>
+        public AvaloniaList<IndexValueItemViewModel<AIConfigViewModel>> DifferentialPressure { get; } = new AvaloniaList<IndexValueItemViewModel<AIConfigViewModel>>();
+        /// <summary>
+        /// 支撑压力通道
+        /// </summary>
+        public AvaloniaList<IndexValueItemViewModel<AIConfigViewModel>> Pressure { get; } = new AvaloniaList<IndexValueItemViewModel<AIConfigViewModel>>();
+        /// <summary>
+        /// 水平缸通道
+        /// </summary>
+        public AvaloniaList<IndexValueItemViewModel<AOConfigViewModel>> Horizontal { get; } = new AvaloniaList<IndexValueItemViewModel<AOConfigViewModel>>();
+        /// <summary>
+        /// 垂直缸通道
+        /// </summary>
+        public AvaloniaList<IndexValueItemViewModel<AOConfigViewModel>> Vertical { get; } = new AvaloniaList<IndexValueItemViewModel<AOConfigViewModel>>();
+        /// <summary>
+        /// 平衡缸通道
+        /// </summary>
+        public AvaloniaList<IndexValueItemViewModel<AOChannel>> Balancing { get; } = new AvaloniaList<IndexValueItemViewModel<AOChannel>>();
+        /// <summary>
+        /// 结果通道
+        /// </summary>
+
+        public AvaloniaList<IndexValueItemViewModel<ResultChannelViewModel>> ResultChannels { get; } = new AvaloniaList<IndexValueItemViewModel<ResultChannelViewModel>>();
+        public override void UpDateModel(ShakerChannelConfigModel model)
+        {
+            foreach (var item in Displacement)
+            {
+                item.Value.PropertyChanged -= (_, _) => SetSaveClose(true);
+            }
+            Displacement.Clear();
+            Displacement.AddRange(Enumerable.Range(0, model.Displacement.Count).Select(x => new IndexValueItemViewModel<AIConfigViewModel>(x + 1, new AIConfigViewModel(model.Displacement[x]))));
+            foreach (var item in Displacement)
+            {
+                item.Value.PropertyChanged += (_, _) => SetSaveClose(true);
+            }
+
+            foreach (var item in Acceleration)
+            {
+                item.Value.PropertyChanged -= (_, _) => SetSaveClose(true);
+            }
+            Acceleration.Clear();
+            Acceleration.AddRange(Enumerable.Range(0, model.Acceleration.Count).Select(x => new IndexValueItemViewModel<AIConfigViewModel>(x + 1, new AIConfigViewModel(model.Acceleration[x]))));
+            foreach (var item in Acceleration)
+            {
+                item.Value.PropertyChanged += (_, _) => SetSaveClose(true);
+            }
+
+            foreach (var item in OutSignal)
+            {
+                item.Value.PropertyChanged -= (_, _) => SetSaveClose(true);
+            }
+            OutSignal.Clear();
+            OutSignal.AddRange(Enumerable.Range(0, model.OutSignal.Count).Select(x => new IndexValueItemViewModel<AIConfigViewModel>(x + 1, new AIConfigViewModel(model.OutSignal[x]))));
+            foreach (var item in OutSignal)
+            {
+                item.Value.PropertyChanged += (_, _) => SetSaveClose(true);
+            }
+
+            foreach (var item in DifferentialPressure)
+            {
+                item.Value.PropertyChanged -= (_, _) => SetSaveClose(true);
+            }
+            DifferentialPressure.Clear();
+            DifferentialPressure.AddRange(Enumerable.Range(0, model.DifferentialPressure.Count).Select(x => new IndexValueItemViewModel<AIConfigViewModel>(x + 1, new AIConfigViewModel(model.DifferentialPressure[x]))));
+            foreach (var item in DifferentialPressure)
+            {
+                item.Value.PropertyChanged += (_, _) => SetSaveClose(true);
+            }
+
+            foreach (var item in Pressure)
+            {
+                item.Value.PropertyChanged -= (_, _) => SetSaveClose(true);
+            }
+            Pressure.Clear();
+            Pressure.AddRange(Enumerable.Range(0, model.Pressure.Count).Select(x => new IndexValueItemViewModel<AIConfigViewModel>(x + 1, new AIConfigViewModel(model.Pressure[x]))));
+            foreach (var item in Pressure)
+            {
+                item.Value.PropertyChanged += (_, _) => SetSaveClose(true);
+            }
+
+            foreach (var item in Horizontal)
+            {
+                item.Value.PropertyChanged -= (_, _) => SetSaveClose(true);
+            }
+            Horizontal.Clear();
+            Horizontal.AddRange(Enumerable.Range(0, model.Horizontal.Count).Select(x => new IndexValueItemViewModel<AOConfigViewModel>(x + 1, new AOConfigViewModel(model.Horizontal[x]))));
+            foreach (var item in Horizontal)
+            {
+                item.Value.PropertyChanged += (_, _) => SetSaveClose(true);
+            }
+
+            foreach (var item in Vertical)
+            {
+                item.Value.PropertyChanged -= (_, _) => SetSaveClose(true);
+            }
+            Vertical.Clear();
+            Vertical.AddRange(Enumerable.Range(0, model.Vertical.Count).Select(x => new IndexValueItemViewModel<AOConfigViewModel>(x + 1, new AOConfigViewModel(model.Vertical[x]))));
+            foreach (var item in Vertical)
+            {
+                item.Value.PropertyChanged += (_, _) => SetSaveClose(true);
+            }
+
+
+            foreach (var item in Balancing)
+            {
+                item.PropertyChanged -= (_, _) => SetSaveClose(true);
+            }
+            Balancing.Clear();
+            Balancing.AddRange(Enumerable.Range(0, model.Balancing.Count).Select(x => new IndexValueItemViewModel<AOChannel>(x, model.Balancing[x])));
+            foreach (var item in Balancing)
+            {
+                item.PropertyChanged += (_, _) => SetSaveClose(true);
+            }
+
+
+            foreach (var item in ResultChannels)
+            {
+                item.Value.PropertyChanged -= (_, _) => SetSaveClose(true);
+            }
+            ResultChannels.Clear();
+            ResultChannels.AddRange(Enumerable.Range(0, model.ResultChannels.Count).Select(x => new IndexValueItemViewModel<ResultChannelViewModel>(x,new ResultChannelViewModel(model.ResultChannels[x]))));
+            foreach (var item in ResultChannels)
+            {
+                item.Value.PropertyChanged += (_, _) => SetSaveClose(true);
+            }
+            base.UpDateModel(model);
+        }
+
+        protected override void Save()
+        {
+            if(Displacement.Select(x=>x.Value.Channel).Distinct().Count()!=Displacement.Count)
+            {
+                ShowError($"{LanguageValueViewModel.Instance.Displacement}{LanguageValueViewModel.Instance.ChannelRepeat}");
+                savecanclose = false;
+                return;
+            }
+
+            if(Displacement.Any(x=>x.Value.Sensitivity ==0))
+            {
+
+                ShowError($"{LanguageValueViewModel.Instance.Displacement}{LanguageValueViewModel.Instance.SensitivityIsZero}");
+                savecanclose = false;
+                return;
+            }
+
+            if (Acceleration.Select(x => x.Value.Channel).Distinct().Count() != Acceleration.Count)
+            {
+                ShowError($"{LanguageValueViewModel.Instance.Acceleration}{LanguageValueViewModel.Instance.ChannelRepeat}");
+                savecanclose = false;
+                return;
+            }
+            if(Acceleration.Any(x=>x.Value.Sensitivity ==0))
+            {
+                ShowError($"{LanguageValueViewModel.Instance.Acceleration}{LanguageValueViewModel.Instance.SensitivityIsZero}");
+                savecanclose = false;
+                return;
+            }
+
+
+            if (OutSignal.Select(x => x.Value.Channel).Distinct().Count() != OutSignal.Count)
+            {
+                ShowError($"{LanguageValueViewModel.Instance.OutInputSignal}{LanguageValueViewModel.Instance.ChannelRepeat}");
+                savecanclose = false;
+                return;
+            }
+            if (OutSignal.Any(x => x.Value.Sensitivity == 0))
+            {
+                ShowError($"{LanguageValueViewModel.Instance.OutInputSignal}{LanguageValueViewModel.Instance.OutSignalGainIsZero}");
+                savecanclose = false;
+                return;
+            }
+
+            if (Pressure.Select(x => x.Value.Channel).Distinct().Count() != Pressure.Count)
+            {
+                ShowError($"{LanguageValueViewModel.Instance.ValvePressure}{LanguageValueViewModel.Instance.ChannelRepeat}");
+                savecanclose = false;
+                return;
+            }
+            if (Pressure.Any(x => x.Value.Sensitivity == 0))
+            {
+                ShowError($"{LanguageValueViewModel.Instance.ValvePressure}{LanguageValueViewModel.Instance.SensitivityIsZero}");
+                savecanclose = false;
+                return;
+            }
+
+            if (DifferentialPressure.Select(x => x.Value.Channel).Distinct().Count() != DifferentialPressure.Count)
+            {
+                ShowError($"{LanguageValueViewModel.Instance.DifferentialPressure}{LanguageValueViewModel.Instance.ChannelRepeat}");
+                savecanclose = false;
+                return;
+            }
+            if(DifferentialPressure.Any(x=>x.Value.Sensitivity ==0))
+            {
+                ShowError($"{LanguageValueViewModel.Instance.DifferentialPressure}{LanguageValueViewModel.Instance.SensitivityIsZero}");
+                savecanclose = false;
+                return;
+            }
+
+            if (Vertical.Select(x => x.Value.Channel).Distinct().Count() != Vertical.Count)
+            {
+                ShowError($"{LanguageValueViewModel.Instance.Vertical}{LanguageValueViewModel.Instance.ChannelRepeat}");
+                savecanclose = false;
+                return;
+            }
+
+            if (Horizontal.Select(x => x.Value.Channel).Distinct().Count() != Horizontal.Count)
+            {
+                ShowError($"{LanguageValueViewModel.Instance.Horizontal}{LanguageValueViewModel.Instance.ChannelRepeat}");
+                savecanclose = false;
+                return;
+            }
+
+            if (Balancing.Select(x => x.Value).Distinct().Count() != Balancing.Count)
+            {
+                ShowError($"{LanguageValueViewModel.Instance.Balancing}{LanguageValueViewModel.Instance.ChannelRepeat}");
+                savecanclose = false;
+                return;
+            }
+            if (ResultChannels.Select(x => x.Value.Channel).Distinct().Count() != ResultChannels.Count)
+            {
+                ShowError($"{LanguageValueViewModel.Instance.ResultChannels}{LanguageValueViewModel.Instance.ChannelRepeat}");
+                savecanclose = false;
+                return;
+            }
+            base.Save();
+            savecanclose = false;
+        }
+    }
+}

+ 173 - 0
Client/Dynamicloadsimulationdevice/ViewModels/ShakerConfig/ShakerConfigViewModel.cs

@@ -0,0 +1,173 @@
+using Avalonia.Collections;
+using IViewModel.ViewModels;
+using Shaker.Model;
+using Shaker.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Dynamicloadsimulationdevice.ViewModels
+{
+    internal sealed class ShakerConfigViewModel:ViewModelBase<ShakerConfigModel>
+    {
+        private List<byte> maxResultChannels = new List<byte>();
+        private ShakerConfigViewModel():base()
+        {
+            GetEvent<AllConfig>().Subscrip((sender, args) =>
+            {
+                UpDateModel(args.Data.ShakerConfig);
+            });
+#if NO_DEVICE
+            UpDateModel(new ShakerConfigModel());
+            #endif
+
+        }
+        static ShakerConfigViewModel()
+        {
+
+        }
+        public override void UpDateModel(ShakerConfigModel model)
+        {
+            AIs.Clear();
+            AIs.AddRange(Enumerable.Range(0, model.MaxAICount).Select(x => (AIChannel)((byte)AIChannel.Channel0 + x)));
+
+
+            AOs.Clear();
+            AOs.AddRange(Enumerable.Range(0, model.MaxAOCount).Select(x => (AOChannel)((byte)AOChannel.Channel0 + x)));
+
+            model ??= new ShakerConfigModel();
+            maxResultChannels = [MaxActualDisplacement, MaxGivenDisplacement, MaxHorizontalCylinderDrive, MaxVerticalCylinderDrive, MaxBalancingCylinderDrive, MaxDifferentialPressure, MaxSupportingPressure, MaxSixFreedomsGivenDisplacement, MaxCurrentLocation, MaxAcceleration, MaxOutSignal];
+            base.UpDateModel(model);
+        }
+        public static ShakerConfigViewModel Instance { get; } = new ShakerConfigViewModel();
+        /// <summary>
+        /// 水平缸数
+        /// </summary>
+        public byte HorizontalCount => Model.HorizontalCount;
+        /// <summary>
+        /// 垂直缸数
+        /// </summary>
+        public byte VerticalCount => Model.VerticalCount;
+        /// <summary>
+        /// 平衡缸数
+        /// </summary>
+        public byte BalancingCount => Model.BalancingCount;
+        /// <summary>
+        /// 压差通道数
+        /// </summary>
+        public byte DifferentialPressureCount => Model.DifferentialPressureCount;
+        /// <summary>
+        /// 压力通道数
+        /// </summary>
+        public byte PressureCount => Model.PressureCount;
+        /// <summary>
+        /// 外部输入通道数
+        /// </summary>
+        public byte OutSignalCount => Model.OutSignalCount;
+        /// <summary>
+        /// 重力加速度(m/s^2)
+        /// </summary>
+        public double G => Model.G;
+        /// <summary>
+        /// 位移通道数
+        /// </summary>
+        public byte DisplacementCount => Model.DisplacementCount;
+        /// <summary>
+        /// 加速度通道数
+        /// </summary>
+        public byte AccelerationCount => Model.AccelerationCount;
+        /// <summary>
+        /// 最大模拟输入通道数
+        /// </summary>
+        public byte MaxAICount => Model.MaxAICount;
+        /// <summary>
+        /// 最大模拟输出通道数
+        /// </summary>
+        public byte MaxAOCount => Model.MaxAOCount;
+        /// <summary>
+        /// 水平缸通道数
+        /// </summary>
+        public byte MaxHorizontalCount => Model.MaxHorizontalCount;
+        /// <summary>
+        /// 垂直缸通道数
+        /// </summary>
+        public byte MaxVerticalCount => Model.MaxVerticalCount;
+        /// <summary>
+        /// 平衡缸通道数
+        /// </summary>
+        public byte MaxBalancingCount => Model.MaxBalancingCount;
+        /// <summary>
+        /// 采样率
+        /// </summary>
+        public ushort SampleRate => Model.SampleRate;
+        /// <summary>
+        /// Fpga主时钟
+        /// </summary>
+        public uint FpgaClock => Model.FpgaClock;
+
+        /// <summary>
+        /// fifo中数据通道数
+        /// </summary>
+        public byte MaxFifoChannelCount => Model.MaxFifoChannelCount;
+        /// <summary>
+        /// 实际位移通道数
+        /// </summary>
+        public byte MaxActualDisplacement => Model.MaxActualDisplacement;
+        /// <summary>
+        /// 给定位移通道数
+        /// </summary>
+        public byte MaxGivenDisplacement => Model.MaxGivenDisplacement;
+        /// <summary>
+        /// 水平缸驱动通道数
+        /// </summary>
+        public byte MaxHorizontalCylinderDrive => Model.MaxHorizontalCylinderDrive;
+        /// <summary>
+        /// 垂直缸驱动通道数
+        /// </summary>
+        public byte MaxVerticalCylinderDrive => Model.MaxVerticalCylinderDrive;
+        /// <summary>
+        /// 平衡缸驱动通道数
+        /// </summary>
+        public byte MaxBalancingCylinderDrive => Model.MaxBalancingCylinderDrive;
+        /// <summary>
+        /// 压差通道数
+        /// </summary>
+        public byte MaxDifferentialPressure => Model.MaxDifferentialPressure;
+        /// <summary>
+        /// 压力通道数
+        /// </summary>
+        public byte MaxSupportingPressure => Model.MaxSupportingPressure;
+        /// <summary>
+        /// 六自由度给定位移
+        /// </summary>
+        public byte MaxSixFreedomsGivenDisplacement => Model.MaxSixFreedomsGivenDisplacement;
+        /// <summary>
+        /// 当前位置通道数
+        /// </summary>
+        public byte MaxCurrentLocation => Model.MaxCurrentLocation;
+        /// <summary>
+        /// 实际加速度通道数
+        /// </summary>
+        public byte MaxAcceleration => Model.MaxAcceleration;
+        /// <summary>
+        /// 外部输入通道数
+        /// </summary>
+        public byte MaxOutSignal => Model.MaxOutSignal;
+
+        public IReadOnlyList<byte> MaxResultChannels => maxResultChannels;
+
+        public uint MaxRiseCount => Model.MaxRiseCount;
+        public uint MaxDropCount => Model.MaxDropCount;
+        public uint MaxZeroChangedCount => Model.MaxZeroChangedCount;
+        public uint MaxSignalStopCount => Model.MaxSignalStopCount;
+        public AIChannel MinAIChannel => AIChannel.Channel0;
+        public AIChannel MaxAIChannel => MinAIChannel + MaxAICount;
+        public AOChannel MinAOChannel => AOChannel.Channel0;
+        public AOChannel MaxAOChannel => MinAOChannel + MaxAOCount;
+        public AvaloniaList<AIChannel> AIs { get; } = new AvaloniaList<AIChannel>();
+        public AvaloniaList<AOChannel> AOs { get; } = new AvaloniaList<AOChannel>();
+
+    }
+}

+ 96 - 0
Client/Dynamicloadsimulationdevice/ViewModels/ShakerData/ShakerDataViewModel.cs

@@ -0,0 +1,96 @@
+using IViewModel.Models;
+using IViewModel.ViewModels;
+using OxyPlot;
+using Shaker.Model;
+using Shaker.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Dynamicloadsimulationdevice.ViewModels
+{
+    public sealed class ShakerDataViewModel:ViewModelBase
+    {
+        private object dataLock = new object();
+        private Dictionary<Shaker.Model.ResultChannelType, List<(List<DataPoint> PlotDatas, IViewModel.Models.StatisticsModel Statistics)>> AnalogDataCache = new Dictionary<Shaker.Model.ResultChannelType, List<(List<DataPoint> PlotDatas, IViewModel.Models.StatisticsModel Statistics)>>();
+        private ShakerDataViewModel():base()
+        {
+            GetEvent<AllConfig>().Subscrip((_, args) =>
+            {
+                CommunicationViewModel.Instance.LocalCommunication?.GetEvent(Shaker.Model.Topic.DATA)?.Subscrip((sender, args) =>
+                {
+                    if (args.Data.Length >= 3 && args.Data[0] is byte[] data && args.Data[1] is uint row && args.Data[2] is uint col)
+                    {
+                        ReceiveData(data, row, col);
+                    }
+                });
+            });
+        }
+        private void ReceiveData(byte[] data,uint row,uint col)
+        {
+            int bytecount = Unsafe.SizeOf<float>();
+            if (row == 0 || col == 0 || data.Length != row * col * bytecount) return;
+            lock (dataLock)
+            {
+                var tempdata = Shaker.Tools.Tools.DecompressionBytes(data);
+                AnalogDataCache.Clear();
+                for(int i=0;i<ShakerConfigViewModel.Instance.MaxResultChannels.Count;i++)
+                {
+                    if (i >= row) break;
+                    List<DataPoint> dataPoints = new List<DataPoint>();
+                    float max = float.MinValue;
+                    float min = float.MaxValue;
+                    float v = 0;
+                    for (int j = 0; j < col; j++)
+                    {
+                        float tempv = Unsafe.As<byte, float>(ref tempdata[j * bytecount + i * col * bytecount]);
+                        dataPoints.Add(new DataPoint(1.0 / ShakerConfigViewModel.Instance.SampleRate * j, tempv));
+                        max = tempv > max ? tempv : max;
+                        min = tempv < min ? tempv : min;
+                        v += tempv;
+                    }
+
+
+                    StatisticsModel model = new StatisticsModel()
+                    {
+                        Name = ShakerChannelViewModel.Instance.ResultChannels[i].Value.Name,
+                        Max = max,
+                        Min = min,
+                        RMS = SIMDFxpConvert.SIMDCalc.Instance.Sum.Rms(ref Unsafe.As<byte, float>(ref tempdata[i * bytecount * col]), col),
+                        Average = v / col,
+                        Unit = ShakerChannelViewModel.Instance.ResultChannels[i].Value.Unit,
+                    };
+                    if (AnalogDataCache.TryGetValue(ShakerChannelViewModel.Instance.ResultChannels[i].Value.ChannelType, out var list))
+                    {
+                        if (list == null) list = new List<(List<DataPoint>, StatisticsModel)>();
+                        list.Add((dataPoints, model));
+                    }
+                    else
+                    {
+                        AnalogDataCache[ShakerChannelViewModel.Instance.ResultChannels[i].Value.ChannelType] = new List<(List<DataPoint>, StatisticsModel)> { (dataPoints, model) };
+                    }
+                }
+            }
+        }
+        static ShakerDataViewModel()
+        {
+
+        }
+        public List<(List<DataPoint>, StatisticsModel)> GetAnalogData(ResultChannelType analogType)
+        {
+            lock (dataLock)
+            {
+                if (AnalogDataCache.TryGetValue(analogType, out var list))
+                {
+                    if (list == null) return new List<(List<DataPoint>, StatisticsModel)>();
+                    return list;
+                }
+                return new List<(List<DataPoint>, StatisticsModel)>();
+            }
+        }
+        public static ShakerDataViewModel Instance { get; } = new ShakerDataViewModel();
+    }
+}

+ 41 - 0
Client/Dynamicloadsimulationdevice/ViewModels/ShakerData/StatisticsViewModel.cs

@@ -0,0 +1,41 @@
+using IViewModel;
+using IViewModel.Models;
+using IViewModel.ViewModels;
+using Shaker.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Dynamicloadsimulationdevice.ViewModels
+{
+    public class StatisticsViewModel:ViewModelBase<StatisticsModel>
+    {
+        public StatisticsViewModel():base()
+        {
+
+        }
+        public StatisticsViewModel(StatisticsModel model):base(model)
+        {
+
+        }
+        [PropertyAssociation(nameof(StatisticsModel.Name))]
+        public string Name { get => Model.Name; set =>SetProperty(ref Model.Name , value); }
+        [PropertyAssociation(nameof(StatisticsModel.Max))]
+        public float Max { get => Model.Max; set =>SetProperty(ref Model.Max , value); }
+        [PropertyAssociation(nameof(StatisticsModel.Min))]
+        public float Min { get => Model.Min; set =>SetProperty(ref Model.Min, value); }
+        [PropertyAssociation(nameof(StatisticsModel.RMS))]
+        public float RMS { get => Model.RMS; set =>SetProperty(ref Model.RMS , value); }
+
+        [PropertyAssociation(nameof(StatisticsModel.Average))]
+        public float Average { get => Model.Average; set => SetProperty(ref Model.Average, value); }
+        [PropertyAssociation(nameof(StatisticsModel.Unit))]
+        public string Unit { get => Model.Unit; set =>SetProperty(ref Model.Unit , value); }
+        public override void UpDateModel(StatisticsModel model)
+        {
+            base.UpDateModel(model);
+        }
+    }
+}

+ 227 - 0
Client/Dynamicloadsimulationdevice/ViewModels/SignalPreview/AnalogSignalPreviewViewModel.cs

@@ -0,0 +1,227 @@
+using Avalonia.Collections;
+using Avalonia.Controls;
+using IViewModel.Models;
+using IViewModel.Tools;
+using IViewModel.ViewModels;
+using OxyPlot;
+using OxyPlot.Axes;
+using OxyPlot.Series;
+using Shaker.Model;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Dynamicloadsimulationdevice.ViewModels
+{
+    internal sealed class AnalogSignalPreviewViewModel:DisplayViewModelBase,IDataPreview
+    { 
+        public string AttachTitle 
+        { 
+            get => attachTitle;
+            set
+            {
+                if (attachTitle == value) return;
+                attachTitle = value;
+                UpdatePlotTitle(SelectedAnalog);
+            }
+        }
+        List<OxyPlot.Series.LineSeries> lineSeries = new List<OxyPlot.Series.LineSeries>();
+        private object locker = new object();
+        public AvaloniaList<ViewModels.StatisticsViewModel> Statistics { get; } = new AvaloniaList<StatisticsViewModel>();
+        private ResultChannelType selectedAnalog;
+        private bool canChangedAnalog = true;
+        public bool StatisticsVisibily { get => statisticsVisibily; set =>SetProperty(ref statisticsVisibily , value); }
+        public AnalogSignalPreviewViewModel(bool createcontrol = false):base()
+        {
+            Content = typeof(Views.AnalogSignalPreviewView);
+            if(createcontrol)
+            {
+                Control = (Control)Activator.CreateInstance(Content)!;
+            }
+            PlotModel.Axes.Add(new OxyPlot.Axes.LinearAxis()
+            {
+                MaximumPadding = 0,
+                MinimumPadding = 0,
+                Title = LanguageValueViewModel.Instance.Time,
+                Unit = "s",
+                Key = "X",
+                MajorGridlineStyle = OxyPlot.LineStyle.Solid,
+                Position = OxyPlot.Axes.AxisPosition.Bottom
+            });
+            PlotModel.Axes.Add(new OxyPlot.Axes.LinearAxis()
+            {
+                Title = LanguageValueViewModel.Instance.Ampt,
+                Unit = "V",
+                Key = "Y",
+                MajorGridlineStyle = OxyPlot.LineStyle.Solid,
+                Position = OxyPlot.Axes.AxisPosition.Left,
+            });
+            UpdatePlotTitle(lasttype);
+            PlotModel.Legends.Add(new OxyPlot.Legends.Legend()
+            {
+                ShowInvisibleSeries = true,
+            });
+            PlotController.BindMouseWheel(OxyMouseWheelCommand);
+            GetEvent(Topic.DATA).Subscrip((_, _) =>
+            {
+                if (!UpSignalData) return;
+                lock(locker)
+                {
+                    var data = ShakerDataViewModel.Instance.GetAnalogData(SelectedAnalog);
+                    PlotModel.InvalidatePlot(false);
+                    for(int i=0;i<data.Count;i++)
+                    {
+                        lineSeries[i].ItemsSource = data[i].Item1;
+                        Statistics[i].UpDateModel(data[i].Item2);
+                    }
+                    PlotModel.InvalidatePlot(true);
+                }
+            });
+            GetEvent(Topic.InitSeries).Subscrip((_, _) =>
+            {
+                lock(locker)
+                {
+                    Menu.Clear();
+                    ShakerChannelViewModel.Instance.ResultChannels
+                    .DistinctBy(x=>x.Value.ChannelType)
+                    .ToList()
+                        .ForEach(x =>
+                        {
+                            Menu.Add(new TimeDomainMenuViewModel()
+                            {
+                                IsChecked = false,
+                                IsEnabled = true,
+                                AnalogType = x.Value.ChannelType,
+                                Unit = x.Value.Unit,
+                            });
+                        });
+                    if (CanChangedAnalog)
+                    {
+                        int index = Menu.ToList().FindIndex(x => x.AnalogType == lasttype);
+                        if (index == -1)
+                        {
+                            SelectedAnalog = Menu.First().AnalogType;
+                        }
+                        else
+                        {
+                            SelectedAnalog = lasttype;
+                        }
+                    }
+                    else
+                    {
+                        SelectedAnalog = lasttype;
+                    }
+                }
+            });
+
+
+            GetEvent(IViewModel.ViewModels.LanguageViewModel.LANGUAGECHANGEDEVENT).Subscrip((_, _) =>
+            {
+                PlotModel.InvalidatePlot(false);
+                PlotModel.Axes[0].Title = LanguageValueViewModel.Instance.Time;
+                PlotModel.Axes[1].Title = LanguageValueViewModel.Instance.Ampt;
+                UpdatePlotTitle(SelectedAnalog);
+                var config = ShakerChannelViewModel.Instance.ResultChannels.FirstOrDefault(x => x.Value.ChannelType == SelectedAnalog);
+                if (config == null) return;
+                for(int i=0;i<PlotModel.Series.Count;i++)
+                {
+                    PlotModel.Series[i].Title = LanguageValueViewModel.Instance[config.Value.Name];
+                }
+            });
+        }
+        [AllowNull]
+        public Control Control { get; }
+        private ResultChannelType lasttype =  ResultChannelType.ActualDisplacement;
+        private bool statisticsVisibily = true;
+        private string attachTitle = string.Empty;
+
+        private void UpdatePlotTitle(ResultChannelType analogType)
+        {
+            PlotModel.Title = string.IsNullOrEmpty(AttachTitle) ? LanguageValueViewModel.Instance[analogType.Description()] : $"{LanguageValueViewModel.Instance[AttachTitle]}-{LanguageValueViewModel.Instance[analogType.Description()]}";
+        }
+        public AvaloniaList<TimeDomainMenuViewModel> Menu { get; } = new AvaloniaList<TimeDomainMenuViewModel>();
+        public AnalogSignalPreviewViewModel(ResultChannelType analogType,bool createcontrol = false):this(createcontrol)
+        {
+            lasttype = analogType;
+        }
+        public ResultChannelType SelectedAnalog
+        {
+            get => selectedAnalog;
+            set
+            {
+                SetProperty(ref selectedAnalog, value);
+                ChangeAnalogType(value);
+            }
+        }
+        public bool UpSignalData { get; set; } = true;
+        private void ChangeAnalogType(ResultChannelType type)
+        {
+            lock (locker)
+            {
+                lineSeries.Clear();
+                PlotModel.Series.Clear();
+                Statistics.Clear();
+                for (int i = 0; i < Menu.Count; i++)
+                {
+                    Menu[i].IsChecked = Menu[i].AnalogType == type;
+                }
+                if (ShakerChannelViewModel.Instance.ResultChannels.Count == 0) return;
+                var config = ShakerChannelViewModel.Instance.ResultChannels.FirstOrDefault(x => x.Value.ChannelType == type);
+                if (config == null) return;
+                PlotModel.Axes[1].Unit = config.Value.Unit;
+                var allconfig = ShakerChannelViewModel.Instance.ResultChannels.Where(x => x.Value.ChannelType == type).ToList();
+                for (int i = 0; i < allconfig.Count; i++)
+                {
+                    LineSeries series = new LineSeries();
+                    series.Title = LanguageValueViewModel.Instance[allconfig[i].Value.Name];
+
+                    series.XAxisKey = "X";
+                    series.YAxisKey = "Y";
+                    series.Tag = allconfig[i].Value.Unit;
+                    series.DataFieldX = nameof(DataPoint.X);
+                    series.DataFieldY = nameof(DataPoint.Y);
+                    Statistics.Add(new StatisticsViewModel(new StatisticsModel()
+                    {
+                        Name = allconfig[i].Value.Name,
+                    }));
+                    lineSeries.Add(series);
+                }
+                PlotModel.InvalidatePlot(false);
+                UpdatePlotTitle(type);
+                lineSeries.ForEach(x => PlotModel.Series.Add(x));
+                PlotModel.InvalidatePlot(true);
+            }
+        }
+        public bool CanChangedAnalog
+        {
+            get => canChangedAnalog;
+            set =>SetProperty(ref canChangedAnalog , value); 
+        }
+       
+        public OxyPlot.PlotModel PlotModel { get; } = new OxyPlot.PlotModel();
+        public OxyPlot.PlotController PlotController { get; } = new OxyPlot.PlotController();
+        public IViewCommand<OxyMouseWheelEventArgs> OxyMouseWheelCommand => new DelegatePlotCommand<OxyMouseWheelEventArgs>(OnOxyMouseWheel);
+        private void OnOxyMouseWheel(IPlotView view, IController controller, OxyMouseWheelEventArgs args)
+        {
+            HandleZoomByWheel(view, args);
+            if (view.ActualModel is PlotModel plotModel && plotModel.Axes.Count >= 1 && plotModel.Axes[0] is LinearAxis axis)
+            {
+                axis.MajorStep = (axis.Maximum - axis.Minimum) / 10;
+            }
+        }
+        private void HandleZoomByWheel(IPlotView view, OxyMouseWheelEventArgs args, double factor = 1)
+        {
+            var m = new ZoomStepManipulator(view)
+            {
+                AxisPreference = AxisPreference.X,
+                Step = args.Delta * 0.001 * factor,
+                FineControl = args.IsControlDown,
+            };
+            m.Started(args);
+        }
+
+    }
+}

+ 16 - 0
Client/Dynamicloadsimulationdevice/ViewModels/SignalPreview/IDataPreview.cs

@@ -0,0 +1,16 @@
+using CommunityToolkit.Mvvm.Input;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace Dynamicloadsimulationdevice.ViewModels
+{
+    internal interface IDataPreview
+    {
+        public bool UpSignalData { get; set; }
+        public ICommand WindowCloseCommand => new RelayCommand(() => UpSignalData = false);
+    }
+}

+ 145 - 0
Client/Dynamicloadsimulationdevice/ViewModels/SignalPreview/SignalPreviewViewModel.cs

@@ -0,0 +1,145 @@
+using Avalonia.Collections;
+using CommunityToolkit.Mvvm.Input;
+using IViewModel.ViewModels;
+using Shaker.Model;
+using Shaker.Models;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace Dynamicloadsimulationdevice.ViewModels
+{
+    internal sealed class SignalPreviewViewModel:DisplayViewModelBase, IDataPreview
+    {
+        public override bool AppendSeparator => true;
+        public override string IconKey => nameof(IconResourceValueViewModel.SignalViewGeometry);
+        public override string MenuKey => nameof(LanguageValueViewModel.SignalPreview);
+        public override string MenuParentKey => nameof(LanguageValueViewModel.MenuDevice);
+        public AvaloniaList<AnalogSignalPreviewViewModel> SignalPreviews { get; } = new AvaloniaList<AnalogSignalPreviewViewModel>();
+        private bool upSignalData;
+        private int rowCount = 2;
+        private int columnCount = 2;
+        private bool showLayout = false;
+
+        private SignalPreviewViewModel()
+        {
+            Title = nameof(LanguageValueViewModel.SignalPreview);
+            this.ButtonVisibily = false;
+            Content = typeof(Views.SignalPreviewView);
+            Enum.GetValues<ResultChannelType>()
+                .Where(x => x != ResultChannelType.None)
+                .ToList()
+                .ForEach(x =>
+                {
+                    SignalPreviews.Add(new AnalogSignalPreviewViewModel(x));
+                });
+            timer.AutoReset = false;
+            timer.Elapsed += (_, _) =>
+            {
+                ShowLayout = true;
+            };
+            timer.Enabled = false;
+            timer.Stop();
+        }
+        static SignalPreviewViewModel()
+        {
+
+        }
+
+        public ICommand OneCommnad => new RelayCommand(One);
+        private void One()
+        {
+            RowCount = 1;
+            ColumnCount = 1;
+            ShowLayout = false;
+        }
+        public ICommand OneTwoCommand=>new RelayCommand(OneTwo);
+        private void OneTwo()
+        {
+            RowCount = 1;
+            ColumnCount = 2;
+            ShowLayout = false;
+        }
+        public ICommand TwoOneCommand=>new RelayCommand(TwoOne);
+        private void TwoOne()
+        {
+            RowCount = 2;
+            ColumnCount = 1;
+            ShowLayout = false;
+        }
+        public ICommand TwoTwoCommand=>new RelayCommand(TwoTwo);
+        private void TwoTwo()
+        {
+            RowCount = 2;
+            ColumnCount = 2;
+            ShowLayout = false;
+        }
+        public bool ShowLayout { get => showLayout; set =>SetProperty(ref showLayout , value); }
+
+        private System.Timers.Timer timer = new System.Timers.Timer(1000);
+        private bool buttonPointerOver;
+        private bool panelPointerOver;
+
+        public ICommand PointerExitedCommand=>new RelayCommand(PointerExited);
+        private void PointerExited()
+        {
+            ButtonPointerOver = false;
+            timer.Stop();
+        }
+        public bool ButtonPointerOver
+        {
+            get => buttonPointerOver;
+            set
+            {
+                SetProperty(ref buttonPointerOver, value);
+                ShowLayout = ShowLayout ? PanelPointerOver : (ButtonPointerOver || PanelPointerOver);
+            }
+        }
+        public bool PanelPointerOver 
+        {
+            get => panelPointerOver;
+            set
+            {
+                SetProperty(ref panelPointerOver, value);
+                ShowLayout = ShowLayout ? PanelPointerOver : (ButtonPointerOver || PanelPointerOver);
+            }
+        }
+        public ICommand ControlPointerEnteredCommand=>new RelayCommand(ControlPointerEntered);
+        private void ControlPointerEntered()
+        {
+            PanelPointerOver = true;
+        }
+        public ICommand ControlPointerExitedCommand => new RelayCommand(ControlPointerExited);
+        private void ControlPointerExited()
+        {
+            PanelPointerOver = false;
+        }
+        public ICommand PointerEnteredCommand=>new RelayCommand(PointerEntered);
+        private void PointerEntered()
+        {
+            ButtonPointerOver = true;
+            ShowLayout = false;
+            timer.Start();
+        }
+        public int RowCount { get => rowCount; set =>SetProperty(ref rowCount , value); }
+        public int ColumnCount { get => columnCount; set =>SetProperty(ref columnCount , value); }
+
+        public static SignalPreviewViewModel Instance { get; } = new SignalPreviewViewModel();
+        public bool UpSignalData 
+        { 
+            get => upSignalData;
+            set
+            {
+                upSignalData = value;
+                foreach (var item in SignalPreviews)
+                {
+                    item.UpSignalData = value;
+                }
+            }
+        }
+    }
+}

+ 21 - 0
Client/Dynamicloadsimulationdevice/ViewModels/SignalPreview/TimeDomainMenuViewModel.cs

@@ -0,0 +1,21 @@
+using IViewModel.Tools;
+using Shaker.Model;
+using Shaker.Models;
+
+namespace Dynamicloadsimulationdevice.ViewModels
+{
+    public sealed class TimeDomainMenuViewModel: CommunityToolkit.Mvvm.ComponentModel.ObservableObject
+    {
+        private ResultChannelType analogType =  ResultChannelType.ActualDisplacement;
+        private bool isEnabled = true;
+        private string unit = string.Empty;
+        private bool isChecked = false;
+
+        public ResultChannelType AnalogType { get => analogType; set =>SetProperty(ref analogType , value); }
+        public bool IsEnabled { get => isEnabled; set =>SetProperty(ref isEnabled , value); }
+        public string Key => AnalogType.Description();
+        public string Unit { get => unit; set =>SetProperty(ref unit , value); }
+        public bool IsChecked { get => isChecked; set =>SetProperty(ref isChecked , value); }
+
+    }
+}

+ 110 - 0
Client/Dynamicloadsimulationdevice/Views/PlotConfig/PlotConfigView.axaml

@@ -0,0 +1,110 @@
+<UserControl
+    x:Class="Dynamicloadsimulationdevice.Views.PlotConfigView"
+    xmlns="https://github.com/avaloniaui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:vm="using:Dynamicloadsimulationdevice.ViewModels"
+    d:DesignHeight="600"
+    d:DesignWidth="750"
+    x:DataType="vm:PlotConfigViewModel"
+    DataContext="{Binding Source={x:Static vm:PlotConfigViewModel.Instance}}"
+    mc:Ignorable="d">
+    <Grid>
+        <StackPanel IsVisible="{Binding !NoSeries}" Orientation="Vertical">
+            <Grid
+                Height="42"
+                Background="#3FAAAAAA"
+                ColumnDefinitions="200,150,200,*,80">
+                <TextBlock
+                    HorizontalAlignment="Center"
+                    VerticalAlignment="Center"
+                    FontWeight="Bold"
+                    Text="{DynamicResource PlotName}" />
+                <TextBlock
+                    Grid.Column="1"
+                    HorizontalAlignment="Center"
+                    VerticalAlignment="Center"
+                    FontWeight="Bold"
+                    Text="{DynamicResource PlotColor}" />
+                <TextBlock
+                    Grid.Column="2"
+                    HorizontalAlignment="Center"
+                    VerticalAlignment="Center"
+                    FontWeight="Bold"
+                    Text="{DynamicResource PlotLineStyle}" />
+                <TextBlock
+                    Grid.Column="3"
+                    HorizontalAlignment="Center"
+                    VerticalAlignment="Center"
+                    FontWeight="Bold"
+                    Text="{DynamicResource PlotMarkerType}" />
+                <TextBlock
+                    Grid.Column="4"
+                    HorizontalAlignment="Center"
+                    VerticalAlignment="Center"
+                    FontWeight="Bold"
+                    Text="{DynamicResource PlotStrokeThickness}" />
+            </Grid>
+            <ScrollViewer Height="450">
+                <ItemsControl ItemsSource="{Binding SeriesConfigs}">
+                    <ItemsControl.ItemTemplate>
+                        <DataTemplate>
+                            <Grid
+                                Height="42"
+                                x:DataType="vm:SeriesConfigViewModel"
+                                ColumnDefinitions="200,150,200,*,80">
+                                <TextBlock
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Center"
+                                    Text="{Binding Name}" />
+                                <ColorPicker
+                                    Grid.Column="1"
+                                    Width="130"
+                                    Margin="10,0,10,0"
+                                    Color="{Binding Color}" />
+                                <ComboBox
+                                    Grid.Column="2"
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Center"
+                                    HorizontalContentAlignment="Center"
+                                    VerticalContentAlignment="Center"
+                                    DisplayMemberBinding="{Binding Key}"
+                                    ItemsSource="{Binding LineStyle, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}"
+                                    SelectedValue="{Binding LineStyle}"
+                                    SelectedValueBinding="{Binding Value}" />
+
+                                <ComboBox
+                                    Grid.Column="3"
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Center"
+                                    HorizontalContentAlignment="Center"
+                                    VerticalContentAlignment="Center"
+                                    DisplayMemberBinding="{Binding Key}"
+                                    ItemsSource="{Binding MarkerType, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}"
+                                    SelectedValue="{Binding MarkerType}"
+                                    SelectedValueBinding="{Binding Value}" />
+                                <NumericUpDown
+                                    Grid.Column="4"
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Center"
+                                    HorizontalContentAlignment="Center"
+                                    VerticalContentAlignment="Center"
+                                    Maximum="10"
+                                    Minimum="1"
+                                    Value="{Binding StrokeThickness}" />
+                            </Grid>
+                        </DataTemplate>
+                    </ItemsControl.ItemTemplate>
+                </ItemsControl>
+
+            </ScrollViewer>
+        </StackPanel>
+        <TextBlock
+            HorizontalAlignment="Center"
+            VerticalAlignment="Center"
+            FontSize="42"
+            IsVisible="{Binding NoSeries}"
+            Text="{DynamicResource NoSeries}" />
+    </Grid>
+</UserControl>

+ 13 - 0
Client/Dynamicloadsimulationdevice/Views/PlotConfig/PlotConfigView.axaml.cs

@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Dynamicloadsimulationdevice.Views;
+
+public partial class PlotConfigView : UserControl
+{
+    public PlotConfigView()
+    {
+        InitializeComponent();
+    }
+}

+ 210 - 0
Client/Dynamicloadsimulationdevice/Views/ServoConfig/ServoConfigView.axaml

@@ -0,0 +1,210 @@
+<UserControl
+    x:Class="Dynamicloadsimulationdevice.Views.ServoConfigView"
+    xmlns="https://github.com/avaloniaui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:ivm="using:IViewModel.Convert"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:suki="https://github.com/kikipoulet/SukiUI"
+    xmlns:vm="using:Dynamicloadsimulationdevice.ViewModels"
+    d:DesignHeight="520"
+    d:DesignWidth="780"
+    x:DataType="vm:ServoConfigViewModel"
+    DataContext="{Binding Source={x:Static vm:ServoConfigViewModel.Instance}}"
+    mc:Ignorable="d">
+    <Grid
+        HorizontalAlignment="Center"
+        VerticalAlignment="Top"
+        ColumnDefinitions="auto,auto">
+        <suki:GlassCard
+            Width="420"
+            Margin="10"
+            HorizontalAlignment="Left"
+            VerticalAlignment="Top">
+            <suki:GroupBox Header="{DynamicResource DisplacementPI}">
+                <Grid RowDefinitions="*,auto">
+                    <Grid RowDefinitions="auto,*">
+                        <Grid ColumnDefinitions="62,*,*">
+                            <TextBlock
+                                HorizontalAlignment="Center"
+                                VerticalAlignment="Center"
+                                Text="{DynamicResource ServoValveIndex}" />
+                            <TextBlock
+                                Grid.Column="1"
+                                HorizontalAlignment="Center"
+                                VerticalAlignment="Center"
+                                Text="{DynamicResource DisplacementP}" />
+                            <TextBlock
+                                Grid.Column="2"
+                                HorizontalAlignment="Center"
+                                VerticalAlignment="Center"
+                                Text="{DynamicResource DisplacementI}" />
+                        </Grid>
+                        <ScrollViewer Grid.Row="1">
+                            <ItemsControl ItemsSource="{Binding PI}">
+                                <ItemsControl.ItemTemplate>
+                                    <DataTemplate>
+                                        <Grid ColumnDefinitions="62,*,*">
+                                            <TextBlock
+                                                HorizontalAlignment="Center"
+                                                VerticalAlignment="Center"
+                                                Text="{Binding Index}" />
+                                            <NumericUpDown
+                                                Grid.Column="1"
+                                                Margin="4"
+                                                Increment="0.001"
+                                                Maximum="{Binding Value.MaxP}"
+                                                Minimum="{Binding Value.MinP}"
+                                                Value="{Binding Value.P}" />
+                                            <NumericUpDown
+                                                Grid.Column="2"
+                                                Margin="4"
+                                                Increment="0.001"
+                                                Maximum="{Binding Value.MaxI}"
+                                                Minimum="{Binding Value.MinI}"
+                                                Value="{Binding Value.I}" />
+                                        </Grid>
+                                    </DataTemplate>
+                                </ItemsControl.ItemTemplate>
+                            </ItemsControl>
+                        </ScrollViewer>
+                    </Grid>
+                    <StackPanel Grid.Row="1" Orientation="Horizontal">
+                        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
+                            <Run Text="{DynamicResource MaxDisplacementIntegral}" />
+                            <Run Text="(V)" />
+                        </TextBlock>
+                        <NumericUpDown
+                            Width="120"
+                            Margin="4,0,0,0"
+                            Increment="0.001"
+                            Maximum="{Binding MaxMaxIntegratedVoltage}"
+                            Minimum="{Binding MinMaxIntegratedVoltage}"
+                            Value="{Binding MaxIntegratedVoltage}" />
+                    </StackPanel>
+                </Grid>
+            </suki:GroupBox>
+        </suki:GlassCard>
+        <StackPanel
+            Grid.Column="1"
+            VerticalAlignment="Top"
+            Orientation="Vertical">
+            <suki:GlassCard
+                Width="320"
+                Margin="0,10,10,10"
+                HorizontalAlignment="Left"
+                VerticalAlignment="Top">
+                <suki:GroupBox Header="{DynamicResource DisplacementFeedforwardGain}">
+                    <StackPanel VerticalAlignment="Top" Orientation="Horizontal">
+                        <TextBlock
+                            HorizontalAlignment="Center"
+                            VerticalAlignment="Center"
+                            Text="{DynamicResource DisplacementFeedforwardGain}" />
+                        <NumericUpDown
+                            Width="120"
+                            Margin="4,0,0,0"
+                            Increment="0.001"
+                            Maximum="{Binding MaxDisplacementFeedforwardGain}"
+                            Minimum="{Binding MinDisplacementFeedforwardGain}"
+                            Value="{Binding DisplacementFeedforwardGain}" />
+                    </StackPanel>
+                </suki:GroupBox>
+            </suki:GlassCard>
+
+            <suki:GlassCard
+                Width="320"
+                Margin="0,0,10,10"
+                HorizontalAlignment="Left"
+                VerticalAlignment="Top">
+                <suki:GroupBox Header="{DynamicResource BarrierPotential}">
+                    <StackPanel>
+
+                        <StackPanel VerticalAlignment="Top" Orientation="Horizontal">
+                            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
+                                <Run Text="{DynamicResource VerticalBarrierPotential}" />
+                                <Run Text="(V)" />
+                            </TextBlock>
+                            <NumericUpDown
+                                Width="120"
+                                Margin="4,0,0,0"
+                                Increment="0.001"
+                                Maximum="{Binding MaxVerticalBarrierPotential}"
+                                Minimum="{Binding MinVerticalBarrierPotential}"
+                                Value="{Binding VerticalBarrierPotential}" />
+                        </StackPanel>
+
+                        <StackPanel
+                            Margin="0,4,0,0"
+                            VerticalAlignment="Top"
+                            Orientation="Horizontal">
+                            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
+                                <Run Text="{DynamicResource HorizontalBarrierPotential}" />
+                                <Run Text="(V)" />
+                            </TextBlock>
+                            <NumericUpDown
+                                Width="120"
+                                Margin="4,0,0,0"
+                                Increment="0.001"
+                                Maximum="{Binding MaxHorizontalBarrierPotential}"
+                                Minimum="{Binding MinHorizontalBarrierPotential}"
+                                Value="{Binding HorizontalBarrierPotential}" />
+                        </StackPanel>
+                    </StackPanel>
+                </suki:GroupBox>
+            </suki:GlassCard>
+
+            <suki:GlassCard
+                Grid.Row="1"
+                Grid.Column="1"
+                Width="320"
+                Margin="0,0,10,10"
+                HorizontalAlignment="Left"
+                VerticalAlignment="Top">
+                <suki:GroupBox Header="{DynamicResource SecurityConfig}">
+                    <StackPanel>
+                        <StackPanel Orientation="Horizontal">
+                            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
+                                <Run Text="{DynamicResource MaxDriver}" />
+                                <Run Text="(V)" />
+                            </TextBlock>
+                            <NumericUpDown
+                                Width="120"
+                                Margin="4,0,0,0"
+                                Increment="0.001"
+                                Maximum="{Binding MaxMaxDriverVoltage}"
+                                Minimum="{Binding MinMaxDriverVoltage}"
+                                Value="{Binding MaxDriverVoltage}" />
+                        </StackPanel>
+                        <StackPanel Margin="0,4,0,0" Orientation="Horizontal">
+                            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
+                                <Run Text="{DynamicResource DriverOverLimitVoltage}" />
+                                <Run Text="(V)" />
+                            </TextBlock>
+                            <NumericUpDown
+                                Width="120"
+                                Margin="4,0,0,0"
+                                Increment="0.001"
+                                Maximum="{Binding MaxDriverOverLimitVoltage}"
+                                Minimum="{Binding MinDriverOverLimitVoltage}"
+                                Value="{Binding DriverOverLimitVoltage}" />
+                        </StackPanel>
+                        <StackPanel Margin="0,4,0,0" Orientation="Horizontal">
+                            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
+                                <Run Text="{DynamicResource EmerhencyDriverLimitVoltage}" />
+                                <Run Text="(V)" />
+                            </TextBlock>
+                            <NumericUpDown
+                                Width="120"
+                                Margin="4,0,0,0"
+                                Increment="0.001"
+                                Maximum="{Binding MaxEmerhencyDriverLimitVoltage}"
+                                Minimum="{Binding MinEmerhencyDriverLimitVoltage}"
+                                Value="{Binding EmerhencyDriverLimitVoltage}" />
+                        </StackPanel>
+                    </StackPanel>
+                </suki:GroupBox>
+            </suki:GlassCard>
+        </StackPanel>
+
+    </Grid>
+</UserControl>

+ 13 - 0
Client/Dynamicloadsimulationdevice/Views/ServoConfig/ServoConfigView.axaml.cs

@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Dynamicloadsimulationdevice.Views;
+
+public partial class ServoConfigView : UserControl
+{
+    public ServoConfigView()
+    {
+        InitializeComponent();
+    }
+}

+ 655 - 0
Client/Dynamicloadsimulationdevice/Views/ShakerChannel/ShakerChannelView.axaml

@@ -0,0 +1,655 @@
+<UserControl
+    x:Class="Dynamicloadsimulationdevice.Views.ShakerChannelView"
+    xmlns="https://github.com/avaloniaui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:convert="using:IViewModel.Convert"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:ivm="using:IViewModel"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:shakermodel="using:Shaker.Model"
+    xmlns:suki="https://github.com/kikipoulet/SukiUI"
+    xmlns:vm="using:Dynamicloadsimulationdevice.ViewModels"
+    d:DesignHeight="450"
+    d:DesignWidth="800"
+    x:DataType="vm:ShakerChannelViewModel"
+    DataContext="{Binding Source={x:Static vm:ShakerChannelViewModel.Instance}}"
+    mc:Ignorable="d">
+    <TabControl>
+        <TabItem Header="{DynamicResource AI}">
+            <ScrollViewer>
+
+                <UniformGrid Columns="2">
+                    <suki:GlassCard Margin="10">
+                        <suki:GroupBox Header="{DynamicResource Displacement}">
+                            <StackPanel
+                                HorizontalAlignment="Left"
+                                VerticalAlignment="Top"
+                                Orientation="Vertical">
+                                <Grid
+                                    Width="560"
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Top"
+                                    ColumnDefinitions="62,*,0.8*,0.8*">
+                                    <TextBlock
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{DynamicResource ServoValveIndex}" />
+                                    <TextBlock
+                                        Grid.Column="1"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{DynamicResource Channel}" />
+                                    <TextBlock
+                                        Grid.Column="2"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center">
+                                        <Run Text="{DynamicResource Sensitivity}" />
+                                        <Run Text="{Binding Displacement[0].Value.Unit, StringFormat='{}(mV/{0})', Mode=OneWay}" />
+                                    </TextBlock>
+                                    <TextBlock
+                                        Grid.Column="3"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center">
+                                        <Run Text="{DynamicResource Bias}" />
+                                        <Run Text="{Binding Displacement[0].Value.Unit, StringFormat='{}({0})', Mode=OneWay}" />
+                                    </TextBlock>
+                                </Grid>
+                                <ScrollViewer>
+                                    <ItemsControl ItemsSource="{Binding Displacement}">
+                                        <ItemsControl.ItemTemplate>
+                                            <DataTemplate>
+                                                <Grid
+                                                    Width="560"
+                                                    HorizontalAlignment="Center"
+                                                    VerticalAlignment="Top"
+                                                    ColumnDefinitions="62,*,0.8*,0.8*">
+                                                    <TextBlock
+                                                        HorizontalAlignment="Center"
+                                                        VerticalAlignment="Center"
+                                                        Text="{Binding Index}" />
+                                                    <ComboBox
+                                                        Grid.Column="1"
+                                                        ItemsSource="{Binding Source={x:Static vm:ShakerConfigViewModel.Instance}, Path=AIs}"
+                                                        SelectedValue="{Binding Value.Channel}" />
+                                                    <NumericUpDown
+                                                        Grid.Column="2"
+                                                        Increment="0.001"
+                                                        Maximum="{Binding Value.MaxSensitivity}"
+                                                        Minimum="{Binding Value.MinSensitivity}"
+                                                        Value="{Binding Value.Sensitivity}" />
+                                                    <NumericUpDown
+                                                        Grid.Column="3"
+                                                        Margin="4,0,4,0"
+                                                        Increment="0.001"
+                                                        Maximum="{Binding Value.MaxBias}"
+                                                        Minimum="{Binding Value.MinBias}"
+                                                        Value="{Binding Value.Bias}" />
+                                                </Grid>
+                                            </DataTemplate>
+                                        </ItemsControl.ItemTemplate>
+                                    </ItemsControl>
+                                </ScrollViewer>
+                            </StackPanel>
+                        </suki:GroupBox>
+                    </suki:GlassCard>
+
+
+                    <StackPanel>
+
+                        <suki:GlassCard Margin="10">
+                            <suki:GroupBox Header="{DynamicResource Acceleration}">
+                                <StackPanel
+                                    HorizontalAlignment="Left"
+                                    VerticalAlignment="Top"
+                                    Orientation="Vertical">
+                                    <Grid
+                                        Width="460"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Top"
+                                        ColumnDefinitions="62,*,0.8*">
+                                        <TextBlock
+                                            HorizontalAlignment="Center"
+                                            VerticalAlignment="Center"
+                                            Text="{DynamicResource ServoValveIndex}" />
+                                        <TextBlock
+                                            Grid.Column="1"
+                                            HorizontalAlignment="Center"
+                                            VerticalAlignment="Center"
+                                            Text="{DynamicResource Channel}" />
+                                        <TextBlock
+                                            Grid.Column="2"
+                                            HorizontalAlignment="Center"
+                                            VerticalAlignment="Center">
+                                            <Run Text="{DynamicResource Sensitivity}" />
+                                            <Run Text="{Binding Acceleration[0].Value.Unit, StringFormat='{}(mV/{0})', Mode=OneWay}" />
+                                        </TextBlock>
+                                    </Grid>
+                                    <ScrollViewer>
+                                        <ItemsControl ItemsSource="{Binding Acceleration}">
+                                            <ItemsControl.ItemTemplate>
+                                                <DataTemplate>
+                                                    <Grid
+                                                        Width="460"
+                                                        HorizontalAlignment="Center"
+                                                        VerticalAlignment="Top"
+                                                        ColumnDefinitions="62,*,0.8*">
+                                                        <TextBlock
+                                                            HorizontalAlignment="Center"
+                                                            VerticalAlignment="Center"
+                                                            Text="{Binding Index}" />
+                                                        <ComboBox
+                                                            Grid.Column="1"
+                                                            ItemsSource="{Binding Source={x:Static vm:ShakerConfigViewModel.Instance}, Path=AIs}"
+                                                            SelectedValue="{Binding Value.Channel}" />
+                                                        <NumericUpDown
+                                                            Grid.Column="2"
+                                                            Increment="0.001"
+                                                            Maximum="{Binding Value.MaxSensitivity}"
+                                                            Minimum="{Binding Value.MinSensitivity}"
+                                                            Value="{Binding Value.Sensitivity}" />
+                                                    </Grid>
+                                                </DataTemplate>
+                                            </ItemsControl.ItemTemplate>
+                                        </ItemsControl>
+                                    </ScrollViewer>
+                                </StackPanel>
+                            </suki:GroupBox>
+                        </suki:GlassCard>
+
+
+
+                        <suki:GlassCard Margin="10">
+                            <suki:GroupBox Header="{DynamicResource OutInputSignal}">
+                                <StackPanel
+                                    HorizontalAlignment="Left"
+                                    VerticalAlignment="Top"
+                                    Orientation="Vertical">
+                                    <Grid
+                                        Width="460"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Top"
+                                        ColumnDefinitions="62,*,0.8*">
+                                        <TextBlock
+                                            HorizontalAlignment="Center"
+                                            VerticalAlignment="Center"
+                                            Text="{DynamicResource ServoValveIndex}" />
+                                        <TextBlock
+                                            Grid.Column="1"
+                                            HorizontalAlignment="Center"
+                                            VerticalAlignment="Center"
+                                            Text="{DynamicResource Channel}" />
+                                        <TextBlock
+                                            Grid.Column="2"
+                                            HorizontalAlignment="Center"
+                                            VerticalAlignment="Center">
+                                            <Run Text="{DynamicResource OutSignalGain}" />
+                                            <Run Text="{Binding OutSignal[0].Value.Unit, StringFormat='{}({0}/mV)', Mode=OneWay}" />
+                                        </TextBlock>
+                                    </Grid>
+                                    <ScrollViewer>
+                                        <ItemsControl ItemsSource="{Binding OutSignal}">
+                                            <ItemsControl.ItemTemplate>
+                                                <DataTemplate>
+                                                    <Grid
+                                                        Width="460"
+                                                        HorizontalAlignment="Center"
+                                                        VerticalAlignment="Top"
+                                                        ColumnDefinitions="62,*,0.8*">
+                                                        <TextBlock
+                                                            HorizontalAlignment="Center"
+                                                            VerticalAlignment="Center"
+                                                            Text="{Binding Index}" />
+                                                        <ComboBox
+                                                            Grid.Column="1"
+                                                            ItemsSource="{Binding Source={x:Static vm:ShakerConfigViewModel.Instance}, Path=AIs}"
+                                                            SelectedValue="{Binding Value.Channel}" />
+                                                        <NumericUpDown
+                                                            Grid.Column="2"
+                                                            Increment="0.001"
+                                                            Maximum="{Binding Value.MaxSensitivity}"
+                                                            Minimum="{Binding Value.MinSensitivity}"
+                                                            Value="{Binding Value.Sensitivity}" />
+                                                    </Grid>
+                                                </DataTemplate>
+                                            </ItemsControl.ItemTemplate>
+                                        </ItemsControl>
+                                    </ScrollViewer>
+                                </StackPanel>
+                            </suki:GroupBox>
+                        </suki:GlassCard>
+
+                    </StackPanel>
+
+                    <suki:GlassCard Margin="10">
+                        <suki:GroupBox Header="{DynamicResource ValvePressure}">
+                            <StackPanel
+                                HorizontalAlignment="Left"
+                                VerticalAlignment="Top"
+                                Orientation="Vertical">
+                                <Grid
+                                    Width="560"
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Top"
+                                    ColumnDefinitions="62,*,0.8*,0.8*">
+                                    <TextBlock
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{DynamicResource ServoValveIndex}" />
+                                    <TextBlock
+                                        Grid.Column="1"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{DynamicResource Channel}" />
+                                    <TextBlock
+                                        Grid.Column="2"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center">
+                                        <Run Text="{DynamicResource Sensitivity}" />
+                                        <Run Text="{Binding Pressure[0].Value.Unit, StringFormat='{}(mV/{0})', Mode=OneWay}" />
+                                    </TextBlock>
+                                    <TextBlock
+                                        Grid.Column="3"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center">
+                                        <Run Text="{DynamicResource Bias}" />
+                                        <Run Text="{Binding Pressure[0].Value.Unit, StringFormat='{}({0})', Mode=OneWay}" />
+                                    </TextBlock>
+                                </Grid>
+                                <ScrollViewer>
+                                    <ItemsControl ItemsSource="{Binding Pressure}">
+                                        <ItemsControl.ItemTemplate>
+                                            <DataTemplate>
+                                                <Grid
+                                                    Width="560"
+                                                    HorizontalAlignment="Center"
+                                                    VerticalAlignment="Top"
+                                                    ColumnDefinitions="62,*,0.8*,0.8*">
+                                                    <TextBlock
+                                                        HorizontalAlignment="Center"
+                                                        VerticalAlignment="Center"
+                                                        Text="{Binding Index}" />
+                                                    <ComboBox
+                                                        Grid.Column="1"
+                                                        ItemsSource="{Binding Source={x:Static vm:ShakerConfigViewModel.Instance}, Path=AIs}"
+                                                        SelectedValue="{Binding Value.Channel}" />
+                                                    <NumericUpDown
+                                                        Grid.Column="2"
+                                                        Increment="0.001"
+                                                        Maximum="{Binding Value.MaxSensitivity}"
+                                                        Minimum="{Binding Value.MinSensitivity}"
+                                                        Value="{Binding Value.Sensitivity}" />
+                                                    <NumericUpDown
+                                                        Grid.Column="3"
+                                                        Margin="4,0,4,0"
+                                                        Increment="0.001"
+                                                        Maximum="{Binding Value.MaxBias}"
+                                                        Minimum="{Binding Value.MinBias}"
+                                                        Value="{Binding Value.Bias}" />
+                                                </Grid>
+                                            </DataTemplate>
+                                        </ItemsControl.ItemTemplate>
+                                    </ItemsControl>
+                                </ScrollViewer>
+                            </StackPanel>
+                        </suki:GroupBox>
+                    </suki:GlassCard>
+
+
+
+                    <suki:GlassCard Margin="10">
+                        <suki:GroupBox Header="{DynamicResource DifferentialPressure}">
+                            <StackPanel
+                                HorizontalAlignment="Left"
+                                VerticalAlignment="Top"
+                                Orientation="Vertical">
+                                <Grid
+                                    Width="560"
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Top"
+                                    ColumnDefinitions="62,*,0.8*,0.8*">
+                                    <TextBlock
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{DynamicResource ServoValveIndex}" />
+                                    <TextBlock
+                                        Grid.Column="1"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{DynamicResource Channel}" />
+                                    <TextBlock
+                                        Grid.Column="2"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center">
+                                        <Run Text="{DynamicResource Sensitivity}" />
+                                        <Run Text="{Binding DifferentialPressure[0].Value.Unit, StringFormat='{}(mV/{0})', Mode=OneWay}" />
+                                    </TextBlock>
+                                    <TextBlock
+                                        Grid.Column="3"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center">
+                                        <Run Text="{DynamicResource Bias}" />
+                                        <Run Text="{Binding DifferentialPressure[0].Value.Unit, StringFormat='{}(mV/{0})', Mode=OneWay}" />
+                                    </TextBlock>
+                                </Grid>
+                                <ScrollViewer>
+                                    <ItemsControl ItemsSource="{Binding DifferentialPressure}">
+                                        <ItemsControl.ItemTemplate>
+                                            <DataTemplate>
+                                                <Grid
+                                                    Width="560"
+                                                    HorizontalAlignment="Center"
+                                                    VerticalAlignment="Top"
+                                                    ColumnDefinitions="62,*,0.8*,0.8*">
+                                                    <TextBlock
+                                                        HorizontalAlignment="Center"
+                                                        VerticalAlignment="Center"
+                                                        Text="{Binding Index}" />
+                                                    <ComboBox
+                                                        Grid.Column="1"
+                                                        ItemsSource="{Binding Source={x:Static vm:ShakerConfigViewModel.Instance}, Path=AIs}"
+                                                        SelectedValue="{Binding Value.Channel}" />
+                                                    <NumericUpDown
+                                                        Grid.Column="2"
+                                                        Increment="0.001"
+                                                        Maximum="{Binding Value.MaxSensitivity}"
+                                                        Minimum="{Binding Value.MinSensitivity}"
+                                                        Value="{Binding Value.Sensitivity}" />
+                                                    <NumericUpDown
+                                                        Grid.Column="3"
+                                                        Margin="4,0,4,0"
+                                                        Increment="0.001"
+                                                        Maximum="{Binding Value.MaxBias}"
+                                                        Minimum="{Binding Value.MinBias}"
+                                                        Value="{Binding Value.Bias}" />
+                                                </Grid>
+                                            </DataTemplate>
+                                        </ItemsControl.ItemTemplate>
+                                    </ItemsControl>
+                                </ScrollViewer>
+                            </StackPanel>
+                        </suki:GroupBox>
+                    </suki:GlassCard>
+                </UniformGrid>
+            </ScrollViewer>
+        </TabItem>
+        <TabItem Header="{DynamicResource AO}">
+            <ScrollViewer>
+                <Grid ColumnDefinitions="*,auto" RowDefinitions="*,*">
+
+                    <suki:GlassCard
+                        Grid.Row="0"
+                        Grid.Column="0"
+                        Margin="10">
+                        <suki:GroupBox Header="{DynamicResource Vertical}">
+                            <StackPanel
+                                Width="620"
+                                HorizontalAlignment="Left"
+                                VerticalAlignment="Top"
+                                Orientation="Vertical">
+                                <Grid
+                                    Width="{Binding $parent[StackPanel].Width}"
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Top"
+                                    ColumnDefinitions="62,140,120,0.5*,0.3*,0.5*">
+                                    <TextBlock
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{DynamicResource ServoValveIndex}" />
+                                    <TextBlock
+                                        Grid.Column="1"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{DynamicResource Channel}" />
+                                    <TextBlock
+                                        Grid.Column="2"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center">
+                                        <Run Text="{DynamicResource ServoValvePolarity}" />
+                                    </TextBlock>
+                                    <TextBlock
+                                        Grid.Column="3"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center">
+                                        <Run Text="{DynamicResource Bias}" />
+                                        <Run Text="(V)" />
+                                    </TextBlock>
+
+                                    <TextBlock
+                                        Grid.Column="4"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{DynamicResource ServoValveOpenLoop}" />
+                                    <TextBlock
+                                        Grid.Column="5"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center">
+                                        <Run Text="{DynamicResource OpenLoopVoltage}" />
+                                        <Run Text="(V)" />
+                                    </TextBlock>
+                                </Grid>
+                                <ScrollViewer>
+                                    <ItemsControl ItemsSource="{Binding Vertical}">
+                                        <ItemsControl.ItemTemplate>
+                                            <DataTemplate>
+                                                <Grid
+                                                    Width="{Binding $parent[StackPanel].Width}"
+                                                    HorizontalAlignment="Center"
+                                                    VerticalAlignment="Top"
+                                                    ColumnDefinitions="62,140,120,0.5*,0.3*,0.5*">
+                                                    <TextBlock
+                                                        HorizontalAlignment="Center"
+                                                        VerticalAlignment="Center"
+                                                        Text="{Binding Index}" />
+                                                    <ComboBox
+                                                        Grid.Column="1"
+                                                        ItemsSource="{Binding Source={x:Static vm:ShakerConfigViewModel.Instance}, Path=AOs}"
+                                                        SelectedValue="{Binding Value.Channel}" />
+                                                    <ComboBox
+                                                        Grid.Column="2"
+                                                        ItemsSource="{Binding Source={x:Static shakermodel:Polarity.Positive}, Converter={x:Static convert:EnumToCollectionConverter.Instance}, Mode=OneTime}"
+                                                        SelectedValue="{Binding Path=Value.Polarity}"
+                                                        SelectedValueBinding="{Binding Value}">
+                                                        <ComboBox.ItemTemplate>
+                                                            <DataTemplate>
+                                                                <TextBlock Text="{ivm:ResourceBinding Key}" />
+                                                            </DataTemplate>
+                                                        </ComboBox.ItemTemplate>
+                                                    </ComboBox>
+                                                    <NumericUpDown
+                                                        Grid.Column="3"
+                                                        Margin="4,0,4,0"
+                                                        Increment="0.001"
+                                                        Maximum="{Binding Value.MaxBias}"
+                                                        Minimum="{Binding Value.MinBias}"
+                                                        Value="{Binding Value.Bias}" />
+                                                    <ToggleSwitch
+                                                        Grid.Column="4"
+                                                        HorizontalAlignment="Center"
+                                                        VerticalAlignment="Center"
+                                                        IsChecked="{Binding Value.OpenLoop}" />
+                                                    <NumericUpDown
+                                                        Grid.Column="5"
+                                                        Margin="4,0,4,0"
+                                                        Increment="0.001"
+                                                        IsEnabled="{Binding Value.OpenLoop}"
+                                                        Maximum="{Binding Value.MaxOpenLoopDriver}"
+                                                        Minimum="{Binding Value.MinOpenLoopDriver}"
+                                                        Value="{Binding Value.OpenLoopDriver}" />
+
+                                                </Grid>
+                                            </DataTemplate>
+                                        </ItemsControl.ItemTemplate>
+                                    </ItemsControl>
+                                </ScrollViewer>
+                            </StackPanel>
+                        </suki:GroupBox>
+                    </suki:GlassCard>
+
+
+                    <suki:GlassCard
+                        Grid.Row="1"
+                        Grid.Column="0"
+                        Margin="10,0,10,10">
+                        <suki:GroupBox Header="{DynamicResource Horizontal}">
+                            <StackPanel
+                                Width="620"
+                                HorizontalAlignment="Left"
+                                VerticalAlignment="Top"
+                                Orientation="Vertical">
+                                <Grid
+                                    Width="{Binding $parent[StackPanel].Width}"
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Top"
+                                    ColumnDefinitions="62,140,120,0.5*,0.3*,0.5*">
+                                    <TextBlock
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{DynamicResource ServoValveIndex}" />
+                                    <TextBlock
+                                        Grid.Column="1"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{DynamicResource Channel}" />
+                                    <TextBlock
+                                        Grid.Column="2"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center">
+                                        <Run Text="{DynamicResource ServoValvePolarity}" />
+                                    </TextBlock>
+                                    <TextBlock
+                                        Grid.Column="3"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center">
+                                        <Run Text="{DynamicResource Bias}" />
+                                        <Run Text="(V)" />
+                                    </TextBlock>
+
+                                    <TextBlock
+                                        Grid.Column="4"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{DynamicResource ServoValveOpenLoop}" />
+                                    <TextBlock
+                                        Grid.Column="5"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center">
+                                        <Run Text="{DynamicResource OpenLoopVoltage}" />
+                                        <Run Text="(V)" />
+                                    </TextBlock>
+                                </Grid>
+                                <ScrollViewer>
+                                    <ItemsControl ItemsSource="{Binding Horizontal}">
+                                        <ItemsControl.ItemTemplate>
+                                            <DataTemplate>
+                                                <Grid
+                                                    Width="{Binding $parent[StackPanel].Width}"
+                                                    HorizontalAlignment="Center"
+                                                    VerticalAlignment="Top"
+                                                    ColumnDefinitions="62,140,120,0.5*,0.3*,0.5*">
+                                                    <TextBlock
+                                                        HorizontalAlignment="Center"
+                                                        VerticalAlignment="Center"
+                                                        Text="{Binding Index}" />
+                                                    <ComboBox
+                                                        Grid.Column="1"
+                                                        ItemsSource="{Binding Source={x:Static vm:ShakerConfigViewModel.Instance}, Path=AOs}"
+                                                        SelectedValue="{Binding Value.Channel}" />
+                                                    <ComboBox
+                                                        Grid.Column="2"
+                                                        ItemsSource="{Binding Source={x:Static shakermodel:Polarity.Positive}, Converter={x:Static convert:EnumToCollectionConverter.Instance}, Mode=OneTime}"
+                                                        SelectedValue="{Binding Path=Value.Polarity}"
+                                                        SelectedValueBinding="{Binding Value}">
+                                                        <ComboBox.ItemTemplate>
+                                                            <DataTemplate>
+                                                                <TextBlock Text="{ivm:ResourceBinding Key}" />
+                                                            </DataTemplate>
+                                                        </ComboBox.ItemTemplate>
+                                                    </ComboBox>
+                                                    <NumericUpDown
+                                                        Grid.Column="3"
+                                                        Margin="4,0,4,0"
+                                                        Increment="0.001"
+                                                        Maximum="{Binding Value.MaxBias}"
+                                                        Minimum="{Binding Value.MinBias}"
+                                                        Value="{Binding Value.Bias}" />
+                                                    <ToggleSwitch
+                                                        Grid.Column="4"
+                                                        HorizontalAlignment="Center"
+                                                        VerticalAlignment="Center"
+                                                        IsChecked="{Binding Value.OpenLoop}" />
+                                                    <NumericUpDown
+                                                        Grid.Column="5"
+                                                        Margin="4,0,4,0"
+                                                        Increment="0.001"
+                                                        IsEnabled="{Binding Value.OpenLoop}"
+                                                        Maximum="{Binding Value.MaxOpenLoopDriver}"
+                                                        Minimum="{Binding Value.MinOpenLoopDriver}"
+                                                        Value="{Binding Value.OpenLoopDriver}" />
+
+                                                </Grid>
+                                            </DataTemplate>
+                                        </ItemsControl.ItemTemplate>
+                                    </ItemsControl>
+                                </ScrollViewer>
+                            </StackPanel>
+                        </suki:GroupBox>
+                    </suki:GlassCard>
+
+
+
+                    <suki:GlassCard
+                        Grid.Row="0"
+                        Grid.Column="1"
+                        Margin="0,10,10,10">
+                        <suki:GroupBox Header="{DynamicResource Balancing}">
+                            <StackPanel
+                                Width="200"
+                                HorizontalAlignment="Left"
+                                VerticalAlignment="Top"
+                                Orientation="Vertical">
+                                <Grid
+                                    Width="{Binding $parent[StackPanel].Width}"
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Top"
+                                    ColumnDefinitions="60,140">
+                                    <TextBlock
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{DynamicResource ServoValveIndex}" />
+                                    <TextBlock
+                                        Grid.Column="1"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{DynamicResource Channel}" />
+                                </Grid>
+                                <ScrollViewer>
+                                    <ItemsControl ItemsSource="{Binding Balancing}">
+                                        <ItemsControl.ItemTemplate>
+                                            <DataTemplate>
+                                                <Grid
+                                                    Width="{Binding $parent[StackPanel].Width}"
+                                                    HorizontalAlignment="Center"
+                                                    VerticalAlignment="Top"
+                                                    ColumnDefinitions="60,140">
+                                                    <TextBlock
+                                                        HorizontalAlignment="Center"
+                                                        VerticalAlignment="Center"
+                                                        Text="{Binding Index}" />
+                                                    <ComboBox
+                                                        Grid.Column="1"
+                                                        ItemsSource="{Binding Source={x:Static vm:ShakerConfigViewModel.Instance}, Path=AOs}"
+                                                        SelectedValue="{Binding Value}" />
+
+                                                </Grid>
+                                            </DataTemplate>
+                                        </ItemsControl.ItemTemplate>
+                                    </ItemsControl>
+                                </ScrollViewer>
+                            </StackPanel>
+                        </suki:GroupBox>
+                    </suki:GlassCard>
+                </Grid>
+            </ScrollViewer>
+        </TabItem>
+    </TabControl>
+</UserControl>

+ 13 - 0
Client/Dynamicloadsimulationdevice/Views/ShakerChannel/ShakerChannelView.axaml.cs

@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Dynamicloadsimulationdevice.Views;
+
+public partial class ShakerChannelView : UserControl
+{
+    public ShakerChannelView()
+    {
+        InitializeComponent();
+    }
+}

+ 202 - 0
Client/Dynamicloadsimulationdevice/Views/SignalPreview/AnalogSignalPreviewView.axaml

@@ -0,0 +1,202 @@
+<UserControl
+    x:Class="Dynamicloadsimulationdevice.Views.AnalogSignalPreviewView"
+    xmlns="https://github.com/avaloniaui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:local="using:Dynamicloadsimulationdevice"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:oxy="http://oxyplot.org/avalonia"
+    xmlns:suki="https://github.com/kikipoulet/SukiUI"
+    xmlns:vm="using:Dynamicloadsimulationdevice.ViewModels"
+    xmlns:ivm="using:IViewModel"
+    d:DesignHeight="450"
+    d:DesignWidth="800"
+    x:DataType="vm:AnalogSignalPreviewViewModel"
+    mc:Ignorable="d">
+    <Grid>
+        <oxy:PlotView
+            Background="Transparent"
+            Controller="{Binding PlotController}"
+            DefaultTrackerTemplate="{StaticResource DefaultTrackerTemplate}"
+            Model="{Binding PlotModel}" />
+        <Button
+            Margin="0,0,16,56"
+            HorizontalAlignment="Right"
+            VerticalAlignment="Bottom"
+            Classes="Basic"
+            Cursor="Hand">
+            <Interaction.Behaviors>
+                <EventTriggerBehavior EventName="Click">
+                    <InvokeCommandAction Command="{Binding Source={x:Static vm:PlotConfigViewModel.Instance}, Path=PlotConfigCommand}" CommandParameter="{Binding PlotModel}" />
+                </EventTriggerBehavior>
+            </Interaction.Behaviors>
+            <PathIcon Background="Transparent" Data="{StaticResource SettingGeometry}">
+                <PathIcon.Styles>
+                    <Style Selector="PathIcon:pointerover">
+                        <Setter Property="Foreground" Value="Black" />
+                    </Style>
+                    <Style Selector="PathIcon">
+                        <Setter Property="Foreground" Value="#AAAAAAAA" />
+                    </Style>
+                </PathIcon.Styles>
+            </PathIcon>
+        </Button>
+        <StackPanel
+            HorizontalAlignment="Right"
+            VerticalAlignment="Top"
+            Orientation="Horizontal">
+            <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+                <TextBlock VerticalAlignment="Center" Text="{DynamicResource Statistics}" />
+                <ToggleSwitch IsChecked="{Binding StatisticsVisibily, Mode=TwoWay}" />
+            </StackPanel>
+
+            <Button
+                HorizontalAlignment="Right"
+                VerticalAlignment="Top"
+                Classes="Basic"
+                IsVisible="{Binding CanChangedAnalog}">
+                <PathIcon Data="{StaticResource SettingGeometry}" />
+                <Button.Flyout>
+                    <Flyout ShowMode="Standard">
+                        <suki:GlassCard
+                            Background="Black"
+                            CornerRadius="10"
+                            Opacity="1"
+                            OpacityMask="Black">
+                            <suki:GroupBox>
+                                <suki:GroupBox.Header>
+                                    <TextBlock Foreground="White" Text="{DynamicResource SelectSignalType}" />
+                                </suki:GroupBox.Header>
+                                <ItemsControl ItemsSource="{Binding Menu}">
+                                    <ItemsControl.ItemTemplate>
+                                        <DataTemplate>
+                                            <Grid
+                                                Name="item"
+                                                Height="{StaticResource ItemHeight}"
+                                                x:DataType="vm:TimeDomainMenuViewModel"
+                                                Background="Transparent"
+                                                Cursor="Hand">
+                                                <Interaction.Behaviors>
+                                                    <EventTriggerBehavior EventName="PointerReleased">
+                                                        <ChangePropertyAction
+                                                            PropertyName="SelectedAnalog"
+                                                            TargetObject="{Binding $parent[Button].DataContext}"
+                                                            Value="{Binding #item.DataContext.AnalogType}" />
+                                                    </EventTriggerBehavior>
+                                                </Interaction.Behaviors>
+                                                <Grid.Styles>
+                                                    <Style Selector="Grid:pointerover">
+                                                        <Setter Property="Background" Value="LightGray" />
+                                                    </Style>
+                                                </Grid.Styles>
+                                                <Grid.ColumnDefinitions>
+                                                    <ColumnDefinition Width="30" />
+                                                    <ColumnDefinition Width="*" />
+                                                </Grid.ColumnDefinitions>
+                                                <PathIcon
+                                                    Data="{x:Static suki:Icons.Check}"
+                                                    Foreground="White"
+                                                    IsVisible="{Binding IsChecked}" />
+                                                <Line
+                                                    HorizontalAlignment="Right"
+                                                    VerticalAlignment="Center"
+                                                    Stroke="LightGray"
+                                                    StrokeThickness="2"
+                                                    StartPoint="0,4"
+                                                    EndPoint="0,24" />
+                                                <TextBlock
+                                                    Grid.Column="1"
+                                                    Margin="10,0,0,0"
+                                                    HorizontalAlignment="Left"
+                                                    VerticalAlignment="Center"
+                                                    Foreground="White"
+                                                    Text="{ivm:ResourceBinding Key}" />
+                                            </Grid>
+                                        </DataTemplate>
+                                    </ItemsControl.ItemTemplate>
+                                </ItemsControl>
+                            </suki:GroupBox>
+                        </suki:GlassCard>
+                    </Flyout>
+                </Button.Flyout>
+            </Button>
+        </StackPanel>
+        <StackPanel
+            Margin="80,60"
+            HorizontalAlignment="Left"
+            VerticalAlignment="Top"
+            IsHitTestVisible="False"
+            Orientation="Horizontal">
+            <Border
+                HorizontalAlignment="Left"
+                VerticalAlignment="Top"
+                Background="#1FAAAAAA"
+                CornerRadius="6"
+                IsHitTestVisible="False"
+                IsVisible="{Binding StatisticsVisibily}">
+                <StackPanel Margin="10">
+                    <Grid Height="20" ColumnDefinitions="80,80,80,80,80">
+                        <TextBlock
+                            HorizontalAlignment="Center"
+                            VerticalAlignment="Center"
+                            Text="{DynamicResource ChannelName}" />
+                        <TextBlock
+                            Grid.Column="1"
+                            HorizontalAlignment="Center"
+                            VerticalAlignment="Center"
+                            Text="{DynamicResource Max}" />
+                        <TextBlock
+                            Grid.Column="2"
+                            HorizontalAlignment="Center"
+                            VerticalAlignment="Center"
+                            Text="{DynamicResource Min}" />
+                        <TextBlock
+                            Grid.Column="3"
+                            HorizontalAlignment="Center"
+                            VerticalAlignment="Center"
+                            Text="{DynamicResource Rms}" />
+                        <TextBlock
+                            Grid.Column="4"
+                            HorizontalAlignment="Center"
+                            VerticalAlignment="Center"
+                            Text="{DynamicResource Avg}" />
+                    </Grid>
+                    <ItemsControl ItemsSource="{Binding Statistics}">
+                        <ItemsControl.ItemTemplate>
+                            <DataTemplate>
+                                <Grid Height="20" ColumnDefinitions="80,80,80,80,80">
+                                    <TextBlock
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{ivm:ResourceBinding Name}" />
+                                    <TextBlock
+                                        Grid.Column="1"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{Binding Max, StringFormat='{}{0:F3}'}" />
+                                    <TextBlock
+                                        Grid.Column="2"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{Binding Min, StringFormat='{}{0:F3}'}" />
+                                    <TextBlock
+                                        Grid.Column="3"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{Binding RMS, StringFormat='{}{0:F3}'}" />
+                                    <TextBlock
+                                        Grid.Column="4"
+                                        HorizontalAlignment="Center"
+                                        VerticalAlignment="Center"
+                                        Text="{Binding Average, StringFormat='{}{0:F3}'}" />
+                                </Grid>
+                            </DataTemplate>
+                        </ItemsControl.ItemTemplate>
+                    </ItemsControl>
+                </StackPanel>
+            </Border>
+            
+        </StackPanel>
+
+    </Grid>
+</UserControl>

+ 13 - 0
Client/Dynamicloadsimulationdevice/Views/SignalPreview/AnalogSignalPreviewView.axaml.cs

@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Dynamicloadsimulationdevice.Views;
+
+public partial class AnalogSignalPreviewView : UserControl
+{
+    public AnalogSignalPreviewView()
+    {
+        InitializeComponent();
+    }
+}

+ 192 - 0
Client/Dynamicloadsimulationdevice/Views/SignalPreview/SignalPreviewView.axaml

@@ -0,0 +1,192 @@
+<UserControl
+    x:Class="Dynamicloadsimulationdevice.Views.SignalPreviewView"
+    xmlns="https://github.com/avaloniaui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:convert="using:IViewModel.Convert"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:oxy="http://oxyplot.org/avalonia"
+    xmlns:vm="using:Dynamicloadsimulationdevice.ViewModels"
+    d:DesignHeight="450"
+    d:DesignWidth="800"
+    x:DataType="vm:SignalPreviewViewModel"
+    Background="Transparent"
+    DataContext="{Binding Source={x:Static vm:SignalPreviewViewModel.Instance}}"
+    mc:Ignorable="d">
+    <Grid>
+        <ItemsControl ItemsSource="{Binding SignalPreviews}">
+            <ItemsControl.ItemTemplate>
+                <DataTemplate>
+                    <ContentPresenter Content="{Binding Content, Converter={x:Static convert:Type2ViewConverter.Instance}, ConverterParameter='1'}" />
+                </DataTemplate>
+            </ItemsControl.ItemTemplate>
+            <ItemsControl.ItemsPanel>
+                <ItemsPanelTemplate>
+                    <UniformGrid Columns="{Binding ColumnCount}" Rows="{Binding RowCount}" />
+                </ItemsPanelTemplate>
+            </ItemsControl.ItemsPanel>
+        </ItemsControl>
+
+        <Button
+            Width="36"
+            Height="36"
+            Margin="10,4,0,0"
+            Padding="0"
+            HorizontalAlignment="Left"
+            VerticalAlignment="Top"
+            Background="Transparent"
+            Classes="Basic">
+            <Interaction.Behaviors>
+                <EventTriggerBehavior EventName="PointerEntered">
+                    <InvokeCommandAction Command="{Binding PointerEnteredCommand}" />
+                </EventTriggerBehavior>
+                <EventTriggerBehavior EventName="PointerExited">
+                    <InvokeCommandAction Command="{Binding PointerExitedCommand}" />
+                </EventTriggerBehavior>
+            </Interaction.Behaviors>
+            <PathIcon
+                Width="{Binding $parent[Button].Width}"
+                Height="{Binding $parent[Button].Height}"
+                Margin="0"
+                Padding="0"
+                Data="{StaticResource LayoutGeometry}" />
+        </Button>
+        <Popup
+            Width="160"
+            Height="140"
+            IsOpen="{Binding ShowLayout}"
+            Placement="Pointer"
+            PlacementMode="Bottom"
+            PlacementTarget="{Binding $parent.Children[1]}">
+            <Border
+                Background="#e9f0fc"
+                BorderBrush="Gray"
+                BorderThickness="1"
+                CornerRadius="4">
+                <Interaction.Behaviors>
+                    <EventTriggerBehavior EventName="PointerEntered">
+                        <InvokeCommandAction Command="{Binding ControlPointerEnteredCommand}" />
+                    </EventTriggerBehavior>
+                    <EventTriggerBehavior EventName="PointerExited">
+                        <InvokeCommandAction Command="{Binding ControlPointerExitedCommand}" />
+                    </EventTriggerBehavior>
+                </Interaction.Behaviors>
+                <UniformGrid Columns="2" Rows="2">
+                    <UniformGrid.Styles>
+                        <Style Selector="Border">
+                            <Setter Property="Background" Value="#d5d8dd" />
+                            <Setter Property="BorderBrush" Value="#858789" />
+                            <Setter Property="Cursor" Value="Hand" />
+                        </Style>
+                        <Style Selector="Border.Pointerover">
+                            <Setter Property="Background" Value="#0067c0" />
+                        </Style>
+                    </UniformGrid.Styles>
+                    <Border
+                        Margin="4"
+                        BorderThickness="1"
+                        CornerRadius="6">
+                        <Interaction.Behaviors>
+                            <EventTriggerBehavior EventName="PointerPressed">
+                                <InvokeCommandAction Command="{Binding OneCommnad}" />
+                            </EventTriggerBehavior>
+                        </Interaction.Behaviors>
+                        <Border.Styles>
+                            <Style Selector="Border">
+                                <Setter Property="Background" Value="#d5d8dd" />
+                                <Setter Property="BorderBrush" Value="#858789" />
+                                <Setter Property="Cursor" Value="Hand" />
+                            </Style>
+                            <Style Selector="Border:pointerover">
+                                <Setter Property="Background" Value="#0067c0" />
+                            </Style>
+                        </Border.Styles>
+                    </Border>
+
+                    <Grid
+                        Margin="4"
+                        Background="Transparent"
+                        ColumnDefinitions="*,*"
+                        Cursor="Hand">
+
+                        <Interaction.Behaviors>
+                            <EventTriggerBehavior EventName="PointerPressed">
+                                <InvokeCommandAction Command="{Binding OneTwoCommand}" />
+                            </EventTriggerBehavior>
+                        </Interaction.Behaviors>
+                        <Border
+                            Margin="0,0,2,0"
+                            BorderThickness="1"
+                            Classes.Pointerover="{Binding $parent[Grid].IsPointerOver}"
+                            CornerRadius="6,0,0,6" />
+                        <Border
+                            Grid.Column="1"
+                            Margin="2,0,0,0"
+                            BorderThickness="1"
+                            Classes.Pointerover="{Binding $parent[Grid].IsPointerOver}"
+                            CornerRadius="0,6,6,0" />
+                    </Grid>
+                    <Grid
+                        Margin="4"
+                        Background="Transparent"
+                        Cursor="Hand"
+                        RowDefinitions="*,*">
+                        <Interaction.Behaviors>
+                            <EventTriggerBehavior EventName="PointerPressed">
+                                <InvokeCommandAction Command="{Binding TwoOneCommand}" />
+                            </EventTriggerBehavior>
+                        </Interaction.Behaviors>
+                        <Border
+                            Margin="0,0,0,2"
+                            BorderThickness="1"
+                            Classes.Pointerover="{Binding $parent[Grid].IsPointerOver}"
+                            CornerRadius="6,6,0,0" />
+                        <Border
+                            Grid.Row="1"
+                            Margin="0,2,0,0"
+                            BorderThickness="1"
+                            Classes.Pointerover="{Binding $parent[Grid].IsPointerOver}"
+                            CornerRadius="0,0,6,6" />
+                    </Grid>
+                    <Grid
+                        Margin="4"
+                        Background="Transparent"
+                        ColumnDefinitions="*,*"
+                        Cursor="Hand"
+                        RowDefinitions="*,*">
+                        <Interaction.Behaviors>
+                            <EventTriggerBehavior EventName="PointerPressed">
+                                <InvokeCommandAction Command="{Binding TwoTwoCommand}" />
+                            </EventTriggerBehavior>
+                        </Interaction.Behaviors>
+                        <Border
+                            Margin="0,0,2,2"
+                            BorderThickness="1"
+                            Classes.Pointerover="{Binding $parent[Grid].IsPointerOver}"
+                            CornerRadius="6,0,0,0" />
+                        <Border
+                            Grid.Row="1"
+                            Margin="0,2,2,0"
+                            BorderThickness="1"
+                            Classes.Pointerover="{Binding $parent[Grid].IsPointerOver}"
+                            CornerRadius="0,0,0,6" />
+
+                        <Border
+                            Grid.Column="1"
+                            Margin="2,0,0,2"
+                            BorderThickness="1"
+                            Classes.Pointerover="{Binding $parent[Grid].IsPointerOver}"
+                            CornerRadius="0,6,0,0" />
+                        <Border
+                            Grid.Row="1"
+                            Grid.Column="1"
+                            Margin="2,2,0,0"
+                            BorderThickness="1"
+                            Classes.Pointerover="{Binding $parent[Grid].IsPointerOver}"
+                            CornerRadius="0,0,6,0" />
+                    </Grid>
+                </UniformGrid>
+            </Border>
+        </Popup>
+    </Grid>
+</UserControl>

+ 13 - 0
Client/Dynamicloadsimulationdevice/Views/SignalPreview/SignalPreviewView.axaml.cs

@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Dynamicloadsimulationdevice.Views;
+
+public partial class SignalPreviewView : UserControl
+{
+    public SignalPreviewView()
+    {
+        InitializeComponent();
+    }
+}

+ 108 - 0
Client/IViewModel/Convert/EnumToDescription.cs

@@ -0,0 +1,108 @@
+using Avalonia;
+using Avalonia.Data.Converters;
+using IViewModel.Tools;
+using IViewModel.ViewModels;
+using Shaker.Models;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace IViewModel.Convert
+{
+    public sealed class EnumToBooleanConverter : IValueConverter
+    {
+        public static EnumToBooleanConverter Instance { get; } = new EnumToBooleanConverter();
+        public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+        {
+            if(value is Enum e1 &&parameter is Enum e2 && e1.GetType() == e2.GetType())
+            {
+                return e1.ToString() == e2.ToString();
+            }
+            else if(value is Enum e && parameter is string s)
+            {
+                return e.ToString() == s;
+            }
+                return false;
+        }
+
+        public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+    public class EnumToDescription : IValueConverter
+    {
+        public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+        {
+            if (value == null || value == AvaloniaProperty.UnsetValue || !value.GetType().IsEnum) return "";
+            if( value is Enum e) return e.Description();
+            return "";
+        }
+
+        public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+    public sealed class EnumToCollectionConverter : IValueConverter
+    {
+        public static EnumToCollectionConverter Instance { get; } = new EnumToCollectionConverter();
+        public static string Value { get; } = nameof(ValueDescription.Value);
+        public static string Key { get; } = nameof(ValueDescription.Key);
+
+        public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+        {
+            if (value == null || value == AvaloniaProperty.UnsetValue || !value.GetType().IsEnum) return null;
+            try
+            {
+                string parstr = (parameter ==null ?"":parameter.ToString())!;
+                List<Enum> showValues = new List<Enum>();
+                if (!string.IsNullOrEmpty(parstr)) showValues = parstr.Split(",".ToCharArray()).Select(x => (Enum)Enum.ToObject(value.GetType(), int.Parse(x))).ToList();
+                List<ValueDescription> valueDescriptions = new List<ValueDescription>();
+                Enum.GetValues(value.GetType())
+                           .Cast<Enum>()
+                           .ToList().ForEach(e =>
+                           {
+                               if (parameter != null && showValues.Count > 0)
+                               {
+                                   if (showValues.FindIndex(x => x.ToString() == e.ToString()) >= 0) valueDescriptions.Add(new ValueDescription() { Value = e, Key = e.Description() });
+                               }
+                               else valueDescriptions.Add(new ValueDescription() { Value = e, Key = e.Description() });
+                           });
+                return valueDescriptions;
+            }
+            catch { }
+            return null;
+        }
+
+        public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+        {
+            return null;
+        }
+    }
+
+    public sealed class ValueDescription : ViewModelBase
+    {
+        private Enum? value;
+        private string key = string.Empty;
+        private bool visibility = true;
+        private bool isEnabled = true;
+
+        public ValueDescription()
+        {
+        }
+
+        public Enum? Value { get => value; set => SetProperty(ref this.value, value); }
+        public string Key { get => key; set => SetProperty(ref key, value); }
+
+        public bool Visibility { get => visibility; set => SetProperty(ref visibility, value); }
+        public bool IsEnabled { get => isEnabled; set => SetProperty(ref isEnabled, value); }
+
+        public override string ToString()
+        {
+            return Key;
+        }
+    }
+}

+ 2 - 1
Client/IViewModel/IViewModel.csproj

@@ -8,15 +8,16 @@
 
   <ItemGroup>
     <PackageReference Include="log4net" Version="3.0.4" />
+    <PackageReference Include="SukiUI" Version="6.0.1" />
   </ItemGroup>
 
   <ItemGroup>
+    <ProjectReference Include="..\..\Avalonia\IconResourceSourceGenerator\IconResourceSourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
     <ProjectReference Include="..\..\Communication\ICommunication\ICommunication.csproj" />
     <ProjectReference Include="..\..\EventBroker\EventBroker\EventBroker.csproj" />
     <ProjectReference Include="..\..\NativeLibraryLoader\NativeLibraryLoader.csproj" />
     <ProjectReference Include="..\..\Shaker.Model\Shaker.Model.csproj" />
     <ProjectReference Include="..\Avalonia\Avalonia.Xaml.Behaviors\Avalonia.Xaml.Behaviors.csproj" />
-    <ProjectReference Include="..\Avalonia\SukiUI\SukiUI.csproj" />
     <ProjectReference Include="..\Language\ILanguage\ILanguage.csproj" />
     <ProjectReference Include="..\Language\LanguageSourceGenerator\LanguageSourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
   </ItemGroup>

+ 23 - 0
Client/IViewModel/Models/StatisticsModel.cs

@@ -0,0 +1,23 @@
+using IModel;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IViewModel.Models
+{
+    public sealed class StatisticsModel : IModel.BaseModel
+    {
+        public string Name = string.Empty;
+        public float Max;
+        public float Min;
+        public float RMS;
+        public float Average;
+        public string Unit = string.Empty;
+        public override object Clone()
+        {
+            return this.CloneBase();
+        }
+    }
+}

+ 18 - 0
Client/IViewModel/Tools/EnumHelper.cs

@@ -0,0 +1,18 @@
+using System;
+using System.ComponentModel;
+using System.Linq;
+
+namespace IViewModel.Tools
+{
+    public static class EnumHelper
+    {
+        public static string Description<T>(this T e) where T:Enum
+        {
+            if (e == null || !e.GetType().IsEnum) return "";
+            return (e.GetType()?
+                     .GetField(e.ToString())?
+                     .GetCustomAttributes(typeof(DescriptionAttribute), false)
+                     .FirstOrDefault() as DescriptionAttribute)?.Description ?? e.ToString();
+        }
+    }
+}

+ 3 - 3
Client/IViewModel/Tools/ResourceBinding.cs

@@ -13,13 +13,13 @@ namespace IViewModel
     /// <summary>
     /// 多语言绑定时使用,没有其他任何功能
     /// </summary>
-    public class ResourceBindingExtensions
+    public sealed class ResourceBindingExtensions
     {
         public static readonly AttachedProperty<object?> BindingExtensionProperty = AvaloniaProperty.RegisterAttached<Control, object?>("BindingExtension", typeof(Control), defaultValue: null);
         public static object? GetBindingExtension(Control control) => control.GetValue(BindingExtensionProperty);
         public static void SetBindingExtension(Control control, object? value) => control.SetValue(BindingExtensionProperty, value);
     }
-    public class ResourceBinding : MarkupExtension
+    public sealed class ResourceBinding : MarkupExtension
     {
         private PathConverter converter = new PathConverter();
 
@@ -53,7 +53,7 @@ namespace IViewModel
         /// </summary>
         public string Path { get; set; }
 
-        private class PathConverter : IValueConverter
+        private sealed class PathConverter : IValueConverter
         {
             public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
             {

+ 2 - 1
Client/IViewModel/ViewModels/DisplayViewModelBase.cs

@@ -15,6 +15,7 @@ namespace IViewModel.ViewModels
 {
     public abstract class DisplayViewModelBase : ViewModelBase, IDisplayViewModel
     {
+        public virtual string IconKey { get; } = string.Empty;
         public virtual bool ShowTop { get; } = false;
         public virtual bool AppendSeparator { get; }
         public virtual string MenuKey { get; } = string.Empty;
@@ -119,7 +120,7 @@ namespace IViewModel.ViewModels
         {
             Save();
         });
-        private protected virtual void Save()
+        protected virtual void Save()
         {
 
         }

+ 11 - 3
Client/IViewModel/ViewModels/DisplayViewModelBase{TModel}.cs

@@ -16,6 +16,8 @@ namespace IViewModel.ViewModels
 
     public abstract class DisplayViewModelBase<TModel> :ViewModelBase<TModel>,IDisplayViewModel where TModel : IModel.IModel
     {
+        protected void SetSaveClose(bool enabled) => SaveIsEnabled = enabled;
+        public virtual string IconKey { get; } = string.Empty;
         public virtual bool ShowTop { get; } = false;
 
         public virtual bool AppendSeparator { get; }
@@ -113,8 +115,8 @@ namespace IViewModel.ViewModels
         }
         private protected bool cansave = false;
 
-        public virtual string OKContent => "Save";
-        public virtual string CancelContent => "Cancel";
+        public virtual string OKContent => nameof(LanguageValueViewModel.Save);
+        public virtual string CancelContent => nameof(LanguageValueViewModel.Close);
 
 
         public bool SaveClose { get => saveClose; set => SetProperty(ref saveClose, value); }
@@ -131,15 +133,21 @@ namespace IViewModel.ViewModels
                 {
                     DispatherInovke.Inovke(() => SaveClose = true);
                 });
+                UpLastModel();
                 return;
             }
             cansave = false;
         });
+        protected void UpLastModel()
+        {
+            lastModel = (TModel)Model.Clone();
+            SaveIsEnabled = false;
+        }
         public ICommand CancelCommand => new RelayCommand(Cancel);
 
         public virtual string Title { get => title; set => SetProperty(ref title, value); }
         public bool ButtonVisibily { get => buttonVisibily; set => SetProperty(ref buttonVisibily, value); }
-        private protected bool savecanclose = true;
+        protected bool savecanclose = true;
         protected virtual void Save()
         {
             lastModel = default;

+ 26 - 0
Client/IViewModel/ViewModels/File/ExitViewModel.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IViewModel.ViewModels
+{
+    [MenuOrder(int.MaxValue)]
+    public sealed class ExitViewModel:DisplayViewModelBase
+    {
+        private ExitViewModel():base()
+        {
+
+        }
+        static ExitViewModel()
+        {
+
+        }
+        public static ExitViewModel Instance { get; } = new ExitViewModel();
+        public override bool AppendSeparator => true;
+        public override string MenuKey => nameof(LanguageValueViewModel.MenuExit);
+        public override string MenuParentKey => nameof(LanguageValueViewModel.MenuFile);
+        public override string IconKey => nameof(IconResourceValueViewModel.ExitGeometry);
+    }
+}

+ 78 - 0
Client/IViewModel/ViewModels/File/FileLoadViewModel.cs

@@ -0,0 +1,78 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Platform.Storage;
+using CommunityToolkit.Mvvm.Input;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace IViewModel.ViewModels
+{
+    public sealed class FileLoadViewModel:DisplayViewModelBase
+    {
+        private FileLoadViewModel() : base()
+        {
+            Content = typeof(Views.LoadConfigView);
+            Title = nameof(LanguageValueViewModel.MenuLoadConfig);
+        }
+        static FileLoadViewModel()
+        {
+
+        }
+        public override bool ShowTop => true;
+        public override string IconKey => nameof(IconResourceValueViewModel.LoadConfigGeometry);
+        public static FileLoadViewModel Instance { get; } = new FileLoadViewModel();
+        public override bool AppendSeparator => false;
+        public override string MenuKey => nameof(LanguageValueViewModel.MenuLoadConfig);
+        public override string MenuParentKey => nameof(LanguageValueViewModel.MenuFile);
+        public override string OKContent => nameof(LanguageValueViewModel.Confirm);
+        public override bool CanResize => false;
+        public override double Height => 320;
+        public override double Width => 620;
+        private string filter = "*";
+        public override void InitData()
+        {
+            base.InitData();
+            LoadFileAction = null;
+        }
+        public string Filter
+        {
+            get => filter;
+            set
+            {
+                if (string.IsNullOrEmpty(value))
+                {
+                    filter = "*";
+                }
+                else filter = value;
+            }
+        }
+        public ICommand SelectFileCommand => new RelayCommand<string?>(SelectFile);
+        [AllowNull]
+        public Action<string> LoadFileAction { get; set; }
+        private async void SelectFile(string? p)
+        {
+            if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                var folder = await TopLevel.GetTopLevel(desktop.MainWindow)!.StorageProvider.OpenFilePickerAsync(new Avalonia.Platform.Storage.FilePickerOpenOptions()
+                {
+                    AllowMultiple = false,
+                    Title = p == null ? "" : Application.Current?.FindResource(p) + "",
+                    FileTypeFilter = new FilePickerFileType[]
+                    {
+                        new FilePickerFileType($"{(p ==null ?"": Application.Current?.FindResource(p) + "")}"){ Patterns = new []{"*."+Filter } }
+                    }
+                });
+                if (folder != null && folder.Count > 0)
+                {
+                    LoadFileAction?.Invoke(folder.First().Path.LocalPath);
+                }
+            }
+        }
+    }
+}

+ 78 - 0
Client/IViewModel/ViewModels/File/FileSaveViewModel.cs

@@ -0,0 +1,78 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Platform.Storage;
+using CommunityToolkit.Mvvm.Input;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace IViewModel.ViewModels
+{
+    public sealed class FileSaveViewModel:DisplayViewModelBase
+    {
+        private FileSaveViewModel() : base()
+        {
+            Content = typeof(Views.SaveConfigView);
+            Title = nameof(LanguageValueViewModel.MenuSaveConfig);
+        }
+        static FileSaveViewModel()
+        {
+
+        }
+        public override bool ShowTop => true;
+        public override string IconKey => nameof(IconResourceValueViewModel.SaveConfigGeometry);
+        public static FileSaveViewModel Instance { get; } = new FileSaveViewModel();
+        public override bool AppendSeparator => false;
+        public override string MenuKey => nameof(LanguageValueViewModel.MenuSaveConfig);
+        public override string MenuParentKey => nameof(LanguageValueViewModel.MenuFile);
+        public override string OKContent => nameof(LanguageValueViewModel.Confirm);
+        public override bool CanResize => false;
+        public override double Height => 320;
+        public override double Width => 620;
+        private string filter = "*";
+        public override void InitData()
+        {
+            base.InitData();
+            SaveFileAction = null;
+        }
+        public string Filter 
+        {
+            get => filter;
+            set
+            {
+                if (string.IsNullOrEmpty(value))
+                {
+                    filter = "*";
+                }
+                else filter = value;
+            }
+        }
+        public ICommand SelectFileCommand => new RelayCommand<string?>(SelectFile);
+        [AllowNull]
+        public Action<string> SaveFileAction { get; set; }
+        private async void SelectFile(string? p)
+        {
+            if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                var folder = await TopLevel.GetTopLevel(desktop.MainWindow)!.StorageProvider.OpenFilePickerAsync(new Avalonia.Platform.Storage.FilePickerOpenOptions()
+                {
+                    AllowMultiple = false,
+                    Title = p == null ? "" : Application.Current?.FindResource(p) + "",
+                    FileTypeFilter = new FilePickerFileType[]
+                    {
+                        new FilePickerFileType($"{(p ==null ?"": Application.Current?.FindResource(p) + "")}"){ Patterns = new []{"*."+Filter } }
+                    }
+                });
+                if (folder != null && folder.Count > 0)
+                {
+                    SaveFileAction?.Invoke(folder.First().Path.LocalPath);
+                }
+            }
+        }
+    }
+}

+ 1 - 0
Client/IViewModel/ViewModels/IDisplayViewModel.cs

@@ -9,6 +9,7 @@ namespace IViewModel.ViewModels
         public bool AppendSeparator { get; }
         public bool ShowTop { get; }
         public string MenuParentKey { get; }
+        public string IconKey { get; }
         public Type Content { get; set; }
         public string MenuKey { get; }
         public bool ShowMenu => !string.IsNullOrEmpty(MenuKey);

+ 3 - 2
Client/IViewModel/ViewModels/IndexValueItemViewModel.cs

@@ -1,10 +1,11 @@
-using IViewModel.Models;
+using Avalonia.Collections;
+using IViewModel.Models;
 using IViewModel.ViewModels;
 using System.Diagnostics.CodeAnalysis;
 
 namespace IViewModel.ViewModels
 {
-    public class IndexValueItemViewModel<T>:ViewModelBase<IndexValueItemModel<T>> where T :CommunityToolkit.Mvvm.ComponentModel.ObservableObject
+    public sealed class IndexValueItemViewModel<T>:ViewModelBase<IndexValueItemModel<T>> 
     {
         public IndexValueItemViewModel(int index,T value)
         {

+ 1 - 1
Client/IViewModel/ViewModels/Log/LogViewModel.cs

@@ -13,7 +13,7 @@ namespace IViewModel.ViewModels
         private LogViewModel()
         {
             ButtonVisibily = false;
-            GetEvent<Models.LogItemModel>().Subscrip((sender, args) =>
+            GetEvent<LogItemModel>().Subscrip((sender, args) =>
             {
                 Tools.DispatherInovke.Inovke(() => Logs.Add(new IndexValueItemViewModel<LogItemViewModel>(Logs.Count + 1, new LogItemViewModel(args.Data.Message, args.Data.LogType))));
             });

+ 10 - 0
Client/IViewModel/ViewModels/MenuViewModel.cs

@@ -13,6 +13,7 @@ namespace IViewModel.ViewModels
 
     public sealed class MenuViewModel:ViewModelBase
     {
+        public IReadOnlyList<string> MainMenus { get; } = [nameof(LanguageValueViewModel.MenuFile),nameof(LanguageValueViewModel.MenuDevice),nameof(LanguageValueViewModel.MenuAbout)];
         public ICommand Command => new RelayCommand<MenuItemViewModel?>((p) => MenuClick?.Invoke(this,p));
         public event EventHandler<MenuItemViewModel?> MenuClick;
 
@@ -46,6 +47,10 @@ namespace IViewModel.ViewModels
         public AvaloniaList<MenuItemViewModel> Menus { get; } = new AvaloniaList<MenuItemViewModel>();
         private MenuViewModel()
         {
+            Menus.AddRange(MainMenus.Select(x => new MenuItemViewModel()
+            {
+                Header = x,
+            }));
         }
 
         static MenuViewModel()
@@ -54,4 +59,9 @@ namespace IViewModel.ViewModels
         }
         public static MenuViewModel Instance { get; } = new MenuViewModel();
     }
+    public sealed class MenuOrderAttribute : Attribute
+    {
+        public MenuOrderAttribute(int order) => Order = order;
+        public int Order { get; }
+    }
 }

+ 4 - 4
Client/IViewModel/ViewModels/ViewModelBase.cs

@@ -15,15 +15,15 @@ namespace IViewModel.ViewModels
         {
         }
         [return: NotNull]
-        protected EventBroker.EventData<TData> GetEvent<TData>() => (EventBroker.EventData<TData>)EventBroker.Instance.GetEvent<TData>();
+        protected static EventBroker.EventData<TData> GetEvent<TData>() => (EventBroker.EventData<TData>)EventBroker.Instance.GetEvent<TData>();
 
         [return: NotNull]
-        protected EventBroker.EventData<TData, T> GetEvent<TData, T>() => (EventBroker.EventData<TData, T>)EventBroker.Instance.GetEvent<TData, T>();
+        protected static EventBroker.EventData<TData, T> GetEvent<TData, T>() => (EventBroker.EventData<TData, T>)EventBroker.Instance.GetEvent<TData, T>();
 
         [return: NotNull]
-        public EventBroker.AnonymousEventData GetEvent([NotNull] string eventName) => (EventBroker.AnonymousEventData)EventBroker.Instance.GetEvent(eventName);
+        protected static EventBroker.AnonymousEventData GetEvent([NotNull] string eventName) => (EventBroker.AnonymousEventData)EventBroker.Instance.GetEvent(eventName);
 
         [return: NotNull]
-        public EventBroker.AnonymousEventData<T> GetEvent<T>([NotNull] string eventName) => (EventBroker.AnonymousEventData<T>)EventBroker.Instance.GetEvent<T>(eventName);
+        protected static EventBroker.AnonymousEventData<T> GetEvent<T>([NotNull] string eventName) => (EventBroker.AnonymousEventData<T>)EventBroker.Instance.GetEvent<T>(eventName);
     }
 }

+ 2 - 1
Client/IViewModel/ViewModels/ViewModelBase{TModel}.cs

@@ -16,6 +16,7 @@ namespace IViewModel.ViewModels
         {
             UpDateModel(model);
         }
+        
         private List<FieldInfo> AllFields = new List<FieldInfo>();
         private List<(string, IReadOnlyList<string>)> AllProperties = new List<(string, IReadOnlyList<string>)>();
         public ViewModelBase()
@@ -93,7 +94,7 @@ namespace IViewModel.ViewModels
 
 
         [AllowNull]
-        private protected TModel lastModel;
+        protected TModel lastModel;
         protected virtual void RefreshUI(List<string> changedNames)
         {
 

+ 56 - 0
Client/IViewModel/Views/File/LoadConfigView.axaml

@@ -0,0 +1,56 @@
+<UserControl
+    x:Class="IViewModel.Views.LoadConfigView"
+    xmlns="https://github.com/avaloniaui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:vm="using:IViewModel.ViewModels"
+    Width="{Binding Width}"
+    Height="{Binding Height}"
+    d:DesignHeight="450"
+    d:DesignWidth="800"
+    x:DataType="vm:FileLoadViewModel"
+    DataContext="{Binding Source={x:Static vm:FileLoadViewModel.Instance}}"
+    mc:Ignorable="d">
+    <StackPanel
+        Height="{StaticResource ItemHeight}"
+        HorizontalAlignment="Center"
+        Orientation="Horizontal">
+        <TextBlock VerticalAlignment="Center" Text="{DynamicResource MenuFile}" />
+
+        <Border
+            Width="420"
+            Margin="4,0,0,0"
+            Background="Transparent"
+            BorderThickness="1"
+            CornerRadius="6"
+            Cursor="Hand">
+            <TextBlock
+                Margin="10,4,10,4"
+                VerticalAlignment="Center"
+                Text="{Binding SelectedFile}" />
+            <Interaction.Behaviors>
+                <EventTriggerBehavior EventName="PointerReleased">
+                    <InvokeCommandAction Command="{Binding SelectFileCommand}" CommandParameter="MenuFile" />
+                </EventTriggerBehavior>
+            </Interaction.Behaviors>
+            <Border.Styles>
+                <Style Selector="Border">
+                    <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}" />
+                </Style>
+                <Style Selector="Border:pointerover">
+                    <Setter Property="BorderBrush" Value="{DynamicResource ThemeAccentBrush}" />
+                </Style>
+            </Border.Styles>
+        </Border>
+        <Button
+            Width="{StaticResource ItemHeight}"
+            Height="{StaticResource ItemHeight}"
+            Margin="4,0,0,0"
+            Padding="0,8,0,8"
+            Command="{Binding SelectFileCommand}"
+            CommandParameter="MenuFile">
+            <PathIcon Data="{StaticResource SelectFileGeometry}" />
+        </Button>
+    </StackPanel>
+</UserControl>

+ 13 - 0
Client/IViewModel/Views/File/LoadConfigView.axaml.cs

@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace IViewModel.Views;
+
+public partial class LoadConfigView : UserControl
+{
+    public LoadConfigView()
+    {
+        InitializeComponent();
+    }
+}

+ 58 - 0
Client/IViewModel/Views/File/SaveConfigView.axaml

@@ -0,0 +1,58 @@
+<UserControl
+    x:Class="IViewModel.Views.SaveConfigView"
+    xmlns="https://github.com/avaloniaui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:vm="using:IViewModel.ViewModels"
+    Width="{Binding Width}"
+    Height="{Binding Height}"
+    d:DesignHeight="450"
+    d:DesignWidth="800"
+    x:DataType="vm:FileSaveViewModel"
+    DataContext="{Binding Source={x:Static vm:FileSaveViewModel.Instance}}"
+    mc:Ignorable="d">
+    <StackPanel
+        Height="{StaticResource ItemHeight}"
+        HorizontalAlignment="Center"
+        Orientation="Horizontal">
+        <TextBlock VerticalAlignment="Center" Text="{DynamicResource MenuFile}" />
+
+        <Border
+            Width="420"
+            Margin="4,0,0,0"
+            Background="Transparent"
+            BorderThickness="1"
+            CornerRadius="6"
+            Cursor="Hand">
+            <TextBlock
+                Margin="10,4,10,4"
+                VerticalAlignment="Center"
+                Text="{Binding SelectedFile}" />
+            <Interaction.Behaviors>
+                <EventTriggerBehavior EventName="PointerReleased">
+                    <InvokeCommandAction Command="{Binding SelectFileCommand}" CommandParameter="MenuFile" />
+                </EventTriggerBehavior>
+            </Interaction.Behaviors>
+            <Border.Styles>
+                <Style Selector="Border">
+                    <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}" />
+                </Style>
+                <Style Selector="Border:pointerover">
+                    <Setter Property="BorderBrush" Value="{DynamicResource ThemeAccentBrush}" />
+                </Style>
+            </Border.Styles>
+        </Border>
+        <Button
+            Width="{StaticResource ItemHeight}"
+            Height="{StaticResource ItemHeight}"
+            Margin="4,0,0,0"
+            Padding="0,8,0,8"
+            Command="{Binding SelectFileCommand}"
+            CommandParameter="MenuFile">
+            <PathIcon Data="{StaticResource SelectFileGeometry}" />
+        </Button>
+
+
+    </StackPanel>
+</UserControl>

+ 13 - 0
Client/IViewModel/Views/File/SaveConfigView.axaml.cs

@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace IViewModel.Views;
+
+public partial class SaveConfigView : UserControl
+{
+    public SaveConfigView()
+    {
+        InitializeComponent();
+    }
+}

+ 42 - 0
Client/IViewModel/Views/SplashScreen.axaml

@@ -0,0 +1,42 @@
+<Window
+    x:Class="IViewModel.SplashScreen"
+    xmlns="https://github.com/avaloniaui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:suki="https://github.com/kikipoulet/SukiUI"
+    Title="SplashScreen"
+    Width="220"
+    Height="260"
+    d:DesignHeight="260"
+    d:DesignWidth="220"
+    Background="White"
+    BorderBrush="Transparent"
+    BorderThickness="0"
+    CanResize="False"
+    CornerRadius="0"
+    ExtendClientAreaChromeHints="NoChrome"
+    ExtendClientAreaTitleBarHeightHint="-1"
+    ExtendClientAreaToDecorationsHint="True"
+    IsHitTestVisible="False"
+    ShowInTaskbar="False"
+    SystemDecorations="None"
+    Topmost="True"
+    WindowStartupLocation="CenterScreen"
+    mc:Ignorable="d">
+    <StackPanel
+        HorizontalAlignment="Center"
+        VerticalAlignment="Center"
+        Orientation="Vertical">
+        <suki:Loading
+            Width="220"
+            Height="220"
+            LoadingStyle="Glow" />
+        <TextBlock
+            HorizontalAlignment="Center"
+            VerticalAlignment="Center"
+            FontSize="22"
+            FontWeight="Bold"
+            Text="启动中" />
+    </StackPanel>
+</Window>

+ 13 - 0
Client/IViewModel/Views/SplashScreen.axaml.cs

@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace IViewModel;
+
+public partial class SplashScreen : Window
+{
+    public SplashScreen()
+    {
+        InitializeComponent();
+    }
+}

+ 1 - 0
Client/Language/LanguageSourceGenerator/LanguageSourceGenerator.cs

@@ -29,6 +29,7 @@ namespace LanguageSourceGenerator
                     stringBuilder.AppendLine("{");
                     stringBuilder.AppendLine("    public sealed class LanguageValueViewModel:ViewModelBase");
                     stringBuilder.AppendLine("    {");
+                    stringBuilder.AppendLine($"        public string this[string key] => (Avalonia.Application.Current?.FindResource(key) ?? \"<no set>\")+\"\";");
                     stringBuilder.AppendLine("        private LanguageValueViewModel()");
                     stringBuilder.AppendLine("        {");
                     stringBuilder.AppendLine("            GetEvent(LanguageViewModel.LANGUAGECHANGEDEVENT).Subscrip((_, _) =>");

+ 39 - 3
Client/Language/Zh-CN/Language.axaml

@@ -8,16 +8,44 @@
     <s:String x:Key="Name">中文(简体)</s:String>
     <s:String x:Key="Key">zh-cn</s:String>
     <s:String x:Key="Positive">正极性</s:String>
+    <s:String x:Key="AppStarting">启动中</s:String>
+    <s:String x:Key="NotSupport">不支持</s:String>
+    <s:String x:Key="ControlConfig">控制参数</s:String>
+    <s:String x:Key="BarrierPotential">阀死区电压</s:String>
+    <s:String x:Key="VerticalBarrierPotential">水平阀死区电压</s:String>
+    <s:String x:Key="HorizontalBarrierPotential">竖直阀死区电压</s:String>
+    <s:String x:Key="DriverOverLimitVoltage">驱动超限电压</s:String>
+    <s:String x:Key="EmerhencyDriverLimitVoltage">急停后驱动限幅值</s:String>
+    <s:String x:Key="Close">关闭</s:String>
+    <s:String x:Key="Apply">应用</s:String>
+    <s:String x:Key="Save">保存</s:String>
+    <s:String x:Key="SaveAs">另存为</s:String>
+    <s:String x:Key="ResultChannels">Fifo通道</s:String>
+    <s:String x:Key="ResultChannelTypActualDisplacement">实际位移</s:String>
+    <s:String x:Key="ResultChannelTypeGivenDisplacement">给定位移</s:String>
+    <s:String x:Key="ResultChannelTypeHorizontalCylinderDrive">水平缸驱动</s:String>
+    <s:String x:Key="ResultChannelTypeVerticalCylinderDrive">竖直缸驱动</s:String>
+    <s:String x:Key="ResultChannelTypeBalanceCylinderDrive">平衡缸驱动</s:String>
+    <s:String x:Key="ResultChannelTypeDifferentialPressure">压差</s:String>
+    <s:String x:Key="ResultChannelTypeSupportingPressure">支撑压力</s:String>
+    <s:String x:Key="ResultChannelTypeSixFreedomsGivenDisplacement">六自由度给定位移</s:String>
+    <s:String x:Key="ResultChannelTypeCurrentLocation">自由度正解</s:String>
+    <s:String x:Key="ResultChannelTypeAcceleration">加速度</s:String>
+    <s:String x:Key="ResultChannelTypeExternalInput">外部输入</s:String>
+    <s:String x:Key="ResultChannelTypeNone">未使用</s:String>
+    <s:String x:Key="DisplacementFeedforwardGain">位移前馈增益</s:String>
     <s:String x:Key="UpTable">台面上升</s:String>
     <s:String x:Key="DownTable">台面下降</s:String>
     <s:String x:Key="TablePostion">台面微调</s:String>
     <s:String x:Key="Negative">负极性</s:String>
     <s:String x:Key="DataCatalog">数据目录</s:String>
+    <s:String x:Key="ChannelRepeat">通道重复</s:String>
     <s:String x:Key="SaveDataCatalog">数据保存目录</s:String>
     <s:String x:Key="SelectDataCatalog">选择数据保存目录</s:String>
     <s:String x:Key="ServoValvePolarity">伺服阀极性</s:String>
     <s:String x:Key="ServoValveConfig">伺服阀</s:String>
     <s:String x:Key="ServoValveOffset">伺服阀直偏</s:String>
+    <s:String x:Key="ServoValveOpenLoop">伺服开环</s:String>
     <s:String x:Key="ServoValveIndex">序号</s:String>
     <s:String x:Key="OffsetVoltage">直偏电压(V)</s:String>
     <s:String x:Key="OpenLoopVoltage">开环驱动电压</s:String>
@@ -29,8 +57,11 @@
     <s:String x:Key="DisplacementSensor">位移传感器</s:String>
     <s:String x:Key="DisplacementSensitivity">灵敏度</s:String>
     <s:String x:Key="DisplacementBias">偏置电压</s:String>
+    <s:String x:Key="Bias">偏置</s:String>
     <s:String x:Key="AccelerationSensor">加速度传感器</s:String>
     <s:String x:Key="AccelerationSensitivity">灵敏度</s:String>
+    <s:String x:Key="SensitivityIsZero">灵敏度为0</s:String>
+    <s:String x:Key="OutSignalGainIsZero">外部输入增益为0</s:String>
     <s:String x:Key="Sensitivity">灵敏度</s:String>
     <s:String x:Key="SecurityConfig">安全参数</s:String>
     <s:String x:Key="MaxOutInput">最大外部输入</s:String>
@@ -39,7 +70,7 @@
     <s:String x:Key="Acceleration">加速度</s:String>
     <s:String x:Key="MaxVelocity">最大速度</s:String>
     <s:String x:Key="MaxDisplacement">最大位移</s:String>
-    <s:String x:Key="MaxDriver">最大驱动</s:String>
+    <s:String x:Key="MaxDriver">最大驱动电压</s:String>
     <s:String x:Key="ValvePower">阀上电</s:String>
     <s:String x:Key="MaxJitterAcceleration">最大跳动加速度</s:String>
     <s:String x:Key="MaxJitterDisplacement">最大跳动位移</s:String>
@@ -89,7 +120,6 @@
     <s:String x:Key="PromptNo">否</s:String>
     <s:String x:Key="Confirm">确认</s:String>
     <s:String x:Key="AskDisconnect">是否断开连接?</s:String>
-    <s:String x:Key="Save">保存</s:String>
     <s:String x:Key="Cancel">取消</s:String>
     <s:String x:Key="GetConfigError">获取参数失败</s:String>
     <s:String x:Key="PromptExitMsg">是否退出程序?</s:String>
@@ -145,7 +175,7 @@
     <s:String x:Key="SweepSpectrum">扫频谱</s:String>
     <s:String x:Key="SweepLimit">限制</s:String>
     <s:String x:Key="SweepSchedule">计划表</s:String>
-    <s:String x:Key="Item">项目</s:String>
+    <s:String x:Key="ItemName">项目</s:String>
     <s:String x:Key="Load">负载</s:String>
     <s:String x:Key="MaxLoad">最大能力</s:String>
     <s:String x:Key="Synthesis">合成</s:String>
@@ -256,6 +286,8 @@
     <s:String x:Key="FrameXTitle">帧数</s:String>
 
 
+    <s:String x:Key="AI">输入通道</s:String>
+    <s:String x:Key="AO">输出通道</s:String>
     <s:String x:Key="SignalValue">信号值</s:String>
     <s:String x:Key="GivenDisplacementSignal">给定位移</s:String>
     <s:String x:Key="MeasuredDisplacementSignal">实测位移</s:String>
@@ -265,6 +297,10 @@
     <s:String x:Key="OutInputSignal">外部输入</s:String>
     <s:String x:Key="ValveDriveSignal">阀驱动</s:String>
     <s:String x:Key="ValvePressure">阀压力</s:String>
+    <s:String x:Key="DifferentialPressure">压差</s:String>
+    <s:String x:Key="Vertical">竖直缸</s:String>
+    <s:String x:Key="Horizontal">水平缸</s:String>
+    <s:String x:Key="Balancing">平衡缸</s:String>
     <s:String x:Key="ValveDriveSignal1">阀驱动1</s:String>
     <s:String x:Key="ValveDriveSignal2">阀驱动2</s:String>
     <s:String x:Key="ValveDriveSignal3">阀驱动3</s:String>

+ 13 - 0
Client/OilSourceControl/OilSourceControl/View/OilControlView.axaml

@@ -15,6 +15,18 @@
     x:DataType="vm:OilSourceStatusViewModel"
     DataContext="{Binding Source={x:Static vm:OilSourceStatusViewModel.Instance}}"
     mc:Ignorable="d">
+
+    <UserControl.Styles>
+        <Style Selector="view|LEDControl">
+            <Setter Property="Foreground" Value="Green" />
+        </Style>
+        <Style Selector="view|LEDControl.Error">
+            <Setter Property="Foreground" Value="Red" />
+        </Style>
+        <Style Selector="view|LEDControl.Warn">
+            <Setter Property="Foreground" Value="Yellow" />
+        </Style>
+    </UserControl.Styles>
     <UserControl.IsEnabled>
         <MultiBinding Converter="{x:Static convert:MutliBoolConverter.Instance}">
             <Binding Path="IsConnect" />
@@ -23,6 +35,7 @@
         </MultiBinding>
     </UserControl.IsEnabled>
     <Grid>
+
         <Grid.RowDefinitions>
             <RowDefinition />
             <RowDefinition Height="auto" />

+ 1 - 0
Client/OilSourceControl/OilSourceControl/ViewModel/OilSourceStatusViewModel.cs

@@ -23,6 +23,7 @@ namespace OilSourceControl.ViewModel
         public override double Width => 1300;
         public override double Height => 800;
         public override bool CanResize => false;
+        public override string IconKey => nameof(IconResourceValueViewModel.OilSourceGeometry);
         private OilSourceStatusViewModel()
         {
             Title = nameof(LanguageValueViewModel.MenuOilSourceControl);

+ 2 - 7
Dynamicloadsimulationdevice.sln

@@ -83,8 +83,6 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Avalonia.Xaml.Interactions.
 EndProject
 Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Avalonia.Xaml.Interactivity", "Client\Avalonia\Avalonia.Xaml.Interactivity\Avalonia.Xaml.Interactivity.shproj", "{3279E103-E03F-4D4D-A6EE-27250C33BAA7}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SukiUI", "Client\Avalonia\SukiUI\SukiUI.csproj", "{E018E4AB-D763-E3D4-F152-EACAF3BB7B08}"
-EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OxyPlot", "OxyPlot", "{78E8DB5C-DF86-44EF-8AAB-517CAD379EA5}"
 EndProject
 Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "OxyPlot", "Client\OxyPlot\OxyPlot\OxyPlot.shproj", "{C0C33A23-E2BA-4F04-83B3-2D512DB0A46C}"
@@ -103,7 +101,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LanguageSourceGenerator", "
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dynamicloadsimulationdevice", "Client\Dynamicloadsimulationdevice\Dynamicloadsimulationdevice.csproj", "{3CABFF42-E9F7-F3EE-D00A-B2767C0A2C38}"
 	ProjectSection(ProjectDependencies) = postProject
+		{22424D07-6DA7-3638-E4A2-1B8665DF67D7} = {22424D07-6DA7-3638-E4A2-1B8665DF67D7}
 		{4D46BC91-0A6A-8259-D8D4-39AE3BB4EDDA} = {4D46BC91-0A6A-8259-D8D4-39AE3BB4EDDA}
+		{C417CFFB-A633-FA0A-35FF-D96126D3D389} = {C417CFFB-A633-FA0A-35FF-D96126D3D389}
 	EndProjectSection
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IconResource", "Avalonia\IconResource\IconResource.csproj", "{19F1BC1D-AA34-4197-928B-F28FE4EAA146}"
@@ -210,10 +210,6 @@ Global
 		{0E712C66-8FE3-DDCB-E424-D60028BD1800}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{0E712C66-8FE3-DDCB-E424-D60028BD1800}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{0E712C66-8FE3-DDCB-E424-D60028BD1800}.Release|Any CPU.Build.0 = Release|Any CPU
-		{E018E4AB-D763-E3D4-F152-EACAF3BB7B08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{E018E4AB-D763-E3D4-F152-EACAF3BB7B08}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{E018E4AB-D763-E3D4-F152-EACAF3BB7B08}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{E018E4AB-D763-E3D4-F152-EACAF3BB7B08}.Release|Any CPU.Build.0 = Release|Any CPU
 		{435CAE37-384F-EFC1-99CE-5BE44BD7AA8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{435CAE37-384F-EFC1-99CE-5BE44BD7AA8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{435CAE37-384F-EFC1-99CE-5BE44BD7AA8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -329,7 +325,6 @@ Global
 		{8C66CBF0-CCB2-48FF-9A9B-E39CCC5A68B7} = {7216AA3D-DA63-49CA-B673-54A7A88441B3}
 		{33C2C0A8-F484-4F92-8DE8-71FF1AAEC529} = {7216AA3D-DA63-49CA-B673-54A7A88441B3}
 		{3279E103-E03F-4D4D-A6EE-27250C33BAA7} = {7216AA3D-DA63-49CA-B673-54A7A88441B3}
-		{E018E4AB-D763-E3D4-F152-EACAF3BB7B08} = {7216AA3D-DA63-49CA-B673-54A7A88441B3}
 		{78E8DB5C-DF86-44EF-8AAB-517CAD379EA5} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
 		{C0C33A23-E2BA-4F04-83B3-2D512DB0A46C} = {78E8DB5C-DF86-44EF-8AAB-517CAD379EA5}
 		{435CAE37-384F-EFC1-99CE-5BE44BD7AA8A} = {78E8DB5C-DF86-44EF-8AAB-517CAD379EA5}

BIN
Fpga/FPGA/Fpga_Main.vi


+ 1 - 1
Fpga/fpga.aliases

@@ -2,4 +2,4 @@
 RT CompactRIO终端 = "0.0.0.0"
 
 [我的电脑]
-我的电脑 = "192.168.176.1"
+我的电脑 = "192.168.1.132"

+ 8 - 7
Fpga/fpga.lvproj

@@ -2682,6 +2682,7 @@
 						<Item Name="FxpSim.dll" Type="Document" URL="/&lt;vilib&gt;/rvi/FXPMathLib/sim/FxpSim.dll"/>
 						<Item Name="Clear Errors.vi" Type="VI" URL="/&lt;vilib&gt;/Utility/error.llb/Clear Errors.vi"/>
 						<Item Name="LVFixedPointOverflowPolicyTypeDef.ctl" Type="VI" URL="/&lt;vilib&gt;/fxp/LVFixedPointOverflowPolicyTypeDef.ctl"/>
+						<Item Name="lvSimController.dll" Type="Document" URL="/&lt;vilib&gt;/rvi/Simulation/lvSimController.dll"/>
 					</Item>
 				</Item>
 				<Item Name="程序生成规范" Type="Build">
@@ -2699,10 +2700,10 @@
 						<Property Name="Comp.Version.Minor" Type="Int">0</Property>
 						<Property Name="Comp.VersionAutoIncrement" Type="Bool">false</Property>
 						<Property Name="Comp.Vivado.EnableMultiThreading" Type="Bool">true</Property>
-						<Property Name="Comp.Vivado.OptDirective" Type="Str"></Property>
-						<Property Name="Comp.Vivado.PhysOptDirective" Type="Str"></Property>
-						<Property Name="Comp.Vivado.PlaceDirective" Type="Str"></Property>
-						<Property Name="Comp.Vivado.RouteDirective" Type="Str"></Property>
+						<Property Name="Comp.Vivado.OptDirective" Type="Str">Default</Property>
+						<Property Name="Comp.Vivado.PhysOptDirective" Type="Str">Default</Property>
+						<Property Name="Comp.Vivado.PlaceDirective" Type="Str">Default</Property>
+						<Property Name="Comp.Vivado.RouteDirective" Type="Str">Default</Property>
 						<Property Name="Comp.Vivado.RunPowerOpt" Type="Bool">false</Property>
 						<Property Name="Comp.Vivado.Strategy" Type="Str">Default</Property>
 						<Property Name="Comp.Xilinx.DesignStrategy" Type="Str">balanced</Property>
@@ -2712,10 +2713,10 @@
 						<Property Name="Comp.Xilinx.SynthGoal" Type="Str">speed</Property>
 						<Property Name="Comp.Xilinx.UseRecommended" Type="Bool">true</Property>
 						<Property Name="DefaultBuildSpec" Type="Bool">true</Property>
-						<Property Name="DestinationDirectory" Type="Path">/E/程序/源代码/c#/Sixdegreeoffreedomdynamicloadsimulationdevice/Service/ShakerService</Property>
-						<Property Name="NI.LV.FPGA.LastCompiledBitfilePath" Type="Path">/E/程序/源代码/c#/Sixdegreeoffreedomdynamicloadsimulationdevice/Service/ShakerService/Shaker.lvbitx</Property>
+						<Property Name="DestinationDirectory" Type="Path">/D/SourceCode/c#/Sixdegreeoffreedomdynamicloadsimulationdevice/Service/ShakerService</Property>
+						<Property Name="NI.LV.FPGA.LastCompiledBitfilePath" Type="Path">/D/SourceCode/c#/Sixdegreeoffreedomdynamicloadsimulationdevice/Service/ShakerService/Shaker.lvbitx</Property>
 						<Property Name="NI.LV.FPGA.LastCompiledBitfilePathRelativeToProject" Type="Path"></Property>
-						<Property Name="ProjectPath" Type="Path">/E/程序/源代码/c#/Sixdegreeoffreedomdynamicloadsimulationdevice/Fpga/fpga.lvproj</Property>
+						<Property Name="ProjectPath" Type="Path">/D/SourceCode/c#/Sixdegreeoffreedomdynamicloadsimulationdevice/Fpga/fpga.lvproj</Property>
 						<Property Name="RelativePath" Type="Bool">false</Property>
 						<Property Name="RunWhenLoaded" Type="Bool">true</Property>
 						<Property Name="SupportDownload" Type="Bool">true</Property>

+ 4 - 4
NIFPGA/Fifo.cs

@@ -80,13 +80,13 @@ namespace NIFPGA
 
         private NiFpga_FifoFlowControl GetFlowControl()
         {
-            int value = 0;
-            _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_GetFifoPropertyI32>()(_Session.Session, FifoSession, Interop.NiFpga_FifoProperty.NiFpga_FifoProperty_FlowControl, ref value));
-            return (NiFpga_FifoFlowControl)value;
+            NiFpga_FifoFlowControl value = 0;
+            _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_GetFifoPropertyI32>()(_Session.Session, FifoSession, Interop.NiFpga_FifoProperty.NiFpga_FifoProperty_FlowControl, ref Unsafe.As<NiFpga_FifoFlowControl,int>(ref value)));
+            return value;
         }
         private void SetFlowControl(NiFpga_FifoFlowControl flowControl)
         {
-            _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_SetFifoPropertyI32>()(_Session.Session, FifoSession, Interop.NiFpga_FifoProperty.NiFpga_FifoProperty_FlowControl, (int)flowControl));
+            _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_SetFifoPropertyI32>()(_Session.Session, FifoSession, Interop.NiFpga_FifoProperty.NiFpga_FifoProperty_FlowControl,Unsafe.As<NiFpga_FifoFlowControl,int>(ref flowControl)));
         }
         public void Start()
         {

+ 1 - 0
NIFPGA/NIFPGA.Interop.cs

@@ -273,6 +273,7 @@ namespace NIFPGA
         [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate NiFpga_Status NiFpgaDll_AcquireFifoReadElementsU16(uint session, uint fifo, ref System.IntPtr elements, [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.U4)] uint elementsRequested, uint timeout, ref uint elementsAcquired, ref uint elementsRemaining);
 
         [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate NiFpga_Status NiFpgaDll_AcquireFifoReadElementsI32(uint session, uint fifo, ref System.IntPtr elements, [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.U4)] uint elementsRequested, uint timeout, ref uint elementsAcquired, ref uint elementsRemaining);
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate NiFpga_Status NiFpgaDll_AcquireFifoReadElementsU32(uint session, uint fifo, ref System.IntPtr elements, [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.U4)] uint elementsRequested, uint timeout, ref uint elementsAcquired, ref uint elementsRemaining);
 
         [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate NiFpga_Status NiFpgaDll_AcquireFifoReadElementsI64(uint session, uint fifo, ref System.IntPtr elements, [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.U4)] uint elementsRequested, uint timeout, ref uint elementsAcquired, ref uint elementsRemaining);
         

+ 62 - 0
NIFPGA/ReadFifo.cs

@@ -60,6 +60,49 @@ namespace NIFPGA
                 }
             }
         }
+        public void ReadAcquire(uint count, uint timeout,ref uint elementsAcquired, ref uint elementsRemaining,ref nint ptr)
+        {
+            lock (_Lock)
+            {
+                T value = default;
+                switch (value)
+                {
+                    case bool:
+                        _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_AcquireFifoReadElementsBool>()(_Session.Session, FifoSession, ref ptr, count, timeout,ref elementsAcquired, ref elementsRemaining));
+                        break;
+                    case byte:
+                        _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_AcquireFifoReadElementsU8>()(_Session.Session, FifoSession, ref ptr, count, timeout,ref elementsAcquired, ref elementsRemaining));
+                        break;
+                    case sbyte:
+                        _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_AcquireFifoReadElementsI8>()(_Session.Session, FifoSession, ref ptr, count, timeout,ref elementsAcquired, ref elementsRemaining));
+                        break;
+                    case ushort:
+                        _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_AcquireFifoReadElementsU16>()(_Session.Session, FifoSession, ref ptr, count, timeout,ref elementsAcquired, ref elementsRemaining));
+                        break;
+                    case short:
+                        _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_AcquireFifoReadElementsI16>()(_Session.Session, FifoSession, ref ptr, count, timeout,ref elementsAcquired, ref elementsRemaining));
+                        break;
+                    case int:
+                        _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_AcquireFifoReadElementsI32>()(_Session.Session, FifoSession, ref ptr, count, timeout,ref elementsAcquired, ref elementsRemaining));
+                        break;
+                    case uint:
+                        _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_AcquireFifoReadElementsU32>()(_Session.Session, FifoSession, ref ptr, count, timeout,ref elementsAcquired, ref elementsRemaining));
+                        break;
+                    case long:
+                        _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_AcquireFifoReadElementsI64>()(_Session.Session, FifoSession, ref ptr, count, timeout,ref elementsAcquired, ref elementsRemaining));
+                        break;
+                    case ulong:
+                        _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_AcquireFifoReadElementsU64>()(_Session.Session, FifoSession, ref ptr, count, timeout,ref elementsAcquired, ref elementsRemaining));
+                        break;
+                    case float:
+                        _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_AcquireFifoReadElementsSgl>()(_Session.Session, FifoSession, ref ptr, count, timeout,ref elementsAcquired, ref elementsRemaining));
+                        break;
+                    case double:
+                        _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_AcquireFifoReadElementsDbl>()(_Session.Session, FifoSession, ref ptr, count, timeout,ref elementsAcquired, ref elementsRemaining));
+                        break;
+                }
+            }
+        }
         public uint ElementsRemaining
         {
             get
@@ -95,5 +138,24 @@ namespace NIFPGA
                 _Convert.FxpConvertToFloat(ref temp[0], _TypeInfo, ref value, count);
             }
         }
+        public unsafe void ReadAcquire(ref float value, uint count, uint timeout, ref uint elementsRemaining)
+        {
+            if (count == 0)
+            {
+                ulong[] temp = new ulong[1];
+                base.Read(ref temp[0], count, timeout, ref elementsRemaining);
+            }
+            else
+            {
+                nint ptr = 0;
+                uint c = 0;
+                base.ReadAcquire(count, timeout,ref c, ref elementsRemaining, ref ptr);
+                if (ptr != 0 && c!=0)
+                {
+                    _Convert.FxpConvertToFloat(ref Unsafe.AsRef<ulong>((void*)ptr), _TypeInfo, ref value, count);
+                    _Session.CheckResult(_Session.GetDelegate<Interop.NiFpgaDll_ReleaseFifoElements>()(_Session.Session, FifoSession, c));
+                }
+            }
+        }
     }
 }

+ 1 - 0
Service/ShakerFpga/ShakerFpga.csproj

@@ -5,6 +5,7 @@
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
     <LangVersion>latest</LangVersion>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
   </PropertyGroup>
 

+ 1 - 1
Service/ShakerService/Service.ReadFifo.cs

@@ -17,7 +17,7 @@ namespace ShakerService
             {
                 if(ShakerFpga.ShakerFpga.Instance.波形数据.ElementsRemaining>=perframelen)
                 {
-                    ShakerFpga.ShakerFpga.Instance.波形数据.Read(ref ShakerDataCache.Instance.GetDataCacheref(0), (uint)perframelen, 100, ref readlen);
+                    ShakerFpga.ShakerFpga.Instance.波形数据.ReadAcquire(ref ShakerDataCache.Instance.GetDataCacheref(0), (uint)perframelen, 100, ref readlen);
                     SendData(ShakerDataCache.Instance.DataCache, (uint)ShakerDataCache.Instance.RowCount, (uint)ShakerDataCache.Instance.ColnumCount);
                     break;
                 }

ファイルの差分が大きいため隠しています
+ 36778 - 60038
Service/ShakerService/Shaker.lvbitx


+ 1 - 1
Service/ShakerService/ViewModel/ShakerConfigViewModel.cs

@@ -91,7 +91,7 @@ namespace ShakerService.ViewModel
         /// <summary>
         /// fifo中返回数据定义
         /// </summary>
-        public int MaxResultChannelCount => CurrentModel.MaxResultChannelCount;
+        public byte MaxResultChannelCount => CurrentModel.MaxFifoChannelCount;
         private protected override void ReadModel()
         {
             base.ReadModel();

+ 5 - 0
Shaker.Model/AIChannelType.cs

@@ -14,22 +14,27 @@ namespace Shaker.Model
         /// <summary>
         /// 位移
         /// </summary>
+        [ResultChannelUnit("mm")]
         Displacement,
         /// <summary>
         /// 加速度
         /// </summary>
+        [ResultChannelUnit("g")]
         Acceleration,
         /// <summary>
         /// 外部输入
         /// </summary>
+        [ResultChannelUnit("mm")]
         OutSignal,
         /// <summary>
         /// 压差
         /// </summary>
+        [ResultChannelUnit("MPa")]
         DifferentialPressure,
         /// <summary>
         /// 压力
         /// </summary>
+        [ResultChannelUnit("MPa")]
         Pressure
     }
 }

+ 7 - 0
Shaker.Model/AOChannelType.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
@@ -11,14 +12,20 @@ namespace Shaker.Model
         /// <summary>
         /// 水平缸
         /// </summary>
+        [ResultChannelUnit("V")]
+        [Description("Horizontal")]
         Horizontal,
         /// <summary>
         /// 垂直缸
         /// </summary>
+        [ResultChannelUnit("V")]
+        [Description("Vertical")]
         Vertical,
         /// <summary>
         /// 平衡缸
         /// </summary>
+        [ResultChannelUnit("V")]
+        [Description("Balancing")]
         Balancing,
     }
 }

+ 21 - 4
Shaker.Model/Models/AIConfigModel.cs

@@ -19,21 +19,38 @@ namespace Shaker.Models
         /// </summary>
         public double Sensitivity = 100;
         /// <summary>
+        /// 最大灵敏度
+        /// </summary>
+        public double MaxSensitivity = double.MaxValue;
+        /// <summary>
+        /// 最小灵敏度
+        /// </summary>
+        public double MinSensitivity = double.MinValue;
+        /// <summary>
         /// 偏置
         ///<para> 当<see cref="ChannelType"/> ==<see cref="AIChannelType.Acceleration"/>和<see cref="AIChannelType.OutSignal"/>无效</para>
         /// </summary>
         public double Bias = 0;
         /// <summary>
-        /// 模拟通道类型
+        /// 最大偏置
         /// </summary>
-        public AIChannelType ChannelType = AIChannelType.Displacement;
+        public double MaxBias = double.MaxValue;
         /// <summary>
-        /// 单位
+        /// 最小偏置
         /// </summary>
-        public string Unit = "";
+        public double MinBias = double.MinValue;
+        /// <summary>
+        /// 模拟通道类型
+        /// </summary>
+        public AIChannelType ChannelType = AIChannelType.Displacement;
+
         public override object Clone()
         {
             return this.CloneBase();
         }
+        public override string ToString()
+        {
+            return $"{ChannelType} {Channel}";
+        }
     }
 }

+ 39 - 1
Shaker.Model/Models/AOConfigModel.cs

@@ -10,16 +10,54 @@ namespace Shaker.Models
 {
     public sealed class AOConfigModel:BaseModel
     {
+        /// <summary>
+        /// 极性
+        /// </summary>
         public Shaker.Model.Polarity Polarity = Polarity.Positive;
+        /// <summary>
+        /// 开环
+        /// </summary>
         public bool OpenLoop = false;
+        /// <summary>
+        /// 开环驱动
+        /// </summary>
         public double OpenLoopDriver = 0;
+        /// <summary>
+        /// 最大开环驱动
+        /// </summary>
+        public double MaxOpenLoopDriver = double.MaxValue;
+        /// <summary>
+        /// 最小开环驱动
+        /// </summary>
+        public double MinOpenLoopDriver = double.MinValue;
+        /// <summary>
+        /// 通道序号
+        /// </summary>
         public AOChannel Channel = AOChannel.Channel0;
+        /// <summary>
+        /// 偏置
+        /// </summary>
         public double Bias = 0;
+        /// <summary>
+        /// 最大偏置
+        /// </summary>
+        public double MaxBias = double.MaxValue;
+        /// <summary>
+        /// 最小偏置
+        /// </summary>
+        public double MinBias = double.MinValue;
+        /// <summary>
+        /// 通道类型
+        /// </summary>
         public AOChannelType ChannelType = AOChannelType.Horizontal;
-        public string Unit = "";
+
         public override object Clone()
         {
             return this.CloneBase();
         }
+        public override string ToString()
+        {
+            return ChannelType != AOChannelType.Balancing ? (OpenLoop ? $"{ChannelType} {Channel} {Polarity} {nameof(Bias)}:{Bias} {nameof(OpenLoop)}{OpenLoop}" : $"{ChannelType} {Channel} {Polarity} {nameof(Bias)}:{Bias}") : $"{ChannelType} {Channel}";
+        }
     }
 }

+ 22 - 0
Shaker.Model/Models/PIModel.cs

@@ -9,8 +9,30 @@ namespace Shaker.Models
 {
     public sealed class PIModel:BaseModel
     {
+        /// <summary>
+        /// P参数
+        /// </summary>
         public double P = 0;
+        /// <summary>
+        /// I参数
+        /// </summary>
         public double I = 0;
+        /// <summary>
+        /// 最大P值
+        /// </summary>
+        public double MaxP = double.MaxValue;
+        /// <summary>
+        /// 最小P值
+        /// </summary>
+        public double MinP = double.MinValue;
+        /// <summary>
+        /// 最大I值
+        /// </summary>
+        public double MaxI = double.MaxValue;
+        /// <summary>
+        /// 最小I值
+        /// </summary>
+        public double MinI = double.MinValue;
         public override object Clone()
         {
             return this.CloneBase();

+ 29 - 0
Shaker.Model/Models/ResultChannelModel.cs

@@ -0,0 +1,29 @@
+using IModel;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Shaker.Models
+{
+    public sealed class ResultChannelModel : IModel.BaseModel
+    {
+        /// <summary>
+        /// 通道定义
+        /// </summary>
+        public Shaker.Model.ResultChannel Channel = Model.ResultChannel.Channal0;
+        /// <summary>
+        /// 通道类型
+        /// </summary>
+        public Shaker.Model.ResultChannelType ChannelType = Model.ResultChannelType.None;
+        public override object Clone()
+        {
+            return this.CloneBase(); ;
+        }
+        public override string ToString()
+        {
+            return Channel.ToString();
+        }
+    }
+}

+ 57 - 0
Shaker.Model/Models/ServoConfigModel.cs

@@ -15,30 +15,87 @@ namespace Shaker.Models
         /// </summary>
         public double HorizontalBarrierPotential = 0;
         /// <summary>
+        /// 最大水平阀死区电压(V)
+        /// </summary>
+        public double MaxHorizontalBarrierPotential = double.MaxValue;
+        /// <summary>
+        /// 最小水平阀死区电压(V)
+        /// </summary>
+        public double MinHorizontalBarrierPotential = double.MinValue;
+        /// <summary>
         /// 竖直阀死区电压(V)
         /// </summary>
         public double VerticalBarrierPotential = 0;
         /// <summary>
+        /// 最大竖直阀死区电压(V)
+        /// </summary>
+        public double MaxVerticalBarrierPotential = double.MaxValue;
+        /// <summary>
+        /// 最小竖直阀死区电压(V)
+        /// </summary>
+        public double MinVerticalBarrierPotential = double.MinValue;
+        /// <summary>
         /// 最大积分电压(V)
         /// </summary>
         public double MaxIntegratedVoltage = 2;
         /// <summary>
+        /// 最大最大积分电压(V)
+        /// </summary>
+        public double MaxMaxIntegratedVoltage = double.MaxValue;
+        /// <summary>
+        /// 最小最大积分电压(V)
+        /// </summary>
+        public double MinMaxIntegratedVoltage = 0;
+        /// <summary>
         /// 位移前馈增益
         /// </summary>
 
         public double DisplacementFeedforwardGain = 1;
         /// <summary>
+        /// 最大位移前馈增益
+        /// </summary>
+        public double MaxDisplacementFeedforwardGain = double.MaxValue;
+        /// <summary>
+        /// 最小位移前馈增益
+        /// </summary>
+        public double MinDisplacementFeedforwardGain = double.MinValue;
+        /// <summary>
+        /// <summary>
         /// 最大驱动电压(V)
         /// </summary>
         public double MaxDriverVoltage = 8;
         /// <summary>
+        /// 最大最大驱动电压(V)
+        /// </summary>
+        public double MaxMaxDriverVoltage = double.MaxValue;
+        /// <summary>
+        /// 最小最大驱动电压(V)
+        /// </summary>
+        public double MinMaxDriverVoltage = 0;
+        /// <summary>
         /// 驱动超限电压(V)
         /// </summary>
         public double DriverOverLimitVoltage = 2;
         /// <summary>
+        /// 最大驱动超限电压(V)
+        /// </summary>
+        public double MaxDriverOverLimitVoltage = double.MaxValue;
+        /// <summary>
+        /// 最小驱动超限电压(V)
+        /// </summary>
+        public double MinDriverOverLimitVoltage = 0;
+        /// <summary>
         /// 急停后驱动限幅值(V)
         /// </summary>
         public double EmerhencyDriverLimitVoltage = 1.2;
+        /// <summary>
+        /// 最大急停后驱动限幅值(V)
+        /// </summary>
+        public double MaxEmerhencyDriverLimitVoltage = double.MaxValue;
+        /// <summary>
+        /// 最小急停后驱动限幅值(V)
+        /// </summary>
+        public double MinEmerhencyDriverLimitVoltage = 0;
         public override object Clone()
         {
             return this.CloneBase();

+ 1 - 1
Shaker.Model/Models/ShakerChannelConfigModel.cs

@@ -47,7 +47,7 @@ namespace Shaker.Models
         /// 结果通道
         /// </summary>
 
-        public List<ResultChannelType> ResultChannels = new List<ResultChannelType>();
+        public List<ResultChannelModel> ResultChannels = new List<ResultChannelModel>();
         public override object Clone()
         {
             return this.CloneBase();

+ 49 - 1
Shaker.Model/Models/ShakerConfigModel.cs

@@ -75,7 +75,55 @@ namespace Shaker.Models
         /// Fpga主时钟
         /// </summary>
         public uint FpgaClock = 40_000_000;
-        public int MaxResultChannelCount = 64;
+
+        /// <summary>
+        /// fifo中数据通道数
+        /// </summary>
+        public byte MaxFifoChannelCount = 64;
+        /// <summary>
+        /// 实际位移通道数
+        /// </summary>
+        public byte MaxActualDisplacement = 8;
+        /// <summary>
+        /// 给定位移通道数
+        /// </summary>
+        public byte MaxGivenDisplacement = 8;
+        /// <summary>
+        /// 水平缸驱动通道数
+        /// </summary>
+        public byte MaxHorizontalCylinderDrive = 4;
+        /// <summary>
+        /// 垂直缸驱动通道数
+        /// </summary>
+        public byte MaxVerticalCylinderDrive = 4;
+        /// <summary>
+        /// 平衡缸驱动通道数
+        /// </summary>
+        public byte MaxBalancingCylinderDrive = 4;
+        /// <summary>
+        /// 压差通道数
+        /// </summary>
+        public byte MaxDifferentialPressure = 8;
+        /// <summary>
+        /// 压力通道数
+        /// </summary>
+        public byte MaxSupportingPressure = 4;
+        /// <summary>
+        /// 六自由度给定位移
+        /// </summary>
+        public byte MaxSixFreedomsGivenDisplacement = 6;
+        /// <summary>
+        /// 当前位置通道数
+        /// </summary>
+        public byte MaxCurrentLocation = 6;
+        /// <summary>
+        /// 实际加速度通道数
+        /// </summary>
+        public byte MaxAcceleration = 3;
+        /// <summary>
+        /// 外部输入通道数
+        /// </summary>
+        public byte MaxOutSignal = 3;
         public uint MaxRiseCount = 6000;
         public uint MaxDropCount = 6000;
         public uint MaxZeroChangedCount = 6000;

+ 106 - 0
Shaker.Model/ResultChannel.cs

@@ -0,0 +1,106 @@
+namespace Shaker.Model
+{
+    public enum ResultChannel
+    {
+        Channal0,
+        Channal1,
+        Channal2,
+        Channal3,
+        Channal4,
+        Channal5,
+        Channal6,
+        Channal7,
+        Channal8,
+        Channal9,
+        Channal10,
+        Channal11,
+        Channal12,
+        Channal13,
+        Channal14,
+        Channal15,
+        Channal16,
+        Channal17,
+        Channal18,
+        Channal19,
+        Channal20,
+        Channal21,
+        Channal22,
+        Channal23,
+        Channal24,
+        Channal25,
+        Channal26,
+        Channal27,
+        Channal28,
+        Channal29,
+        Channal30,
+        Channal31,
+        Channal32,
+        Channal33,
+        Channal34,
+        Channal35,
+        Channal36,
+        Channal37,
+        Channal38,
+        Channal39,
+        Channal40,
+        Channal41,
+        Channal42,
+        Channal43,
+        Channal44,
+        Channal45,
+        Channal46,
+        Channal47,
+        Channal48,
+        Channal49,
+        Channal50,
+        Channal51,
+        Channal52,
+        Channal53,
+        Channal54,
+        Channal55,
+        Channal56,
+        Channal57,
+        Channal58,
+        Channal59,
+        Channal60,
+        Channal61,
+        Channal62,
+        Channal63,
+        Channal64,
+        Channal65,
+        Channal66,
+        Channal67,
+        Channal68,
+        Channal69,
+        Channal70,
+        Channal71,
+        Channal72,
+        Channal73,
+        Channal74,
+        Channal75,
+        Channal76,
+        Channal77,
+        Channal78,
+        Channal79,
+        Channal80,
+        Channal81,
+        Channal82,
+        Channal83,
+        Channal84,
+        Channal85,
+        Channal86,
+        Channal87,
+        Channal88,
+        Channal89,
+        Channal90,
+        Channal91,
+        Channal92,
+        Channal93,
+        Channal94,
+        Channal95,
+        Channal96,
+        Channal97,
+        Channal98,
+        Channal99,
+    }
+}

+ 36 - 8
Shaker.Model/ResultChannelType.cs

@@ -15,52 +15,80 @@ namespace Shaker.Model
         /// <summary>
         /// 实际位移(mm)
         /// </summary>
+        [ResultChannelUnitAttribute("mm")]
         [Description("ResultChannelTypActualDisplacement")]
         ActualDisplacement,
         /// <summary>
         /// 给定位移(mm)
         /// </summary>
+        [ResultChannelUnitAttribute("mm")]
         [Description("ResultChannelTypeGivenDisplacement")]
         GivenDisplacement,
         /// <summary>
-        /// 伺服阀驱动(V)
+        /// 水平缸驱动(V)
         /// </summary>
-        [Description("ResultChannelTypeServoValveDrive")]
-        ServoValveDrive,
+        [ResultChannelUnitAttribute("V")]
+        [Description("ResultChannelTypeHorizontalCylinderDrive")]
+        HorizontalCylinderDrive,
+        /// <summary>
+        /// 竖直缸驱动(V)
+        /// </summary>
+        [ResultChannelUnitAttribute("V")]
+        [Description("ResultChannelTypeVerticalCylinderDrive")]
+        VerticalCylinderDrive,
         /// <summary>
         /// 平衡缸驱动(V)
         /// </summary>
+        [ResultChannelUnitAttribute("V")]
         [Description("ResultChannelTypeBalanceCylinderDrive")]
         BalanceCylinderDrive,
         /// <summary>
-        /// 平衡缸驱动(MPa)
+        /// 压差(MPa)
+        /// </summary>
+        [ResultChannelUnitAttribute("MPa")]
+        [Description("ResultChannelTypeDifferentialPressure")]
+        DifferentialPressure,
+        /// <summary>
+        /// 支撑压力(MPa)
         /// </summary>
-        [Description("ResultChannelTypeSupportingPressureDrive")]
-        SupportingPressureDrive,
+        [ResultChannelUnitAttribute("MPa")]
+        [Description("ResultChannelTypeSupportingPressure")]
+        SupportingPressure,
         /// <summary>
         /// 六自由度给定位移(mm)
         /// </summary>
+        [ResultChannelUnitAttribute("mm")]
         [Description("ResultChannelTypeSixFreedomsGivenDisplacement")]
         SixFreedomsGivenDisplacement,
         /// <summary>
-        /// 当前位置(mm)
+        /// 当前位置/自由度正解(mm)
         /// </summary>
+        [ResultChannelUnitAttribute("mm")]
         [Description("ResultChannelTypeCurrentLocation")]
         CurrentLocation,
         /// <summary>
         /// 加速度(g)
         /// </summary>
+        [ResultChannelUnitAttribute("g")]
         [Description("ResultChannelTypeAcceleration")]
         Acceleration,
         /// <summary>
         /// 外部输入(V)
         /// </summary>
+        [ResultChannelUnitAttribute("V")]
         [Description("ResultChannelTypeExternalInput")]
         ExternalInput,
         /// <summary>
-        /// 未定义
+        /// 未使用
         /// </summary>
+        [ResultChannelUnitAttribute("")]
         [Description("ResultChannelTypeNone")]
         None,
     }
+    [AttributeUsage(AttributeTargets.Field)]
+    public sealed class ResultChannelUnitAttribute : Attribute
+    {
+        public ResultChannelUnitAttribute(string unit)=>Unit = unit;
+        public string Unit { get; }
+    }
 }

+ 11 - 0
Shaker.Model/Tools/Tools.cs

@@ -1,8 +1,11 @@
 using MessagePack;
 using MessagePack.Resolvers;
+using Shaker.Model;
 using System;
 using System.Collections.Generic;
+using System.Data.SqlTypes;
 using System.Linq;
+using System.Runtime.CompilerServices;
 using System.Text;
 using System.Threading.Tasks;
 
@@ -57,5 +60,13 @@ namespace Shaker.Tools
                 }
             }
         }
+        public static string GetUnit<T>(this T e) where T: Enum
+        {
+            if (e == null || !e.GetType().IsEnum) return "";
+            return (e.GetType()?
+                     .GetField(e.ToString())?
+                     .GetCustomAttributes(typeof(ResultChannelUnitAttribute), false)
+                     .FirstOrDefault() as ResultChannelUnitAttribute)?.Unit ?? "";
+        }
     }
 }

+ 1 - 0
Shaker.Model/Topic.cs

@@ -20,5 +20,6 @@ namespace Shaker.Model
 
         public const string MIANWINDOWTOAST = "MainWindowToast";
         public const string DATA = "Data";
+        public const string InitSeries = "InitSeries";
     }
 }

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません