updated UI, redesigned,

implemented state indicators for robot mobile and stationary
main
Jonas Arnold 4 years ago
parent bdf8e1c4e6
commit 0df1227908
  1. 39
      ADIS_Csharp/RobotClientWpf/MainWindow.xaml
  2. 41
      ADIS_Csharp/RobotClientWpf/MainWindow.xaml.cs
  3. 6
      ADIS_Csharp/RobotClientWpf/Utilities/UIAccessHelpers.cs
  4. 2
      ADIS_Csharp/RobotClientWpf/Views/ConfigView.xaml
  5. 52
      ADIS_Csharp/RobotClientWpf/Views/MainView.xaml
  6. 15
      ADIS_Csharp/RobotClientWpf/Views/MainView.xaml.cs
  7. 40
      ADIS_Csharp/RobotLib/Battery/DevBattery.cs
  8. 24
      ADIS_Csharp/RobotLib/Battery/LastResponseUpdateEventArgs.cs
  9. 36
      ADIS_Csharp/RobotLib/Robot.cs

@ -12,16 +12,38 @@
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
<RowDefinition Height="200"/>
<RowDefinition Height="30"/>
<RowDefinition Height="260"/>
<!-- <RowDefinition Height="30"/> Bottom message (NOT USED) -->
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="5,5,5,5" Grid.ColumnSpan="2">
<Label HorizontalAlignment="Center">MQTT Broker IP:</Label>
<!-- ROW 0: Broker IP and states-->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="5">
<Label FontWeight="Bold">MQTT Broker IP:</Label>
<TextBox x:Name="tbIp" Width="120" Height="25" FontSize="15" TextAlignment="Left" Margin="10 0 0 0">192.168.156.156</TextBox>
<Button x:Name="btnConnectDisconnect" Content="Connect" Width="100" Margin="20 0 0 0" Click="btnConnectDisconnect_Click"></Button>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right">
<Label FontWeight="Bold">Robot states:</Label>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Label Content="Robot Mobile:" Margin="5 0 0 0"/>
<Ellipse Fill="Gray" x:Name="elRobotMobileIndicator" Width="20" Height="20" Margin="15 0" Opacity="100"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Label Content="Robot Stationary:" Margin="5 0 0 0"/>
<Ellipse Fill="Gray" x:Name="elRobotStationaryIndicator" Width="20" Height="20" Margin="15 0" Opacity="100"/>
</StackPanel>
</StackPanel>
</Grid>
<!-- ROW 1: Tab control -->
<TabControl Grid.Row="1" x:Name="tabControlMain" Grid.ColumnSpan="2">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
@ -48,6 +70,7 @@
</TabItem>
</TabControl>
<!-- ROW 2: NLOG viewer -->
<dj:NLogViewer Grid.Row="2" Margin="5" DebugForeground="Black" MaxCount="150" ErrorForeground="Black" FatalForeground="Black" TraceForeground="Black" InfoForeground="Black" WarnForeground="Black" >
<dj:NLogViewer.WarnBackground>
<SolidColorBrush Color="#FFFF9D00" Opacity="0.5"/>
@ -68,12 +91,10 @@
<SolidColorBrush Color="Red" Opacity="0.5"/>
</dj:NLogViewer.FatalBackground>
</dj:NLogViewer>
<!--<RichTextBox x:Name="rtbLogs" Grid.Row="2" Margin="10 0 10 0" IsReadOnly="True">
</RichTextBox>-->
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="0,150,0,0">
<!-- ROW 3: Bottom message (NOT USED) -->
<!--<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="0,150,0,0">
<TextBlock x:Name="tbBottomMessage" FontSize="15" Margin="10 5"/>
</StackPanel>
</StackPanel>-->
</Grid>
</Window>

@ -1,5 +1,6 @@
using System;
using System.Net;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@ -22,6 +23,9 @@ namespace RobotClientWpf
private static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger();
private ChallengeFactory challenge;
private bool manualDisconnect;
private Brush onlineBrush = Brushes.Green;
private Brush offlineBrush = Brushes.Red;
private Brush unknownBrush = Brushes.Gray;
public MainWindow()
{
@ -48,12 +52,44 @@ namespace RobotClientWpf
this.configView.InitializeChildView(this.challenge, this);
// subscribe to events
this.challenge.PublisherSubscriber.ConnectionStateChanged += PublisherSubscriber_ConnectionStateChanged;
this.challenge.RobotMobile.Battery.LastResponseUpdate += RobotMobile_Battery_SecondsSinceLastResponseUpdate;
this.challenge.RobotStationary.Battery.LastResponseUpdate += RobotStationary_Battery_SecondsSinceLastResponseUpdate;
}
private void RobotStationary_Battery_SecondsSinceLastResponseUpdate(object? sender, LastResponseUpdateEventArgs e)
{
// set color according to assessed state
if(e.Online == false)
{
log.Warn("Robot Stationary seems to be offline.");
}
else
{
log.Info("Robot Stationary is online.");
}
UIAccessHelpers.SetShapeFillColor(elRobotStationaryIndicator, e.Online ? onlineBrush : offlineBrush);
}
private void RobotMobile_Battery_SecondsSinceLastResponseUpdate(object? sender, LastResponseUpdateEventArgs e)
{
// set color according to assessed state
if (e.Online == false)
{
log.Warn("Robot Mobile seems to be offline.");
}
else
{
log.Info("Robot Mobile is online.");
}
UIAccessHelpers.SetShapeFillColor(elRobotMobileIndicator, e.Online ? onlineBrush : offlineBrush);
}
private void PublisherSubscriber_ConnectionStateChanged(object? sender, bool e)
{
if(e == false)
{
UIAccessHelpers.SetShapeFillColor(elRobotMobileIndicator, unknownBrush);
UIAccessHelpers.SetShapeFillColor(elRobotStationaryIndicator, unknownBrush);
if (manualDisconnect == true)
{
manualDisconnect = false;
@ -77,11 +113,6 @@ namespace RobotClientWpf
}
}
private void buttonBuzz_Click(object sender, RoutedEventArgs e)
{
//Robot.Instance.Buzzer.Beep(300, 500);
}
private void btnConnectDisconnect_Click(object sender, RoutedEventArgs e)
{
Task.Run(delegate ()

@ -101,18 +101,18 @@ namespace RobotClientWpf.Utilities
}
}
public static void SetShapeVisibility(Shape shape, bool visible)
public static void SetShapeFillColor(Shape shape, Brush color)
{
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
SetShapeVisibility(shape, visible);
SetShapeFillColor(shape, color);
}));
}
else
{
shape.Opacity = visible?100:0;
shape.Fill = color;
}
}
}

@ -33,7 +33,7 @@
<Button x:Name="btnApplyConfiguration" Content="Apply" HorizontalAlignment="Right" Height="30" Width="100" Margin="10" Click="btnApplyConfiguration_Click"/>
</DockPanel>
</GroupBox>
<GroupBox Header="Move to zero position">
<GroupBox Header="Initialization">
<DockPanel>
<Button x:Name="btnInitSplitflaps" Content="Move to Zero" HorizontalAlignment="Left" Height="30" Width="100" Margin="10" Click="btnInitSplitflaps_Click"/>
</DockPanel>

@ -12,41 +12,39 @@
<ColumnDefinition Width="400"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition Height="300"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- RIGHT SIDE -->
<StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="1" Grid.RowSpan="2">
<GroupBox Header="Splitflap Display" Margin="10" HorizontalAlignment="Right" Width="180">
<TextBox x:Name="tbSplitflapText" Height="70" Width="150" FontSize="30" IsReadOnly="True" TextWrapping="NoWrap" HorizontalAlignment="Left"/>
<GroupBox Header="Splitflap Display" Margin="10" HorizontalAlignment="Right">
<TextBox x:Name="tbSplitflapText" Height="100" Width="300" FontSize="40" IsReadOnly="True" TextWrapping="NoWrap" HorizontalAlignment="Left"/>
</GroupBox>
<GroupBox Header="Robot" Margin="10" HorizontalAlignment="Right" Width="350">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Label Content="Battery" Margin="10 0"/>
<TextBox x:Name="tbRoboVoltage" Height="25" Width="100" FontSize="15" IsReadOnly="True" TextWrapping="NoWrap" HorizontalAlignment="Left">NaN</TextBox>
<Label Content="V" Margin="10 0"/>
</StackPanel>
<!-- LEFT SIDE -->
<StackPanel Orientation="Horizontal">
<GroupBox Header="Robot Mobile">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<GroupBox Header="State">
<StackPanel Orientation="Vertical">
<Label Content="Battery:"/>
<StackPanel Orientation="Horizontal">
<Label Content="State" Margin="10 0"/>
<Ellipse Fill="Red" x:Name="ellipseRed" Width="20" Height="20" Margin="15 0" Opacity="0"/>
<Ellipse Fill="Green" x:Name="ellipseGreen" Width="20" Height="20" Margin="0 0" Opacity="0"/>
<TextBox x:Name="tbRoboVoltage" Margin="10 0 0 0" Width="100px" Height="30" FontSize="20" IsReadOnly="True" TextWrapping="NoWrap" HorizontalAlignment="Left">NaN</TextBox>
<Label Content="V" FontSize="20"/>
</StackPanel>
</StackPanel>
</GroupBox>
</StackPanel>
<!-- LEFT SIDE -->
<DockPanel>
<GroupBox DockPanel.Dock="Left" Grid.Column="0" Header="Robot Control" Width="200">
<GroupBox Grid.Column="0" Header="Control" Width="200">
<StackPanel Orientation="Vertical" Margin="5">
<Button x:Name="btnModeAuto" Height="25" Margin="0 5" Click="btnModeAuto_Click">Set Automatic Mode</Button>
<Button x:Name="btnModeManual" Height="25" Margin="0 5" Click="btnModeManual_Click">Set Manual Mode</Button>
<Button x:Name="btnModeAuto" Height="30" Margin="5 5" Click="btnModeAuto_Click">Set Automatic Mode</Button>
<Button x:Name="btnModeManual" Height="30" Margin="5 5" Click="btnModeManual_Click">Set Manual Mode</Button>
</StackPanel>
</GroupBox>
<GroupBox DockPanel.Dock="Left" Grid.Column="0" Header="Robot Steering" Width="200">
</StackPanel>
<GroupBox Grid.Column="0" Header="Steering" Width="200">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
@ -61,28 +59,28 @@
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Button Grid.Column="1" Grid.Row="0" Height="30" Width="30" x:Name="btnRoboFwd" Click="btnRoboFwd_Click">
<Button Grid.Column="1" Grid.Row="0" Height="50" Width="50" x:Name="btnRoboFwd" Click="btnRoboFwd_Click">
<Image>
<Image.Source>
<BitmapImage UriSource="/Assets/arrow-down-bold.png" Rotation="Rotate180"/>
</Image.Source>
</Image>
</Button>
<Button Grid.Column="1" Grid.Row="1" Height="30" Width="30" x:Name="btnRoboBwd" Click="btnRoboBwd_Click">
<Button Grid.Column="1" Grid.Row="1" Height="50" Width="50" x:Name="btnRoboBwd" Click="btnRoboBwd_Click">
<Image>
<Image.Source>
<BitmapImage UriSource="/Assets/arrow-down-bold.png"/>
</Image.Source>
</Image>
</Button>
<Button Grid.Column="0" Grid.Row="1" Height="30" Width="30" x:Name="btnRoboLeft" Click="btnRoboLeft_Click">
<Button Grid.Column="0" Grid.Row="1" Height="50" Width="50" x:Name="btnRoboLeft" Click="btnRoboLeft_Click">
<Image>
<Image.Source>
<BitmapImage UriSource="/Assets/arrow-down-bold.png" Rotation="Rotate90"/>
</Image.Source>
</Image>
</Button>
<Button Grid.Column="2" Grid.Row="1" Height="30" Width="30" x:Name="btnRoboRight" Click="btnRoboRight_Click">
<Button Grid.Column="2" Grid.Row="1" Height="50" Width="50" x:Name="btnRoboRight" Click="btnRoboRight_Click">
<Image>
<Image.Source>
<BitmapImage UriSource="/Assets/arrow-down-bold.png" Rotation="Rotate270"/>
@ -94,6 +92,8 @@
<Label Grid.Row="4" Grid.ColumnSpan="3" FontWeight="Bold" FontSize="12" Margin="5 0">Stop with End key</Label>
</Grid>
</GroupBox>
</DockPanel>
</StackPanel>
</GroupBox>
</StackPanel>
</Grid>
</UserControl>

@ -27,21 +27,6 @@ namespace RobotClientWpf.Views
// subscribe to events
this.challenge.RobotStationary.SplitFlap.SplitFlapDisplayChanged += this.SplitFlap_SplitFlapDisplayChanged;
this.challenge.RobotMobile.Battery.BatteryChanged += Battery_BatteryChanged;
this.challenge.RobotMobile.Battery.SecondsSinceLastResponseUpdate += Battery_SecondsSinceLastResponseUpdate;
}
private void Battery_SecondsSinceLastResponseUpdate(object? sender, int e)
{
if(e > 15)
{
UIAccessHelpers.SetShapeVisibility(ellipseRed, true);
UIAccessHelpers.SetShapeVisibility(ellipseGreen, false);
}
else
{
UIAccessHelpers.SetShapeVisibility(ellipseRed, false);
UIAccessHelpers.SetShapeVisibility(ellipseGreen, true);
}
}
private void Battery_BatteryChanged(object? sender, RobotLib.Battery.BatteryEventArgs e)

@ -3,13 +3,15 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.Json;
using System.Threading.Tasks;
namespace RobotLib.Battery
{
public class DevBattery : DevBase
{
private float voltage;
private int secondsSinceLastResponse = 0;
private int lastResponseS = 1000; // seconds since last response
private const int offlineTresholdS = 11; // treshold when to assess a robot as offline (no response for x sec)
private Stopwatch stopwatchLastResponse = new();
private const string TOPIC_ROBO_REQ_BATTERY = "/both/cmd/battery/get_volt";
@ -17,7 +19,7 @@ namespace RobotLib.Battery
private const string TOPIC_STAT_RESP_BATTERY = "/stationary/state/battery/voltage";
public event EventHandler<BatteryEventArgs> BatteryChanged;
public event EventHandler<int> SecondsSinceLastResponseUpdate;
public event EventHandler<LastResponseUpdateEventArgs> LastResponseUpdate;
public float Voltage
{
@ -36,11 +38,22 @@ namespace RobotLib.Battery
public int SecondsSinceLastResponse
{
get { return secondsSinceLastResponse; }
get { return lastResponseS; }
private set
{
secondsSinceLastResponse = value;
SecondsSinceLastResponseUpdate?.Invoke(this, secondsSinceLastResponse);
var lastVal = lastResponseS; // last stored value
lastResponseS = value; // update stored value
// new value is smaller than online time and old value is larger or equal => newly connected
if (value < offlineTresholdS && lastVal >= offlineTresholdS)
{
LastResponseUpdate?.Invoke(this, new LastResponseUpdateEventArgs(true, value));
}
// old value is larger than online time and new value is smaller => newly disconnected
else if(value >= offlineTresholdS && lastVal < offlineTresholdS)
{
Task.Run(() => this.Voltage = float.NaN); // set voltage to NaN, now unknown
LastResponseUpdate?.Invoke(this, new LastResponseUpdateEventArgs(false, value));
}
}
}
@ -57,22 +70,27 @@ namespace RobotLib.Battery
return new List<string>() { TOPIC_ROBO_RESP_BATTERY };
}
}
public void RequestBatteryVoltage()
public void UpdateLastResponse()
{
base.SendMessage(TOPIC_ROBO_REQ_BATTERY, true.ToString());
if (this.stopwatchLastResponse.IsRunning)
{
SecondsSinceLastResponse = this.stopwatchLastResponse.Elapsed.Seconds;
this.SecondsSinceLastResponse = this.stopwatchLastResponse.Elapsed.Seconds;
}
}
public void RequestBatteryVoltage()
{
base.SendMessage(TOPIC_ROBO_REQ_BATTERY, true.ToString());
this.UpdateLastResponse();
}
protected override void ParseMessage(string fromTopic, string message)
{
if (fromTopic == TOPIC_ROBO_RESP_BATTERY || fromTopic == TOPIC_STAT_RESP_BATTERY)
{
this.stopwatchLastResponse.Stop();
this.stopwatchLastResponse.Start();
SecondsSinceLastResponse = 0;
this.stopwatchLastResponse.Restart();
this.SecondsSinceLastResponse = 0;
var parsedString = GetValueFromMesage<string>("voltage", message);
if (parsedString == null) parsedString = "?";

@ -0,0 +1,24 @@
using System;
namespace RobotLib.Battery
{
public class LastResponseUpdateEventArgs : EventArgs
{
/// <summary>
/// Seconds since last got a response from the robot.
/// </summary>
public int Seconds { get; }
/// <summary>
/// Returns true if the robot is assessed to be online.
/// Returns false if the robot was unresonsive for too long.
/// </summary>
public bool Online { get; }
public LastResponseUpdateEventArgs(bool connected, int seconds)
{
Seconds = seconds;
Online = connected;
}
}
}

@ -9,6 +9,16 @@ namespace RobotLib
{
public class Robot
{
public IPublisherSubscriber Com { get; }
public RobotMode Mode { get; }
//public DevBuzzer Buzzer { get; }
public DevBattery Battery { get; }
public DevSplitFlap SplitFlap { get; }
public DevMovement Movement { get; }
public DevLineSensor LineSensor { get; }
public DevStatus Status { get; }
public Robot(IPublisherSubscriber com, RobotMode mode)
{
Com = com;
@ -35,38 +45,18 @@ namespace RobotLib
Timer timer = new Timer
{
Interval = 15000
Interval = 1000
};
timer.Enabled= true;
timer.Elapsed += Timer_Elapsed;
}
public IPublisherSubscriber Com { get; }
public RobotMode Mode { get; }
//public DevBuzzer Buzzer { get; }
public DevBattery Battery { get; }
public DevSplitFlap SplitFlap { get; }
public DevMovement Movement { get; }
public DevLineSensor LineSensor { get; }
public DevStatus Status { get; }
public void Connect(string host, int port)
{
//Com.Connect(host, port);
}
public void Disconnect()
{
//Com.Disconnect();
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (Com.IsConnected)
{
//Battery?.RequestBatteryVoltage();
Battery?.UpdateLastResponse();
//Battery?.RequestBatteryVoltage(); // is automatically sent by robot
}
}
}

Loading…
Cancel
Save