Skip to content

Commit d6c070a

Browse files
author
Marcus Sonestedt
committed
ServoPidControl: Add graph (debug only) + move models to subfolder
1 parent 1db83c3 commit d6c070a

File tree

8 files changed

+219
-34
lines changed

8 files changed

+219
-34
lines changed

ServoPIDControl/ArduinoCom.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Windows;
88
using System.Windows.Threading;
99
using NLog;
10+
using ServoPIDControl.Model;
1011

1112
namespace ServoPIDControl
1213
{
@@ -39,7 +40,7 @@ public class ArduinoCom : IDisposable
3940

4041
private ISerialPort _port;
4142
private readonly StringBuilder _readBuf = new StringBuilder();
42-
private Model _model;
43+
private ViewModel _model;
4344
private readonly DispatcherTimer _timer = new DispatcherTimer {Interval = TimeSpan.FromMilliseconds(250)};
4445
private readonly object _portLock = new object();
4546

@@ -175,7 +176,7 @@ private void LineReceived(string line)
175176
MessageReceived?.Invoke(this, new StringEventArgs {Message = line});
176177
}
177178

178-
public Model Model
179+
public ViewModel Model
179180
{
180181
get => _model;
181182
set

ServoPIDControl/MainWindow.xaml

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,24 @@
44
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
55
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
66
xmlns:controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
7+
xmlns:d3="clr-namespace:InteractiveDataDisplay.WPF;assembly=mpolewaczyk.InteractiveDataDisplay.WPF"
78
xmlns:local="clr-namespace:ServoPIDControl"
9+
xmlns:model="clr-namespace:ServoPIDControl.Model"
810
mc:Ignorable="d"
9-
Title="Arduino Servo PID Control" SizeToContent="WidthAndHeight">
11+
Title="Arduino Servo PID Control">
1012
<Window.DataContext>
11-
<local:Model x:Name="Model" />
13+
<model:ViewModel x:Name="Model" />
1214
</Window.DataContext>
15+
<Window.Resources>
16+
<local:VisibilityToCheckedConverter x:Key="VisibilityToCheckedConverter" />
17+
</Window.Resources>
1318
<Grid>
1419
<Grid.ColumnDefinitions>
1520
<ColumnDefinition Width="Auto" />
16-
<ColumnDefinition Width="Auto" />
21+
<ColumnDefinition Width="1*" />
1722
</Grid.ColumnDefinitions>
1823
<Grid.RowDefinitions>
19-
<RowDefinition Height="1*" />
24+
<RowDefinition Height="Auto" MaxHeight="400" />
2025
<RowDefinition Height="Auto" />
2126
<RowDefinition Height="1*" />
2227
</Grid.RowDefinitions>
@@ -36,7 +41,7 @@
3641
</StackPanel>
3742

3843
<StackPanel Grid.Row="2" Grid.Column="0">
39-
<TextBlock Text="Delta Time:" />
44+
<TextBlock Text="Delta Time:" />
4045
<TextBox x:Name="DeltaTimeTextBox" Text="{Binding DeltaTime, Mode=OneWay, StringFormat=F6}"
4146
IsReadOnly="true"
4247
IsEnabled="{Binding Connected}" TextAlignment="Right" />
@@ -51,7 +56,7 @@
5156
</StackPanel>
5257
<DataGrid Grid.Row="0" Grid.Column="1"
5358
ItemsSource="{Binding Servos}" x:Name="ServosDataGrid"
54-
IsEnabled="{Binding Connected}">
59+
SelectedCellsChanged="ServosDataGrid_OnSelectedCellsChanged">
5560
<DataGrid.Resources>
5661
<Style TargetType="DataGridColumnHeader">
5762
<Setter Property="MinWidth" Value="50" />
@@ -60,17 +65,46 @@
6065
</DataGrid>
6166
<GridSplitter Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
6267
ResizeDirection="Rows" MinHeight="10" />
63-
<RichTextBox x:Name="LogBox" Grid.Row="2" Grid.Column="1" Margin="5"
64-
MinHeight="100" MaxHeight="200" IsReadOnly="True" BorderBrush="{x:Null}"
65-
Foreground="{x:Null}">
66-
<RichTextBox.Resources>
67-
<Style TargetType="{x:Type Paragraph}">
68-
<Setter Property="Margin" Value="1" />
69-
</Style>
70-
</RichTextBox.Resources>
71-
<FlowDocument x:Uid="FlowDocument_1" FontFamily="Lucida Console" FontSize="11"
72-
LineStackingStrategy="BlockLineHeight" IsOptimalParagraphEnabled="True"
73-
PagePadding="5,2"/>
74-
</RichTextBox>
68+
<TabControl x:Name="Tab" Grid.Row="2" Grid.Column="1" Margin="5">
69+
<TabItem Header="Log">
70+
<RichTextBox x:Name="LogBox"
71+
MinHeight="100" IsReadOnly="True" BorderBrush="{x:Null}"
72+
Foreground="{x:Null}">
73+
<RichTextBox.Resources>
74+
<Style TargetType="{x:Type Paragraph}">
75+
<Setter Property="Margin" Value="1" />
76+
</Style>
77+
</RichTextBox.Resources>
78+
<FlowDocument x:Uid="FlowDocument_1" FontFamily="Lucida Console" FontSize="11"
79+
LineStackingStrategy="BlockLineHeight" IsOptimalParagraphEnabled="True"
80+
PagePadding="5,2" />
81+
</RichTextBox>
82+
83+
</TabItem>
84+
<TabItem Header="Chart">
85+
<d3:Chart x:Name="Chart" MinHeight="100">
86+
<d3:Chart.Title>
87+
<TextBlock HorizontalAlignment="Center" FontSize="18" Margin="0"
88+
Text="{Binding Path=CurrentGraphServo.Id, StringFormat=Servo #{0} data}" />
89+
</d3:Chart.Title>
90+
<d3:Chart.LegendContent>
91+
<d3:LegendItemsPanel>
92+
<d3:LegendItemsPanel.Resources>
93+
<DataTemplate x:Key="InteractiveDataDisplay.WPF.LineGraph">
94+
<StackPanel Orientation="Horizontal">
95+
<CheckBox
96+
IsChecked="{Binding Path=Visibility, Converter={StaticResource VisibilityToCheckedConverter}, Mode=TwoWay}" />
97+
<Line Width="15" Height="15" X1="0" Y1="0" X2="15" Y2="15"
98+
Stroke="{Binding Path=Stroke}" StrokeThickness="2" />
99+
<TextBlock Margin="5,0,0,0" Text="{Binding Path=Description}" />
100+
</StackPanel>
101+
</DataTemplate>
102+
</d3:LegendItemsPanel.Resources>
103+
</d3:LegendItemsPanel>
104+
</d3:Chart.LegendContent>
105+
<Grid Name="ChartGrid" />
106+
</d3:Chart>
107+
</TabItem>
108+
</TabControl>
75109
</Grid>
76110
</controls:MetroWindow>

ServoPIDControl/MainWindow.xaml.cs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
using System.ComponentModel;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
24
using System.Linq;
5+
using System.Reflection;
36
using System.Windows;
7+
using System.Windows.Controls;
8+
using System.Windows.Media;
9+
using InteractiveDataDisplay.WPF;
410
using NLog;
511
using NLog.Config;
612
using NLog.Targets.Wrappers;
713
using ServoPIDControl.Annotations;
814
using ServoPIDControl.Helper;
15+
using ServoPIDControl.Model;
916

1017
namespace ServoPIDControl
1118
{
@@ -26,12 +33,40 @@ public MainWindow()
2633
Unloaded += OnUnloaded;
2734

2835
Model.PropertyChanged += ModelOnPropertyChanged;
36+
ServosDataGrid.AutoGeneratedColumns += ServosDataGridOnAutoGeneratedColumns;
2937
}
3038

3139
private void ModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
3240
{
33-
if (e.PropertyName == nameof(Model.ComPorts) && Model.ConnectedPort == null && Model.ComPorts != null)
34-
Model.ConnectedPort = Model.ComPorts.FirstOrDefault();
41+
switch (e.PropertyName)
42+
{
43+
case nameof(Model.ComPorts) when Model.ConnectedPort == null && Model.ComPorts != null:
44+
Model.ConnectedPort = Model.ComPorts.FirstOrDefault();
45+
break;
46+
47+
case nameof(Model.CurrentGraphServo):
48+
ChartGrid.Children.Clear();
49+
if (Model.CurrentGraphServo == null)
50+
break;
51+
52+
// add each series with color based on index
53+
foreach (var (series, i) in Model.CurrentGraphServo.AllTimeSeries.Select((x, i) => (x, i)))
54+
{
55+
var lg = new LineGraph
56+
{
57+
Stroke = new SolidColorBrush(Color.FromArgb(255,
58+
(byte) ((i & 1) != 0 ? 255 : 0),
59+
(byte) ((i & 2) != 0 ? 255 : 0),
60+
(byte) (i == 0 ? 255 : 0))),
61+
Description = series.Name,
62+
};
63+
ChartGrid.Children.Add(lg);
64+
lg.Plot(series.X, series.Y);
65+
}
66+
67+
Chart.XLabelProvider = new LabelProvider();
68+
break;
69+
}
3570
}
3671

3772
private void OnLoaded(object sender, RoutedEventArgs e)
@@ -46,7 +81,7 @@ private void OnLoaded(object sender, RoutedEventArgs e)
4681
// ReSharper disable StringLiteralTypo
4782
Layout = "${processtime} [${level:uppercase=true}] " +
4883
"${logger:shortName=true}: ${message}" +
49-
"${exception:innerFormat=tostring:maxInnerExceptionLevel=10:separator=,:format=tostring}",
84+
"${exception:innerFormat=tostring:maxInnerExceptionLevel=10:separator=,:format=tostring}",
5085
// ReSharper restore StringLiteralTypo
5186
};
5287

@@ -60,12 +95,35 @@ private void OnLoaded(object sender, RoutedEventArgs e)
6095
if (Model.ConnectedPort == null && Model.ComPorts != null)
6196
Model.ConnectedPort = Model.ComPorts.FirstOrDefault();
6297

98+
Model.PropertyChanged += ModelOnPropertyChanged;
99+
63100
Log.Info("Ready!\r\n");
64101
}
65102

103+
private void ServosDataGridOnAutoGeneratedColumns(object sender, EventArgs e)
104+
{
105+
foreach (var name in new[]
106+
{
107+
nameof(ServoPidModel.Times), nameof(ServoPidModel.SetPoints),
108+
nameof(ServoPidModel.Inputs), nameof(ServoPidModel.Outputs),
109+
nameof(ServoPidModel.AllTimeSeries)
110+
})
111+
{
112+
var c = ServosDataGrid.Columns.First(c2 => (string) c2.Header == name);
113+
ServosDataGrid.Columns.Remove(c);
114+
}
115+
}
116+
66117
private void OnUnloaded(object sender, RoutedEventArgs e)
67118
{
68119
_arduinoCom.Model = null;
69120
}
121+
122+
private void ServosDataGrid_OnSelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
123+
{
124+
var servo = (ServoPidModel) ServosDataGrid.SelectedCells.FirstOrDefault().Item;
125+
if (servo != null)
126+
Model.CurrentGraphServo = servo;
127+
}
70128
}
71129
}
Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
using System.ComponentModel;
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Collections.ObjectModel;
5+
using System.ComponentModel;
6+
using System.Linq;
27
using System.Runtime.CompilerServices;
8+
using InteractiveDataDisplay.WPF;
39
using ServoPIDControl.Annotations;
410

5-
namespace ServoPIDControl
11+
namespace ServoPIDControl.Model
612
{
713
public class ServoPidModel : INotifyPropertyChanged
814
{
@@ -21,11 +27,21 @@ public class ServoPidModel : INotifyPropertyChanged
2127
public ServoPidModel(int id)
2228
{
2329
Id = id;
30+
31+
#if DEBUG
32+
Times = new ObservableCollection<float>(Enumerable.Range(0, 500).Select(i => i / 100.0f));
33+
SetPoints = new ObservableCollection<float>(Enumerable.Range(id * 100, 500)
34+
.Select(i => i / 100 % 2 == 0 ? 80.0f : 100.0f));
35+
Inputs = new ObservableCollection<float>(Enumerable.Range(id * 100, 500)
36+
.Select(i => 90 + (float) Math.Sin(i / 50.0f)));
37+
Outputs = new ObservableCollection<float>(Enumerable.Range(id * 100, 500)
38+
.Select(i => 90 + (float) Math.Cos(i / 50.0f)));
39+
#endif
2440
}
2541

26-
public int Id { get; }
42+
public int Id { get; }
2743

28-
public float P
44+
public float P
2945
{
3046
get => _p;
3147
set
@@ -146,6 +162,30 @@ internal set
146162
}
147163
}
148164

165+
// ReSharper disable MemberInitializerValueIgnored
166+
public ObservableCollection<float> Times { get; } = new ObservableCollection<float>();
167+
public ObservableCollection<float> SetPoints { get; } = new ObservableCollection<float>();
168+
public ObservableCollection<float> Inputs { get; } = new ObservableCollection<float>();
169+
public ObservableCollection<float> Outputs { get; } = new ObservableCollection<float>();
170+
// ReSharper restore MemberInitializerValueIgnored
171+
172+
public struct TimeSeries
173+
{
174+
public ObservableCollection<float> X;
175+
public ObservableCollection<float> Y;
176+
public string Name;
177+
}
178+
179+
public IEnumerable<TimeSeries> AllTimeSeries
180+
{
181+
get
182+
{
183+
yield return new TimeSeries {X = Times, Y = SetPoints, Name = "SetPoint"};
184+
yield return new TimeSeries {X = Times, Y = Inputs, Name = "Input"};
185+
yield return new TimeSeries {X = Times, Y = Outputs, Name = "Output"};
186+
}
187+
}
188+
149189
public event PropertyChangedEventHandler PropertyChanged;
150190

151191
[NotifyPropertyChangedInvocator]
Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
using System;
22
using System.Collections.ObjectModel;
33
using System.ComponentModel;
4-
using Ports = System.IO.Ports;
54
using System.Linq;
65
using System.Runtime.CompilerServices;
76
using System.Windows.Threading;
87
using NLog;
98
using ServoPIDControl.Annotations;
9+
using Ports = System.IO.Ports;
1010

11-
namespace ServoPIDControl
11+
namespace ServoPIDControl.Model
1212
{
13-
public class Model : INotifyPropertyChanged, IDisposable
13+
public class ViewModel : INotifyPropertyChanged, IDisposable
1414
{
1515
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
1616

@@ -24,10 +24,11 @@ public class Model : INotifyPropertyChanged, IDisposable
2424
private readonly DispatcherTimer _timer;
2525
private float _minDt;
2626
private float _maxDt;
27+
private ServoPidModel _currentGraphServo;
2728

2829
public event PropertyChangedEventHandler PropertyChanged;
2930

30-
public Model()
31+
public ViewModel()
3132
{
3233
for (var i = 0; i < 4; ++i)
3334
Servos.Add(new ServoPidModel(i));
@@ -136,6 +137,17 @@ public float MaxDt
136137
}
137138
}
138139

140+
public ServoPidModel CurrentGraphServo
141+
{
142+
get => _currentGraphServo;
143+
set
144+
{
145+
if (value == _currentGraphServo) return;
146+
_currentGraphServo = value;
147+
OnPropertyChanged();
148+
}
149+
}
150+
139151
[NotifyPropertyChangedInvocator]
140152
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
141153
{

0 commit comments

Comments
 (0)