Running Alpha V2

This commit is contained in:
NinjaPug
2025-04-15 16:44:35 -04:00
parent 7899ec88ed
commit 6b07cf82fb
9 changed files with 1043 additions and 697 deletions

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@ PCPalService/service_log.txt
/PCPal/Configurator/obj /PCPal/Configurator/obj
/PCPal/Core/bin /PCPal/Core/bin
/PCPal/Core/obj /PCPal/Core/obj
/PCPal/Configurator/bin

View File

@@ -1,8 +1,12 @@
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui" <Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PCPal.Configurator.AppShell"> xmlns:views="clr-namespace:PCPal.Configurator.Views"
x:Class="PCPal.Configurator.AppShell"
Shell.NavBarIsVisible="False"
Shell.FlyoutBehavior="Disabled">
<Shell.ContentTemplate> <ShellContent>
<ContentPage>
<Grid ColumnDefinitions="220,*"> <Grid ColumnDefinitions="220,*">
<!-- Sidebar --> <!-- Sidebar -->
<StackLayout Grid.Column="0" <StackLayout Grid.Column="0"
@@ -68,5 +72,6 @@
<!-- Content area --> <!-- Content area -->
<ContentView Grid.Column="1" x:Name="ContentContainer" /> <ContentView Grid.Column="1" x:Name="ContentContainer" />
</Grid> </Grid>
</Shell.ContentTemplate> </ContentPage>
</ShellContent>
</Shell> </Shell>

View File

@@ -5,7 +5,6 @@ using PCPal.Configurator.Views.LCD;
using PCPal.Configurator.Views.OLED; using PCPal.Configurator.Views.OLED;
using PCPal.Configurator.Views.TFT; using PCPal.Configurator.Views.TFT;
using System.ComponentModel; using System.ComponentModel;
//using UIKit;
namespace PCPal.Configurator; namespace PCPal.Configurator;
@@ -14,6 +13,7 @@ public partial class AppShell : Shell, INotifyPropertyChanged
private bool _isConnected; private bool _isConnected;
private string _connectionStatus; private string _connectionStatus;
private DateTime _lastUpdateTime; private DateTime _lastUpdateTime;
private CancellationTokenSource _monitoringCts;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
@@ -60,7 +60,8 @@ public partial class AppShell : Shell, INotifyPropertyChanged
{ {
InitializeComponent(); InitializeComponent();
_serviceProvider = IPlatformApplication.Current.Services; _serviceProvider = IPlatformApplication.Current?.Services;
_monitoringCts = new CancellationTokenSource();
// Set initial connection status // Set initial connection status
IsConnected = false; IsConnected = false;
@@ -74,17 +75,29 @@ public partial class AppShell : Shell, INotifyPropertyChanged
StartConnectivityMonitoring(); StartConnectivityMonitoring();
} }
protected override void OnDisappearing()
{
base.OnDisappearing();
_monitoringCts?.Cancel();
}
~AppShell()
{
_monitoringCts?.Cancel();
_monitoringCts?.Dispose();
}
private void OnNavMenuSelectionChanged(object sender, SelectionChangedEventArgs e) private void OnNavMenuSelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
if (e.CurrentSelection.FirstOrDefault() is string selection) if (e.CurrentSelection.FirstOrDefault() is string selection)
{ {
ContentView view = selection switch ContentView view = selection switch
{ {
"1602 LCD Display" => _serviceProvider.GetService<LcdConfigView>(), "1602 LCD Display" => _serviceProvider?.GetService<LcdConfigView>(),
"4.6 TFT Display" => _serviceProvider.GetService<TftConfigView>(), "4.6\" TFT Display" => _serviceProvider?.GetService<TftConfigView>(),
"OLED Display" => _serviceProvider.GetService<OledConfigView>(), "OLED Display" => _serviceProvider?.GetService<OledConfigView>(),
"Settings" => _serviceProvider.GetService<SettingsView>(), "Settings" => _serviceProvider?.GetService<SettingsView>(),
"Help" => _serviceProvider.GetService<HelpView>(), "Help" => _serviceProvider?.GetService<HelpView>(),
_ => null _ => null
}; };
@@ -97,7 +110,7 @@ public partial class AppShell : Shell, INotifyPropertyChanged
private async void StartConnectivityMonitoring() private async void StartConnectivityMonitoring()
{ {
var serialPortService = _serviceProvider.GetService<ISerialPortService>(); var serialPortService = _serviceProvider?.GetService<ISerialPortService>();
if (serialPortService != null) if (serialPortService != null)
{ {
// Subscribe to connection status changes // Subscribe to connection status changes
@@ -112,9 +125,15 @@ public partial class AppShell : Shell, INotifyPropertyChanged
}; };
// Start periodic connection check // Start periodic connection check
while (true) try
{ {
await Task.Delay(5000); while (!_monitoringCts.Token.IsCancellationRequested)
{
await Task.Delay(5000, _monitoringCts.Token);
if (_monitoringCts.Token.IsCancellationRequested)
break;
try try
{ {
await serialPortService.CheckConnectionAsync(); await serialPortService.CheckConnectionAsync();
@@ -126,5 +145,11 @@ public partial class AppShell : Shell, INotifyPropertyChanged
} }
} }
} }
catch (OperationCanceledException)
{
// Expected when cancellation is requested
System.Diagnostics.Debug.WriteLine("Connectivity monitoring canceled");
}
}
} }
} }

View File

@@ -1,5 +1,4 @@
//using Android.Sax; using Microsoft.Maui.Controls.Shapes;
using Microsoft.Maui.Controls.Shapes;
using PCPal.Configurator.ViewModels; using PCPal.Configurator.ViewModels;
using PCPal.Core.Models; using PCPal.Core.Models;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -162,14 +161,17 @@ public class OledPreviewCanvas : GraphicsView
dragStartPoint = point; dragStartPoint = point;
// Check if an element was clicked // Check if an element was clicked
if (Elements != null) if (Elements == null || Elements.Count == 0) return;
{
// Need to adjust for scale // Need to adjust for scale
float x = (float)point.X / Scale; float x = (float)point.X / Scale;
float y = (float)point.Y / Scale; float y = (float)point.Y / Scale;
foreach (var element in Elements) // Check in reverse order (top elements first)
for (int i = Elements.Count - 1; i >= 0; i--)
{ {
var element = Elements[i];
if (element is TextElement textElement) if (element is TextElement textElement)
{ {
// Simple bounding box check // Simple bounding box check
@@ -217,11 +219,6 @@ public class OledPreviewCanvas : GraphicsView
else if (element is LineElement lineElement) else if (element is LineElement lineElement)
{ {
// Simplified line hit detection // Simplified line hit detection
float lineLength = (float)Math.Sqrt(
Math.Pow(lineElement.X2 - lineElement.X1, 2) +
Math.Pow(lineElement.Y2 - lineElement.Y1, 2));
// Check if point is close to the line
float distance = DistancePointToLine( float distance = DistancePointToLine(
x, y, x, y,
lineElement.X1, lineElement.Y1, lineElement.X1, lineElement.Y1,
@@ -257,12 +254,13 @@ public class OledPreviewCanvas : GraphicsView
// No element was clicked, deselect // No element was clicked, deselect
SelectedElement = null; SelectedElement = null;
} }
}
private void OnDragInteraction(object sender, TouchEventArgs e) private void OnDragInteraction(object sender, TouchEventArgs e)
{ {
if (!IsEditable || draggedElement == null) return; if (!IsEditable || draggedElement == null) return;
try
{
var point = e.Touches[0]; var point = e.Touches[0];
// Calculate the delta from the start point // Calculate the delta from the start point
@@ -281,7 +279,7 @@ public class OledPreviewCanvas : GraphicsView
dragStartPoint = point; dragStartPoint = point;
// Notify property changes // Notify property changes
var viewModel = BindingContext as OledConfigViewModel; var viewModel = BindingContext as PCPal.Configurator.ViewModels.OledConfigViewModel;
if (viewModel != null) if (viewModel != null)
{ {
// Update the view model properties to reflect the new position // Update the view model properties to reflect the new position
@@ -295,6 +293,11 @@ public class OledPreviewCanvas : GraphicsView
// Invalidate the canvas to redraw // Invalidate the canvas to redraw
Invalidate(); Invalidate();
} }
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error in drag interaction: {ex.Message}");
}
}
private void OnEndInteraction(object sender, TouchEventArgs e) private void OnEndInteraction(object sender, TouchEventArgs e)
{ {
@@ -377,6 +380,8 @@ public class OledCanvasDrawable : IDrawable
} }
public void Draw(ICanvas canvas, RectF dirtyRect) public void Draw(ICanvas canvas, RectF dirtyRect)
{
try
{ {
float scale = _canvas.Scale; float scale = _canvas.Scale;
@@ -440,6 +445,11 @@ public class OledCanvasDrawable : IDrawable
} }
} }
} }
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error drawing canvas: {ex.Message}");
}
}
private void DrawElement(ICanvas canvas, PreviewElement element, float scale, bool isSelected) private void DrawElement(ICanvas canvas, PreviewElement element, float scale, bool isSelected)
{ {

View File

@@ -6,12 +6,20 @@
<!-- Note: For Android please see also Platforms\Android\Resources\values\colors.xml --> <!-- Note: For Android please see also Platforms\Android\Resources\values\colors.xml -->
<Color x:Key="Primary">#512BD4</Color> <Color x:Key="Primary">#1E88E5</Color>
<Color x:Key="PrimaryDark">#ac99ea</Color> <Color x:Key="PrimaryDark">#1565C0</Color>
<Color x:Key="PrimaryDarkText">#242424</Color> <Color x:Key="PrimaryLight">#E3F2FD</Color>
<Color x:Key="Secondary">#DFD8F7</Color> <Color x:Key="Secondary">#CFD8DC</Color>
<Color x:Key="SecondaryDarkText">#9880e5</Color>
<Color x:Key="Tertiary">#2B0B98</Color> <Color x:Key="Tertiary">#2B0B98</Color>
<Color x:Key="Accent">#03A9F4</Color>
<Color x:Key="Background">#F4F6F8</Color>
<Color x:Key="Surface">#FFFFFF</Color>
<Color x:Key="TextPrimary">#333333</Color>
<Color x:Key="TextSecondary">#555555</Color>
<Color x:Key="BorderColor">#E1E4E8</Color>
<Color x:Key="Success">#4CAF50</Color>
<Color x:Key="Error">#F44336</Color>
<Color x:Key="Warning">#FF9800</Color>
<Color x:Key="White">White</Color> <Color x:Key="White">White</Color>
<Color x:Key="Black">Black</Color> <Color x:Key="Black">Black</Color>

View File

@@ -83,15 +83,21 @@ public class StringMatchConverter : IValueConverter
{ {
if (currentValue == targetValue) if (currentValue == targetValue)
{ {
// Handle different return types based on an additional parameter // Default to background coloring
if (parameter is string param2 && param2 == "TextColor") Color returnColor = Application.Current.Resources["PrimaryLight"] as Color ?? Colors.LightBlue;
// Check for additional parameter context
if (targetValue.EndsWith(":TextColor"))
{
// Strip the ":TextColor" suffix before comparison
string strippedTarget = targetValue.Substring(0, targetValue.Length - 10);
if (currentValue == strippedTarget)
{ {
// Return text color for selected item
return Application.Current.Resources["Primary"] as Color ?? Colors.Black; return Application.Current.Resources["Primary"] as Color ?? Colors.Black;
} }
}
// Return background color for selected item return returnColor;
return Application.Current.Resources["PrimaryLight"] as Color ?? Colors.LightBlue;
} }
} }

View File

@@ -86,12 +86,6 @@
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Surface}, Dark=White}" /> <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Surface}, Dark=White}" />
</Style> </Style>
<Style TargetType="TabBar">
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource Surface}, Dark=#333333}" />
<Setter Property="BarTextColor" Value="{AppThemeBinding Light=#777777, Dark=#777777}" />
<Setter Property="BarSelectedTextColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Primary}}" />
</Style>
<!-- Custom Styles --> <!-- Custom Styles -->
<Style x:Key="HeaderLabelStyle" TargetType="Label"> <Style x:Key="HeaderLabelStyle" TargetType="Label">

View File

@@ -4,8 +4,8 @@ using PCPal.Configurator.Views.OLED;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Windows.Input; using System.Windows.Input;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
//using Javax.Xml.Transform;
using System.Diagnostics; using System.Diagnostics;
//using Javax.Xml.Transform;
namespace PCPal.Configurator.ViewModels; namespace PCPal.Configurator.ViewModels;
@@ -49,6 +49,7 @@ public class OledConfigViewModel : BaseViewModel
// Timer for sensor updates // Timer for sensor updates
private Timer _sensorUpdateTimer; private Timer _sensorUpdateTimer;
private CancellationTokenSource _sensorUpdateCts;
#region Properties #region Properties
@@ -410,9 +411,9 @@ public class OledConfigViewModel : BaseViewModel
IConfigurationService configService, IConfigurationService configService,
ISerialPortService serialPortService) ISerialPortService serialPortService)
{ {
_sensorService = sensorService; _sensorService = sensorService ?? throw new ArgumentNullException(nameof(sensorService));
_configService = configService; _configService = configService ?? throw new ArgumentNullException(nameof(configService));
_serialPortService = serialPortService; _serialPortService = serialPortService ?? throw new ArgumentNullException(nameof(serialPortService));
// Initialize collections // Initialize collections
_oledElements = new ObservableCollection<OledElement>(); _oledElements = new ObservableCollection<OledElement>();
@@ -422,6 +423,9 @@ public class OledConfigViewModel : BaseViewModel
_templateList = new ObservableCollection<Template>(); _templateList = new ObservableCollection<Template>();
_customTemplates = new ObservableCollection<Template>(); _customTemplates = new ObservableCollection<Template>();
// Setup cancellation token source
_sensorUpdateCts = new CancellationTokenSource();
// Create views // Create views
_visualEditorView = new OledVisualEditorView { BindingContext = this }; _visualEditorView = new OledVisualEditorView { BindingContext = this };
_markupEditorView = new OledMarkupEditorView { BindingContext = this }; _markupEditorView = new OledMarkupEditorView { BindingContext = this };
@@ -468,6 +472,25 @@ public class OledConfigViewModel : BaseViewModel
_sensorUpdateTimer = new Timer(async (_) => await UpdateSensorDataAsync(), null, Timeout.Infinite, Timeout.Infinite); _sensorUpdateTimer = new Timer(async (_) => await UpdateSensorDataAsync(), null, Timeout.Infinite, Timeout.Infinite);
} }
~OledConfigViewModel()
{
CleanupResources();
}
private void CleanupResources()
{
try
{
_sensorUpdateCts?.Cancel();
_sensorUpdateTimer?.Dispose();
_sensorUpdateCts?.Dispose();
}
catch (Exception ex)
{
Debug.WriteLine($"Error cleaning up resources: {ex.Message}");
}
}
public async Task Initialize() public async Task Initialize()
{ {
IsBusy = true; IsBusy = true;
@@ -492,6 +515,7 @@ public class OledConfigViewModel : BaseViewModel
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.WriteLine($"Error initializing: {ex.Message}");
await Shell.Current.DisplayAlert("Error", $"Failed to initialize: {ex.Message}", "OK"); await Shell.Current.DisplayAlert("Error", $"Failed to initialize: {ex.Message}", "OK");
} }
finally finally
@@ -501,6 +525,8 @@ public class OledConfigViewModel : BaseViewModel
} }
private async Task LoadSensorsAsync() private async Task LoadSensorsAsync()
{
try
{ {
await _sensorService.UpdateSensorValuesAsync(); await _sensorService.UpdateSensorValuesAsync();
@@ -515,11 +541,21 @@ public class OledConfigViewModel : BaseViewModel
} }
} }
// Update on main thread to ensure thread safety
await MainThread.InvokeOnMainThreadAsync(() => {
AvailableSensors = new ObservableCollection<SensorItem>(sensors); AvailableSensors = new ObservableCollection<SensorItem>(sensors);
FilteredSensors = new ObservableCollection<SensorItem>(sensors); FilteredSensors = new ObservableCollection<SensorItem>(sensors);
});
}
catch (Exception ex)
{
Debug.WriteLine($"Error loading sensors: {ex.Message}");
}
} }
private async Task LoadConfigAsync() private async Task LoadConfigAsync()
{
try
{ {
var config = await _configService.LoadConfigAsync(); var config = await _configService.LoadConfigAsync();
@@ -536,8 +572,16 @@ public class OledConfigViewModel : BaseViewModel
await LoadExampleMarkupAsync(); await LoadExampleMarkupAsync();
} }
} }
catch (Exception ex)
{
Debug.WriteLine($"Error loading configuration: {ex.Message}");
await LoadExampleMarkupAsync(); // Fallback to example
}
}
private async Task LoadTemplatesAsync() private async Task LoadTemplatesAsync()
{
try
{ {
// Predefined templates // Predefined templates
var templates = new List<Template> var templates = new List<Template>
@@ -601,6 +645,11 @@ public class OledConfigViewModel : BaseViewModel
CustomTemplates = new ObservableCollection<Template>(customTemplates); CustomTemplates = new ObservableCollection<Template>(customTemplates);
} }
} }
catch (Exception ex)
{
Debug.WriteLine($"Error loading templates: {ex.Message}");
}
}
private void SwitchTab(string tab) private void SwitchTab(string tab)
{ {
@@ -647,6 +696,7 @@ public class OledConfigViewModel : BaseViewModel
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.WriteLine($"Error saving configuration: {ex.Message}");
await Shell.Current.DisplayAlert("Error", $"Failed to save configuration: {ex.Message}", "OK"); await Shell.Current.DisplayAlert("Error", $"Failed to save configuration: {ex.Message}", "OK");
} }
finally finally
@@ -682,6 +732,7 @@ public class OledConfigViewModel : BaseViewModel
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.WriteLine($"Error previewing on device: {ex.Message}");
await Shell.Current.DisplayAlert("Error", $"Preview failed: {ex.Message}", "OK"); await Shell.Current.DisplayAlert("Error", $"Preview failed: {ex.Message}", "OK");
} }
finally finally
@@ -691,6 +742,8 @@ public class OledConfigViewModel : BaseViewModel
} }
private async Task ResetLayoutAsync() private async Task ResetLayoutAsync()
{
try
{ {
bool confirm = await Shell.Current.DisplayAlert( bool confirm = await Shell.Current.DisplayAlert(
"Reset Layout", "Reset Layout",
@@ -702,6 +755,11 @@ public class OledConfigViewModel : BaseViewModel
await LoadExampleMarkupAsync(); await LoadExampleMarkupAsync();
} }
} }
catch (Exception ex)
{
Debug.WriteLine($"Error resetting layout: {ex.Message}");
}
}
public void UpdatePreviewFromMarkup() public void UpdatePreviewFromMarkup()
{ {
@@ -709,7 +767,12 @@ public class OledConfigViewModel : BaseViewModel
{ {
// Parse the markup into preview elements // Parse the markup into preview elements
var markupParser = new MarkupParser(_sensorService.GetAllSensorValues()); var markupParser = new MarkupParser(_sensorService.GetAllSensorValues());
PreviewElements = markupParser.ParseMarkup(OledMarkup); var elements = markupParser.ParseMarkup(OledMarkup);
// Update on main thread to ensure thread safety
MainThread.BeginInvokeOnMainThread(() => {
PreviewElements = elements;
});
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -717,16 +780,44 @@ public class OledConfigViewModel : BaseViewModel
} }
} }
public void UpdateMarkupFromElements()
{
try
{
var sb = new System.Text.StringBuilder();
foreach (var element in OledElements)
{
sb.AppendLine(element.ToMarkup());
}
OledMarkup = sb.ToString();
UpdatePreviewFromMarkup();
}
catch (Exception ex)
{
Debug.WriteLine($"Error updating markup from elements: {ex.Message}");
}
}
private async Task UpdateSensorDataAsync() private async Task UpdateSensorDataAsync()
{ {
try try
{ {
if (_sensorUpdateCts.Token.IsCancellationRequested)
return;
await _sensorService.UpdateSensorValuesAsync(); await _sensorService.UpdateSensorValuesAsync();
UpdatePreviewFromMarkup(); UpdatePreviewFromMarkup();
// Update available sensors // Update available sensors
await LoadSensorsAsync(); await LoadSensorsAsync();
} }
catch (OperationCanceledException)
{
// Expected when cancellation is requested
Debug.WriteLine("Sensor update canceled");
}
catch (Exception ex) catch (Exception ex)
{ {
// Log error but don't display to user since this happens in background // Log error but don't display to user since this happens in background
@@ -735,20 +826,33 @@ public class OledConfigViewModel : BaseViewModel
} }
private void ApplySensorFilter() private void ApplySensorFilter()
{
try
{ {
if (string.IsNullOrEmpty(CurrentSensorFilter) || CurrentSensorFilter == "All") if (string.IsNullOrEmpty(CurrentSensorFilter) || CurrentSensorFilter == "All")
{ {
MainThread.BeginInvokeOnMainThread(() => {
FilteredSensors = new ObservableCollection<SensorItem>(AvailableSensors); FilteredSensors = new ObservableCollection<SensorItem>(AvailableSensors);
});
return; return;
} }
var filtered = AvailableSensors.Where(s => var filtered = AvailableSensors.Where(s =>
s.HardwareName.Contains(CurrentSensorFilter, StringComparison.OrdinalIgnoreCase)).ToList(); s.HardwareName.Contains(CurrentSensorFilter, StringComparison.OrdinalIgnoreCase)).ToList();
MainThread.BeginInvokeOnMainThread(() => {
FilteredSensors = new ObservableCollection<SensorItem>(filtered); FilteredSensors = new ObservableCollection<SensorItem>(filtered);
});
}
catch (Exception ex)
{
Debug.WriteLine($"Error applying sensor filter: {ex.Message}");
}
} }
private void AddElement(string type) private void AddElement(string type)
{
try
{ {
var element = new OledElement var element = new OledElement
{ {
@@ -793,8 +897,15 @@ public class OledConfigViewModel : BaseViewModel
// Update markup // Update markup
UpdateMarkupFromElements(); UpdateMarkupFromElements();
} }
catch (Exception ex)
{
Debug.WriteLine($"Error adding element: {ex.Message}");
}
}
private void DeleteSelectedElement() private void DeleteSelectedElement()
{
try
{ {
if (SelectedElement != null) if (SelectedElement != null)
{ {
@@ -805,30 +916,26 @@ public class OledConfigViewModel : BaseViewModel
UpdateMarkupFromElements(); UpdateMarkupFromElements();
} }
} }
catch (Exception ex)
public void UpdateMarkupFromElements()
{ {
var sb = new System.Text.StringBuilder(); Debug.WriteLine($"Error deleting element: {ex.Message}");
foreach (var element in OledElements)
{
sb.AppendLine(element.ToMarkup());
} }
OledMarkup = sb.ToString();
UpdatePreviewFromMarkup();
} }
private async Task ParseMarkupToElementsAsync(string markup) private async Task ParseMarkupToElementsAsync(string markup)
{ {
if (string.IsNullOrEmpty(markup)) if (string.IsNullOrEmpty(markup))
{ {
await MainThread.InvokeOnMainThreadAsync(() => {
OledElements.Clear(); OledElements.Clear();
});
return; return;
} }
var elements = new List<OledElement>(); var elements = new List<OledElement>();
try
{
// Parse text elements // Parse text elements
foreach (Match match in Regex.Matches(markup, @"<text\s+x=(\d+)\s+y=(\d+)(?:\s+size=(\d+))?>([^<]*)</text>")) foreach (Match match in Regex.Matches(markup, @"<text\s+x=(\d+)\s+y=(\d+)(?:\s+size=(\d+))?>([^<]*)</text>"))
{ {
@@ -914,6 +1021,11 @@ public class OledConfigViewModel : BaseViewModel
} }
}); });
} }
catch (Exception ex)
{
Debug.WriteLine($"Error parsing markup: {ex.Message}");
}
}
private async Task<List<PreviewElement>> ParseMarkupToPreviewElements(string markup) private async Task<List<PreviewElement>> ParseMarkupToPreviewElements(string markup)
{ {
@@ -946,6 +1058,8 @@ public class OledConfigViewModel : BaseViewModel
} }
private void AddSensorToDisplay(string sensorId) private void AddSensorToDisplay(string sensorId)
{
try
{ {
// Find the sensor // Find the sensor
var sensor = AvailableSensors.FirstOrDefault(s => s.Id == sensorId); var sensor = AvailableSensors.FirstOrDefault(s => s.Id == sensorId);
@@ -997,8 +1111,15 @@ public class OledConfigViewModel : BaseViewModel
// Update markup // Update markup
UpdateMarkupFromElements(); UpdateMarkupFromElements();
} }
catch (Exception ex)
{
Debug.WriteLine($"Error adding sensor to display: {ex.Message}");
}
}
private async Task BrowseIconsAsync() private async Task BrowseIconsAsync()
{
try
{ {
// A mock implementation - in a real app, you'd implement a proper icon browser // A mock implementation - in a real app, you'd implement a proper icon browser
var icons = new string[] { "cpu", "gpu", "ram", "disk", "network", "fan" }; var icons = new string[] { "cpu", "gpu", "ram", "disk", "network", "fan" };
@@ -1009,8 +1130,15 @@ public class OledConfigViewModel : BaseViewModel
SelectedElementIconName = result; SelectedElementIconName = result;
} }
} }
catch (Exception ex)
{
Debug.WriteLine($"Error browsing icons: {ex.Message}");
}
}
private void InsertMarkupTemplate(string type) private void InsertMarkupTemplate(string type)
{
try
{ {
string template = string.Empty; string template = string.Empty;
@@ -1023,6 +1151,22 @@ public class OledConfigViewModel : BaseViewModel
case "bar": case "bar":
template = "<bar x=10 y=30 w=100 h=8 val=75 />"; template = "<bar x=10 y=30 w=100 h=8 val=75 />";
break; break;
case "rect":
template = "<rect x=10 y=40 w=50 h=20 />";
break;
case "box":
template = "<box x=70 y=40 w=50 h=20 />";
break;
case "line":
template = "<line x1=10 y1=50 x2=60 y2=50 />";
break;
case "icon":
template = "<icon x=130 y=20 name=cpu />";
break;
} }
if (!string.IsNullOrEmpty(template)) if (!string.IsNullOrEmpty(template))
@@ -1031,8 +1175,15 @@ public class OledConfigViewModel : BaseViewModel
UpdatePreviewFromMarkup(); UpdatePreviewFromMarkup();
} }
} }
catch (Exception ex)
{
Debug.WriteLine($"Error inserting markup template: {ex.Message}");
}
}
private async Task InsertSensorVariableAsync() private async Task InsertSensorVariableAsync()
{
try
{ {
// Create a list of sensor options // Create a list of sensor options
var options = AvailableSensors.Select(s => $"{s.DisplayName} ({s.Id})").ToArray(); var options = AvailableSensors.Select(s => $"{s.DisplayName} ({s.Id})").ToArray();
@@ -1050,20 +1201,42 @@ public class OledConfigViewModel : BaseViewModel
UpdatePreviewFromMarkup(); UpdatePreviewFromMarkup();
} }
} }
catch (Exception ex)
{
Debug.WriteLine($"Error inserting sensor variable: {ex.Message}");
}
}
private async Task LoadExampleMarkupAsync() private async Task LoadExampleMarkupAsync()
{
try
{ {
OledMarkup = await _sensorService.CreateExampleMarkupAsync(); OledMarkup = await _sensorService.CreateExampleMarkupAsync();
await ParseMarkupToElementsAsync(OledMarkup); await ParseMarkupToElementsAsync(OledMarkup);
UpdatePreviewFromMarkup(); UpdatePreviewFromMarkup();
} }
catch (Exception ex)
{
Debug.WriteLine($"Error loading example markup: {ex.Message}");
}
}
private async Task<string> CreateSystemMonitorTemplateAsync() private async Task<string> CreateSystemMonitorTemplateAsync()
{
try
{ {
return await _sensorService.CreateExampleMarkupAsync(); return await _sensorService.CreateExampleMarkupAsync();
} }
catch (Exception ex)
{
Debug.WriteLine($"Error creating system monitor template: {ex.Message}");
return "<text x=10 y=20 size=1>System Monitor</text>";
}
}
private async Task<string> CreateTemperatureMonitorTemplateAsync() private async Task<string> CreateTemperatureMonitorTemplateAsync()
{
try
{ {
var sb = new System.Text.StringBuilder(); var sb = new System.Text.StringBuilder();
@@ -1087,8 +1260,16 @@ public class OledConfigViewModel : BaseViewModel
return sb.ToString(); return sb.ToString();
} }
catch (Exception ex)
{
Debug.WriteLine($"Error creating temperature monitor template: {ex.Message}");
return "<text x=10 y=20 size=1>Temperature Monitor</text>";
}
}
private async Task<string> CreateNetworkMonitorTemplateAsync() private async Task<string> CreateNetworkMonitorTemplateAsync()
{
try
{ {
var sb = new System.Text.StringBuilder(); var sb = new System.Text.StringBuilder();
@@ -1097,8 +1278,16 @@ public class OledConfigViewModel : BaseViewModel
return sb.ToString(); return sb.ToString();
} }
catch (Exception ex)
{
Debug.WriteLine($"Error creating network monitor template: {ex.Message}");
return "<text x=10 y=20 size=1>Network Monitor</text>";
}
}
private async Task<string> CreateStorageMonitorTemplateAsync() private async Task<string> CreateStorageMonitorTemplateAsync()
{
try
{ {
var sb = new System.Text.StringBuilder(); var sb = new System.Text.StringBuilder();
@@ -1107,8 +1296,16 @@ public class OledConfigViewModel : BaseViewModel
return sb.ToString(); return sb.ToString();
} }
catch (Exception ex)
{
Debug.WriteLine($"Error creating storage monitor template: {ex.Message}");
return "<text x=10 y=20 size=1>Storage Monitor</text>";
}
}
private async Task UseSelectedTemplateAsync() private async Task UseSelectedTemplateAsync()
{
try
{ {
if (SelectedTemplate == null) if (SelectedTemplate == null)
{ {
@@ -1128,8 +1325,15 @@ public class OledConfigViewModel : BaseViewModel
SwitchTab("markup"); SwitchTab("markup");
} }
} }
catch (Exception ex)
{
Debug.WriteLine($"Error using selected template: {ex.Message}");
}
}
private async Task SaveAsTemplateAsync() private async Task SaveAsTemplateAsync()
{
try
{ {
if (string.IsNullOrWhiteSpace(NewTemplateName)) if (string.IsNullOrWhiteSpace(NewTemplateName))
{ {
@@ -1192,8 +1396,16 @@ public class OledConfigViewModel : BaseViewModel
await Shell.Current.DisplayAlert("Success", "Template saved successfully!", "OK"); await Shell.Current.DisplayAlert("Success", "Template saved successfully!", "OK");
} }
catch (Exception ex)
{
Debug.WriteLine($"Error saving as template: {ex.Message}");
await Shell.Current.DisplayAlert("Error", $"Failed to save template: {ex.Message}", "OK");
}
}
private async Task UseCustomTemplateAsync(Template template) private async Task UseCustomTemplateAsync(Template template)
{
try
{ {
if (template == null) if (template == null)
{ {
@@ -1213,8 +1425,15 @@ public class OledConfigViewModel : BaseViewModel
SwitchTab("markup"); SwitchTab("markup");
} }
} }
catch (Exception ex)
{
Debug.WriteLine($"Error using custom template: {ex.Message}");
}
}
private async Task DeleteCustomTemplateAsync(Template template) private async Task DeleteCustomTemplateAsync(Template template)
{
try
{ {
if (template == null) if (template == null)
{ {
@@ -1240,6 +1459,11 @@ public class OledConfigViewModel : BaseViewModel
} }
} }
} }
catch (Exception ex)
{
Debug.WriteLine($"Error deleting custom template: {ex.Message}");
}
}
} }
public class Template public class Template

View File

@@ -16,12 +16,13 @@ public interface ISerialPortService
event EventHandler<bool> ConnectionStatusChanged; event EventHandler<bool> ConnectionStatusChanged;
} }
public class SerialPortService : ISerialPortService public class SerialPortService : ISerialPortService, IDisposable
{ {
private SerialPort _serialPort; private SerialPort _serialPort;
private bool _isConnected; private bool _isConnected;
private string _currentPort; private string _currentPort;
private readonly ILogger<SerialPortService> _logger; private readonly ILogger<SerialPortService> _logger;
private bool _disposed = false;
public bool IsConnected => _isConnected; public bool IsConnected => _isConnected;
public string CurrentPort => _currentPort; public string CurrentPort => _currentPort;
@@ -30,13 +31,19 @@ public class SerialPortService : ISerialPortService
public SerialPortService(ILogger<SerialPortService> logger) public SerialPortService(ILogger<SerialPortService> logger)
{ {
_logger = logger; _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_isConnected = false; _isConnected = false;
_currentPort = string.Empty; _currentPort = string.Empty;
} }
public async Task<bool> ConnectAsync(string port) public async Task<bool> ConnectAsync(string port)
{ {
if (string.IsNullOrEmpty(port))
{
_logger.LogWarning("Cannot connect to null or empty port name");
return false;
}
try try
{ {
// Disconnect if already connected // Disconnect if already connected
@@ -71,6 +78,7 @@ public class SerialPortService : ISerialPortService
// Not a valid PCPal device, close the connection // Not a valid PCPal device, close the connection
_serialPort.Close(); _serialPort.Close();
_serialPort.Dispose(); _serialPort.Dispose();
_serialPort = null;
return false; return false;
} }
catch (Exception ex) catch (Exception ex)
@@ -81,6 +89,7 @@ public class SerialPortService : ISerialPortService
{ {
_serialPort.Close(); _serialPort.Close();
_serialPort.Dispose(); _serialPort.Dispose();
_serialPort = null;
} }
return false; return false;
@@ -126,8 +135,15 @@ public class SerialPortService : ISerialPortService
public async Task<bool> SendCommandAsync(string command) public async Task<bool> SendCommandAsync(string command)
{ {
if (string.IsNullOrEmpty(command))
{
_logger.LogWarning("Cannot send null or empty command");
return false;
}
if (!_isConnected || _serialPort == null || !_serialPort.IsOpen) if (!_isConnected || _serialPort == null || !_serialPort.IsOpen)
{ {
_logger.LogWarning("Cannot send command: Device not connected");
return false; return false;
} }
@@ -155,9 +171,10 @@ public class SerialPortService : ISerialPortService
foreach (string port in ports) foreach (string port in ports)
{ {
SerialPort testPort = null;
try try
{ {
using SerialPort testPort = new SerialPort(port, 115200) testPort = new SerialPort(port, 115200)
{ {
ReadTimeout = 1000, ReadTimeout = 1000,
WriteTimeout = 1000 WriteTimeout = 1000
@@ -173,15 +190,33 @@ public class SerialPortService : ISerialPortService
response.Contains("DISPLAY_TYPE:OLED") || response.Contains("DISPLAY_TYPE:OLED") ||
response.Contains("DISPLAY_TYPE:TFT")) response.Contains("DISPLAY_TYPE:TFT"))
{ {
// Make sure to close and dispose the port before returning
testPort.Close();
testPort.Dispose();
return port; return port;
} }
testPort.Close(); testPort.Close();
testPort.Dispose();
} }
catch catch (Exception ex)
{ {
// Skip ports that can't be opened _logger.LogDebug(ex, $"Failed to check port {port}");
continue;
// Clean up the port if an exception occurs
if (testPort != null)
{
try
{
if (testPort.IsOpen)
testPort.Close();
testPort.Dispose();
}
catch (Exception disposalEx)
{
_logger.LogDebug(disposalEx, $"Error disposing test port {port}");
}
}
} }
} }
@@ -224,8 +259,10 @@ public class SerialPortService : ISerialPortService
return connected; return connected;
} }
catch catch (Exception ex)
{ {
_logger.LogError(ex, "Error checking connection");
if (_isConnected) if (_isConnected)
{ {
_isConnected = false; _isConnected = false;
@@ -236,4 +273,40 @@ public class SerialPortService : ISerialPortService
return false; return false;
} }
} }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_serialPort != null && _serialPort.IsOpen)
{
try
{
_serialPort.Close();
_serialPort.Dispose();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing serial port");
}
_serialPort = null;
}
}
_disposed = true;
}
}
~SerialPortService()
{
Dispose(false);
}
} }