using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Opc.Ua; using ZB.MOM.WW.OtOpcUa.Client.Shared; using ZB.MOM.WW.OtOpcUa.Client.Shared.Models; using ZB.MOM.WW.OtOpcUa.Client.UI.Services; namespace ZB.MOM.WW.OtOpcUa.Client.UI.ViewModels; /// /// ViewModel for the history panel. /// public partial class HistoryViewModel : ObservableObject { private readonly IUiDispatcher _dispatcher; private readonly IOpcUaClientService _service; [ObservableProperty] private DateTimeOffset? _endTime = DateTimeOffset.UtcNow; [ObservableProperty] private double _intervalMs = 3600000; [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ReadHistoryCommand))] private bool _isConnected; [ObservableProperty] private bool _isLoading; [ObservableProperty] private int _maxValues = 1000; [ObservableProperty] private AggregateType? _selectedAggregateType; [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ReadHistoryCommand))] private string? _selectedNodeId; [ObservableProperty] private DateTimeOffset? _startTime = DateTimeOffset.UtcNow.AddHours(-1); public HistoryViewModel(IOpcUaClientService service, IUiDispatcher dispatcher) { _service = service; _dispatcher = dispatcher; } /// Available aggregate types (null means "Raw"). public IReadOnlyList AggregateTypes { get; } = [ null, AggregateType.Average, AggregateType.Minimum, AggregateType.Maximum, AggregateType.Count, AggregateType.Start, AggregateType.End, AggregateType.StandardDeviation ]; public bool IsAggregateRead => SelectedAggregateType != null; /// History read results. public ObservableCollection Results { get; } = []; partial void OnSelectedAggregateTypeChanged(AggregateType? value) { OnPropertyChanged(nameof(IsAggregateRead)); } private bool CanReadHistory() { return IsConnected && !string.IsNullOrEmpty(SelectedNodeId); } [RelayCommand(CanExecute = nameof(CanReadHistory))] private async Task ReadHistoryAsync() { if (string.IsNullOrEmpty(SelectedNodeId)) return; IsLoading = true; _dispatcher.Post(() => Results.Clear()); try { var nodeId = NodeId.Parse(SelectedNodeId); IReadOnlyList values; var start = (StartTime ?? DateTimeOffset.UtcNow.AddHours(-1)).UtcDateTime; var end = (EndTime ?? DateTimeOffset.UtcNow).UtcDateTime; if (SelectedAggregateType != null) values = await _service.HistoryReadAggregateAsync( nodeId, start, end, SelectedAggregateType.Value, IntervalMs); else values = await _service.HistoryReadRawAsync( nodeId, start, end, MaxValues); _dispatcher.Post(() => { foreach (var dv in values) Results.Add(new HistoryValueViewModel( Helpers.ValueFormatter.Format(dv.Value), Helpers.StatusCodeFormatter.Format(dv.StatusCode), dv.SourceTimestamp.ToString("O"), dv.ServerTimestamp.ToString("O"))); }); } catch (Exception ex) { _dispatcher.Post(() => { Results.Add(new HistoryValueViewModel( $"Error: {ex.Message}", string.Empty, string.Empty, string.Empty)); }); } finally { _dispatcher.Post(() => IsLoading = false); } } /// /// Clears results and resets state. /// public void Clear() { Results.Clear(); SelectedNodeId = null; } }