Major changes: - Added Debug tab with real-time message logging and hex viewer - Implemented timed data logging ($UMTDL command) per API v1.3 - Fixed BLE message parsing to handle notifications without line endings - Added Schema 2 support (22-field timed logging packets) - Updated Maglink BLE UUIDs (fff0/fff1/fff2) - Added connection timeout (10s) to prevent hanging on unavailable devices - Added connection status display and manual push-button logging trigger - Improved retry logging with attempt numbers and delays - Added console window allocation for Windows debug output - Added BLE disconnect on app close Bug fixes: - Fixed device name filtering for Maglink (ML-* prefix) - Fixed RtkGps enum reference in DeviceScanViewModel - Fixed DebugMessage namespace (moved to Models) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
255 lines
6.9 KiB
C#
255 lines
6.9 KiB
C#
using System.Collections.ObjectModel;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using FieldLogger.Models;
|
|
using FieldLogger.Services;
|
|
|
|
namespace FieldLogger.ViewModels;
|
|
|
|
public sealed partial class DebugViewModel : ObservableObject
|
|
{
|
|
private readonly UmReceiverService _receiver;
|
|
private readonly DeviceConnectionManager _manager;
|
|
|
|
[ObservableProperty]
|
|
private bool _isTimedLoggingEnabled;
|
|
|
|
[ObservableProperty]
|
|
private int _logPeriod = 10; // 10 = 1000ms (10 * 100ms)
|
|
|
|
[ObservableProperty]
|
|
private string _timedLoggingStatus = "Disabled";
|
|
|
|
[ObservableProperty]
|
|
private bool _autoScroll = true;
|
|
|
|
[ObservableProperty]
|
|
private bool _showHex = false;
|
|
|
|
[ObservableProperty]
|
|
private int _messageCount;
|
|
|
|
[ObservableProperty]
|
|
private bool _isConnected;
|
|
|
|
[ObservableProperty]
|
|
private string _connectionStatus = "Not connected";
|
|
|
|
[ObservableProperty]
|
|
private string _connectionStateColor = "Gray";
|
|
|
|
public ObservableCollection<DebugMessage> Messages { get; } = new();
|
|
|
|
public string LogPeriodLabel => $"Period: {LogPeriod * 100}ms ({(LogPeriod * 100 / 1000.0):F1}s)";
|
|
|
|
public DebugViewModel(UmReceiverService receiver, DeviceConnectionManager manager)
|
|
{
|
|
_receiver = receiver;
|
|
_manager = manager;
|
|
}
|
|
|
|
public void OnAppearing()
|
|
{
|
|
_receiver.LineReceived += OnLineReceived;
|
|
UpdateConnectionStatus();
|
|
}
|
|
|
|
public void OnDisappearing()
|
|
{
|
|
_receiver.LineReceived -= OnLineReceived;
|
|
}
|
|
|
|
private void UpdateConnectionStatus()
|
|
{
|
|
IsConnected = _receiver.IsConnected;
|
|
if (IsConnected)
|
|
{
|
|
ConnectionStatus = $"Connected to {_receiver.DeviceName ?? "receiver"}";
|
|
ConnectionStateColor = "Green";
|
|
}
|
|
else
|
|
{
|
|
ConnectionStatus = "Not connected";
|
|
ConnectionStateColor = "Gray";
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task EnablePushButtonLoggingAsync()
|
|
{
|
|
try
|
|
{
|
|
await _receiver.EnableLoggingAsync();
|
|
AddMessage("TX", "$UMPBDL,1", "Command");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await Shell.Current.DisplayAlert("Error", $"Failed to enable logging: {ex.Message}", "OK");
|
|
}
|
|
}
|
|
|
|
partial void OnIsTimedLoggingEnabledChanged(bool value)
|
|
{
|
|
_ = UpdateTimedLoggingAsync(value);
|
|
}
|
|
|
|
partial void OnLogPeriodChanged(int value)
|
|
{
|
|
OnPropertyChanged(nameof(LogPeriodLabel));
|
|
if (IsTimedLoggingEnabled)
|
|
{
|
|
_ = UpdateTimedLoggingAsync(true);
|
|
}
|
|
}
|
|
|
|
private async Task UpdateTimedLoggingAsync(bool enabled)
|
|
{
|
|
try
|
|
{
|
|
if (!_receiver.IsConnected)
|
|
{
|
|
TimedLoggingStatus = "Not connected to receiver";
|
|
IsTimedLoggingEnabled = false;
|
|
return;
|
|
}
|
|
|
|
if (enabled)
|
|
{
|
|
await _receiver.EnableTimedLoggingAsync(LogPeriod);
|
|
TimedLoggingStatus = $"Enabled - {LogPeriod * 100}ms interval";
|
|
AddMessage("TX", $"$UMETDL,{LogPeriod}", "Command");
|
|
}
|
|
else
|
|
{
|
|
await _receiver.DisableTimedLoggingAsync();
|
|
TimedLoggingStatus = "Disabled";
|
|
AddMessage("TX", "$UMETDL,0", "Command");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
TimedLoggingStatus = $"Error: {ex.Message}";
|
|
IsTimedLoggingEnabled = false;
|
|
}
|
|
}
|
|
|
|
private void OnLineReceived(object? sender, string line)
|
|
{
|
|
MainThread.BeginInvokeOnMainThread(() =>
|
|
{
|
|
var messageType = ClassifyMessage(line);
|
|
var parsedData = ParseMessage(line, messageType);
|
|
AddMessage("RX", line, messageType, parsedData);
|
|
});
|
|
}
|
|
|
|
private void AddMessage(string direction, string rawMessage, string type, string? parsedData = null)
|
|
{
|
|
var message = new DebugMessage
|
|
{
|
|
Timestamp = DateTime.Now,
|
|
Direction = direction,
|
|
RawMessage = rawMessage,
|
|
Type = type,
|
|
ParsedData = parsedData,
|
|
TypeColor = GetTypeColor(type)
|
|
};
|
|
|
|
Messages.Add(message);
|
|
MessageCount = Messages.Count;
|
|
|
|
// Keep last 500 messages
|
|
while (Messages.Count > 500)
|
|
{
|
|
Messages.RemoveAt(0);
|
|
}
|
|
|
|
// TODO: Auto-scroll if enabled
|
|
}
|
|
|
|
private static string ClassifyMessage(string line)
|
|
{
|
|
line = line.Trim();
|
|
|
|
// Try parsing as UmLogPacket (data packet)
|
|
var packet = UmLogPacket.TryParse(line);
|
|
if (packet is not null)
|
|
{
|
|
return packet.LogType.HasValue ? "UMLOG (Timed)" : "UMLOG (Button)";
|
|
}
|
|
|
|
// Try parsing as device info
|
|
var info = UmDeviceInfo.TryParse(line);
|
|
if (info is not null)
|
|
return "UMDEV";
|
|
|
|
if (line.Equals("OK", StringComparison.OrdinalIgnoreCase))
|
|
return "OK";
|
|
if (line.Equals("ERROR", StringComparison.OrdinalIgnoreCase))
|
|
return "ERROR";
|
|
if (line.StartsWith("$", StringComparison.Ordinal))
|
|
return "Unknown";
|
|
return "Text";
|
|
}
|
|
|
|
private static string? ParseMessage(string line, string type)
|
|
{
|
|
if (type.StartsWith("UMLOG"))
|
|
{
|
|
var packet = UmLogPacket.TryParse(line);
|
|
if (packet is not null)
|
|
{
|
|
var logTypeStr = packet.LogType.HasValue ? $"[{packet.LogType}] " : "";
|
|
return $"{logTypeStr}{packet.Mode}, {packet.Frequency}Hz, Signal={packet.Signal}, " +
|
|
$"Depth={packet.DepthRaw}, Current={packet.CurrentRaw}, Battery={packet.BatteryVoltage:F2}V";
|
|
}
|
|
}
|
|
else if (type == "UMDEV")
|
|
{
|
|
var info = UmDeviceInfo.TryParse(line);
|
|
if (info is not null)
|
|
{
|
|
return $"Mfr={info.Manufacturer}, Model={info.ModelName}, SN={info.SerialNumber}, FW={info.SoftwareVersion}";
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static string GetModeText(string mode)
|
|
{
|
|
return mode switch
|
|
{
|
|
"0" => "Single",
|
|
"1" => "Twin",
|
|
"2" => "Null",
|
|
"3" => "Sweep",
|
|
"4" => "Twin Sweep",
|
|
"5" => "Omni",
|
|
"6" => "Twin Omni",
|
|
_ => $"Mode {mode}"
|
|
};
|
|
}
|
|
|
|
private static string GetTypeColor(string type)
|
|
{
|
|
return type switch
|
|
{
|
|
"OK" => "Green",
|
|
"ERROR" => "Red",
|
|
"UMLOG (Button)" => "Blue",
|
|
"UMLOG (Timed)" => "DodgerBlue",
|
|
"UMDEV" => "Purple",
|
|
"Command" => "Orange",
|
|
_ when type.StartsWith("UMLOG") => "Blue",
|
|
_ => "Gray"
|
|
};
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void ClearMessages()
|
|
{
|
|
Messages.Clear();
|
|
MessageCount = 0;
|
|
}
|
|
}
|