Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
bf60310
Enhance TLS configuration and error handling in MqttClientService
danielmeza Jan 23, 2026
d540f63
Enhance TLS options in MqttClientService for improved security handling
danielmeza Jan 24, 2026
98c78cb
Merge branch 'chkr1011:main' into main
danielmeza Feb 7, 2026
2eda8b3
Add high-throughput guide for handling 8K+ MQTT messages in desktop U…
danielmeza Feb 12, 2026
a9df2f3
Enhance PacketInspector with null-safe data access and improve UI fee…
danielmeza Feb 12, 2026
4f4c455
Refactor MessageProcessingOptions to remove TrimBatchSize and adjust …
danielmeza Feb 12, 2026
84d6216
Refactor message processing settings and improve UI responsiveness by…
danielmeza Mar 17, 2026
679d786
Merge branch 'main' into merge-high-troutput
danielmeza Mar 17, 2026
9b0a95d
Enable developer tools in debug mode with Microsoft logger integration
danielmeza Mar 17, 2026
5a57dcd
Enhance logging for message processing and packet inspection in MqttC…
danielmeza Mar 17, 2026
5bef6e1
Potential fix for pull request finding
danielmeza Mar 17, 2026
e1bef51
Refactor MqttClientService to improve connection handling and detach …
danielmeza Mar 17, 2026
722bbe7
Refactor connection handling in MainView and MainViewModel; update to…
danielmeza Mar 17, 2026
90a209e
Handle unobserved task exceptions by setting them as observed and dis…
danielmeza Mar 17, 2026
f902490
Optimize packet number handling using Interlocked for thread safety
danielmeza Mar 17, 2026
6c77af8
Add documentation for Mosquitto bug: UNSUBACK packets dropped when ou…
danielmeza Mar 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Source/App.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.TreeDataGrid/Themes/Fluent.axaml"/>

<StyleInclude Source="/Styles/GridSplitter.axaml" />
<StyleInclude Source="/Styles/Badges.axaml" />
Expand Down Expand Up @@ -60,6 +61,20 @@
Value="{DynamicResource ControlContentThemeFontSize}" />
</Style>

<!-- Highlight flash animation for TreeDataGrid rows when new messages arrive -->
<Style Selector="TreeDataGridRow.highlight">
<Style.Animations>
<Animation Duration="0:0:1.5" FillMode="Forward">
<KeyFrame Cue="0%">
<Setter Property="Background" Value="#4400EE00" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background" Value="Transparent" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>

<Style Selector="ToolTip">
<Setter Property="MaxWidth"
Value="999999" />
Expand Down
51 changes: 50 additions & 1 deletion Source/App.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using System;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
using Avalonia.Threading;
using Microsoft.Extensions.DependencyInjection;
using mqttMultimeter.Common;
using Microsoft.Extensions.Logging;
using mqttMultimeter.Controls;
using mqttMultimeter.Logging;
using mqttMultimeter.Main;
using mqttMultimeter.Pages.Connection;
using mqttMultimeter.Pages.Inflight;
Expand All @@ -20,6 +23,8 @@
using mqttMultimeter.Services.Mqtt;
using mqttMultimeter.Services.State;
using mqttMultimeter.Services.Updates;
using ReactiveUI;
using ViewLocator = mqttMultimeter.Common.ViewLocator;

namespace mqttMultimeter;

Expand All @@ -28,9 +33,32 @@ public sealed class App : Application
static MainViewModel? _mainViewModel;

readonly StateService _stateService;

readonly ILoggerFactory _loggerFactory;
readonly ILogger<App> _logger;

public App()
{

_loggerFactory = LoggerFactory.Create(builder =>
{
#if DEBUG
builder.SetMinimumLevel(LogLevel.Debug);
builder.AddDebug();
#endif
});

_logger = _loggerFactory.CreateLogger<App>();
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
Dispatcher.UIThread.UnhandledException += OnUnhandledException;
RxApp.DefaultExceptionHandler = new LogObserverExceptionHandler(_logger);
#if DEBUG
this.AttachDeveloperTools(o =>
{
o.AddMicrosoftLoggerObservable(_loggerFactory);
});
#endif

var serviceProvider = new ServiceCollection()
// Services
.AddSingleton<MqttClientService>()
Expand All @@ -48,6 +76,8 @@ public App()
.AddSingleton<LogPageViewModel>()
.AddSingleton<InfoPageViewModel>()
.AddSingleton<MainViewModel>()
.AddTransient(typeof(ILogger<>), typeof(Logger<>))
.AddSingleton(_loggerFactory)
.BuildServiceProvider();

var viewLocator = new ViewLocator();
Expand All @@ -56,8 +86,11 @@ public App()
serviceProvider.GetRequiredService<AppUpdateService>().EnableUpdateChecks();
_stateService = serviceProvider.GetRequiredService<StateService>();
_mainViewModel = serviceProvider.GetService<MainViewModel>();


}


public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
Expand Down Expand Up @@ -113,4 +146,20 @@ public static void ShowException(Exception exception)

_mainViewModel!.OverlayContent = errorBox;
}


void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
_logger.LogError(e.Exception, "Unhandled exception");
ShowException(e.Exception);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fiexed

e.Handled = true;
}

void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
_logger.LogError(e.Exception, "Unobserved task exception");
e.SetObserved();
Dispatcher.UIThread.Post(() => ShowException(e.Exception));
}

}
15 changes: 9 additions & 6 deletions Source/Controls/AutoGrid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@ protected override void ChildrenChanged(object? sender, NotifyCollectionChangedE

_cellArrangementPending = true;

Dispatcher.UIThread.Post(() =>
Dispatcher.UIThread.Post(
state =>
{
_cellArrangementPending = false;
var grid = (AutoGrid)state!;
grid._cellArrangementPending = false;

var currentRow = 0;
var currentColumn = 0;

foreach (var child in Children)
foreach (var child in grid.Children)
{
if (child is Control control)
{
Expand All @@ -65,15 +67,16 @@ protected override void ChildrenChanged(object? sender, NotifyCollectionChangedE
}
}

if (RowDefinitions.Count - 1 != currentRow)
if (grid.RowDefinitions.Count - 1 != currentRow)
{
RowDefinitions.Clear();
grid.RowDefinitions.Clear();
for (var i = 0; i <= currentRow; i++)
{
RowDefinitions.Add(new RowDefinition(GridLength.Auto));
grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto));
}
}
},
this,
DispatcherPriority.Render);
}
}
31 changes: 31 additions & 0 deletions Source/Extensions/CollectionTrimExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using DynamicData;

namespace mqttMultimeter.Extensions;

public static class CollectionTrimExtensions
{
/// <summary>
/// Adds items and trims the oldest entries inside a single
/// <see cref="SourceList{T}.Edit"/> changeset so the UI receives one update.
/// Removes exactly the overflow (count − max) so the list stays at
/// <paramref name="maxItems"/> rather than dropping a fixed batch size.
/// </summary>
public static void AddRangeAndTrim<T>(
this SourceList<T> source,
IList<T> items,
int maxItems) where T : notnull
{
source.Edit(list =>
{
list.AddRange(items);

var overflow = list.Count - maxItems;
if (overflow > 0)
{
list.RemoveRange(0, overflow);
}
});
}
}
46 changes: 46 additions & 0 deletions Source/Logging/LogObserverExceptionHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reactive.Concurrency;
using Microsoft.Extensions.Logging;
using ReactiveUI;

namespace mqttMultimeter.Logging;

public class LogObserverExceptionHandler(ILogger logger) : IObserver<Exception>
{
public void OnNext(Exception value)
{
if (Debugger.IsAttached)
Debugger.Break();

logger.LogError(value, "MyRxHandler Error");
RxSchedulers.MainThreadScheduler.Schedule(() =>
{
throw value;
});
}

public void OnError(Exception error)
{
if (Debugger.IsAttached)
Debugger.Break();

logger.LogError(error, "MyRxHandler OnError");

RxSchedulers.MainThreadScheduler.Schedule(() =>
{
throw error;
});
}

public void OnCompleted()
{
if (Debugger.IsAttached)
Debugger.Break();
RxSchedulers.MainThreadScheduler.Schedule(() =>
{
throw new NotImplementedException();
});
Comment on lines +41 to +44
}
}
99 changes: 83 additions & 16 deletions Source/Main/MainView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<Design.DataContext>
<main:MainViewModel />
</Design.DataContext>

<UserControl.Styles>
<Style Selector="Border.status_bar_connected">
<Setter Property="Background"
Expand All @@ -27,6 +27,15 @@
<Setter Property="TextBlock.Foreground"
Value="Black" />
</Style>

<Style Selector="TextBlock.status_warning">
<Setter Property="Background"
Value="DarkRed" />
<Setter Property="Foreground"
Value="White" />
<Setter Property="Padding"
Value="4,0" />
</Style>
</UserControl.Styles>

<!-- The overlay with content like the error box etc. -->
Expand Down Expand Up @@ -113,27 +122,85 @@
Classes.status_bar_not_connected="{Binding !ConnectionPage.IsConnected}"
Grid.Row="1">

<Grid ColumnDefinitions="Auto,*,Auto,Auto,Auto">
<!-- The connection status -->
<Border Grid.Column="0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Not connected"
IsVisible="{CompiledBinding !ConnectionPage.IsConnected}" />
<TextBlock Text="Connected"
IsVisible="{CompiledBinding ConnectionPage.IsConnected}" />

<Grid ColumnDefinitions="*,Auto,Auto,Auto">
<!-- The connection selector and actions -->
<Border Grid.Column="0"
Margin="10,0,10,0">
<StackPanel Orientation="Horizontal"
VerticalAlignment="Stretch">
<TextBlock Text="Connection:"
Margin="0,0,5,0"
VerticalAlignment="Center" />
<ComboBox Width="220"
VerticalAlignment="Stretch"
Background="Transparent"
BorderThickness="0"
ItemsSource="{Binding ConnectionPage.Items.Collection}"
SelectedItem="{Binding ConnectionPage.Items.SelectedItem}"
IsVisible="{Binding !ConnectionPage.IsConnected}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock IsVisible="{Binding ConnectionPage.IsConnected}"
Text="{Binding ConnectionPage.Items.SelectedItem.Name}"
Classes="value"
Width="220" />
<ToggleButton x:Name="ConnectionToggle"
Margin="10,0,0,0"
HorizontalContentAlignment="Center"
IsEnabled="{Binding !ConnectionPage.IsConnecting}"
IsChecked="{Binding ConnectionPage.IsConnected, Mode=OneWay}"
Command="{Binding ToggleConnectionCommand}">
<Grid ColumnDefinitions="Auto,Auto"
HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal"
IsVisible="{Binding ConnectionPage.IsConnected}">
<PathIcon Data="{StaticResource plug_disconnected_regular}"
Width="14"
Height="14" />
<TextBlock Margin="6,0,0,0"
Text="Disconnect" />
</StackPanel>
<StackPanel Orientation="Horizontal"
IsVisible="{Binding !ConnectionPage.IsConnected}">
<PathIcon Data="{StaticResource checkmark_circle_regular}"
Width="14"
Height="14" />
<TextBlock Margin="6,0,0,0"
Text="Connect" />
</StackPanel>
</Grid>
</ToggleButton>
<!-- Disconnection reason -->
<TextBlock Margin="10,0,0,0"
Text="{CompiledBinding ConnectionPage.DisconnectedReason.Reason}" />
<TextBlock Margin="10,0,0,0"
Text="{CompiledBinding ConnectionPage.DisconnectedReason.AdditionalInformation}" />
VerticalAlignment="Center"
Text="{CompiledBinding ConnectionPage.DisconnectedReason.Reason}"
IsVisible="{CompiledBinding !ConnectionPage.IsConnected}" />
<TextBlock Margin="5,0,0,0"
VerticalAlignment="Center"
Text="{CompiledBinding ConnectionPage.DisconnectedReason.AdditionalInformation}"
IsVisible="{CompiledBinding !ConnectionPage.IsConnected}" />
</StackPanel>
</Border>

<Separator Grid.Column="2" />
<Separator Grid.Column="1" />

<!-- The count of inflight items -->
<Border Grid.Column="3">
<TextBlock Text="{CompiledBinding Counter, StringFormat={}{0} messages received}" />
<Border Grid.Column="2">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{CompiledBinding ReceivedCounter, StringFormat={}{0} messages received}" />
<TextBlock Margin="10,0,0,0"
Text="{CompiledBinding NotifiedCounter, StringFormat={}Notified {0}}" />
<TextBlock Margin="10,0,0,0"
Text="{CompiledBinding BufferedCounter, StringFormat={}Buffered {0}}" />
<TextBlock Margin="10,0,0,0"
IsVisible="{CompiledBinding ShowDroppedCounter}"
Classes.status_warning="{CompiledBinding HasDroppedMessages}"
Text="{CompiledBinding DroppedCounter, StringFormat={}Dropped {0}}" />
</StackPanel>
</Border>

<!-- The version string -->
Expand Down
Loading
Loading