using AgroBase.Models; using AgroBase.Models.Operacoes; using AgroBase.Services; using Mapsui; using Mapsui.Layers; using NetTopologySuite.Geometries; using Newtonsoft.Json.Linq; using OperationControl.Controls; using OperationControl.Helpers; using OperationControl.Mapas; using OperationControl.Mapas.Editing; using OperationControl.Mapas.Interaction; using OperationControl.Mapas.Models; using OperationControl.Mapas.Rendering; using OperationControl.Mapas.Services; using OperationControl.Models; using OperationControl.ViewModels.Views.Operacao; using OperationControl.Views; using OperationControl.Views.Operacao; using System.ComponentModel; using System.Windows; using System.Windows.Input; using static AgroBase.Models.Enums; using static OperationControl.Services.BaseFixService; using static OperationControl.Services.GpsService; using MessageBox = System.Windows.MessageBox; namespace OperationControl.ViewModels { public class DockWindowViewModel : INotifyPropertyChanged { private readonly ConfigConexaoView _viewConfig; private readonly PreparacaoMapaView _viewPreparacaoMapa; public readonly PreparacaoMapaTopView _viewPreparacaoMapaTop; public readonly PreparacaoMapaBottomView _viewPreparacaoMapaBottom; private readonly PreparacaoMapaStatusView _viewPreparacaoMapaStatus; private readonly OperacaoTopView _viewOperacaoTop; public readonly OperacaoCenterView _viewOperacaoCenter; public readonly OperacaoLeftView _viewOperacaoLeft; public readonly OperacaoRightView _viewOperacaoRight; public readonly OperacaoBottomView _viewOperacaoBottom; public OperacaoBottomViewModel BottomVM => _viewOperacaoBottom?._vm; private DockStep _currentStep; public DockWindowViewModel() { // Views _viewConfig = new ConfigConexaoView(); _viewConfig._vm.TudoOkChanged += OnTudoOkChanged; _viewPreparacaoMapa = new PreparacaoMapaView(); _viewPreparacaoMapaTop = new PreparacaoMapaTopView(); _viewPreparacaoMapaBottom = new PreparacaoMapaBottomView(); _viewPreparacaoMapaBottom._vm.CarregarMapaSolicitado += OnCarregarMapaSolicitado; _viewPreparacaoMapaBottom._vm.FixarBaseSolicitado += OnFixarBaseSolicitado; _viewPreparacaoMapaBottom._vm.AplicarOffsetSolicitado += OnAplicarOffsetSolicitado; _viewPreparacaoMapaBottom._vm.UsarGnssMovelSolicitado += OnUsarGnssMovelSolicitado; _viewPreparacaoMapaBottom._vm.ContinuarSolicitado += OnContinuarPreparacaoSolicitado; _viewPreparacaoMapaBottom._vm.VoltarSolicitado += OnVoltarPreparacaoSolicitado; _viewPreparacaoMapaStatus = new PreparacaoMapaStatusView { DataContext = _viewPreparacaoMapaBottom._vm }; _viewOperacaoTop = new OperacaoTopView(); _viewOperacaoCenter = new OperacaoCenterView(); _viewOperacaoCenter._vm.RoverSelecionadoChanged += OnRoverSelecionadoChanged; /* * Salvar arquivo é uma intenção de UI. * O cockpit só dispara o evento, o Dock abre o diálogo * e o Controller continua dono do documento e da exportação. */ _viewOperacaoCenter .Mapa .EditorOverlay .SalvarMapaEditadoSolicitado += OnSalvarMapaEditadoSolicitado; _viewOperacaoLeft = new OperacaoLeftView(); _viewOperacaoRight = new OperacaoRightView(); _viewOperacaoRight._vm.PropertyChanged += OperacaoRightVm_PropertyChanged; _viewOperacaoRight._vm.RoverSelecionadoChanged += OnRoverSelecionadoChanged; _viewOperacaoRight._vm.SecaoSelecionadaChanged += OnSecaoSelecionadaChanged; _viewOperacaoRight.areaParametrizacao._vm.LerParametrosSolicitado += OnLerParametrosSolicitado; _viewOperacaoRight.areaParametrizacao._vm.SalvarParametrosSolicitado += OnSalvarParametrosSolicitado; _viewOperacaoRight.areaParametrizacao._vm.ModoOperacaoAlterado += OnModoOperacaoParametrizacaoAlterado; _viewOperacaoRight.areaParametrizacao._vm.EditarMapaSolicitado += OnEditarMapaSolicitado; _viewOperacaoRight.areaParametrizacao._vm.CancelarEdicaoMapaSolicitado += OnCancelarEdicaoMapaSolicitado; _viewOperacaoBottom = new OperacaoBottomView(); _viewOperacaoBottom._vm.SolicitarAbrirDiagnostico += AbrirDiagnosticoPeloAlerta; // Subsistema de mapas _mapas.LogGerado += OnMapaLogGerado; _mapas.EntidadeClicada += OnMapaEntidadeClicada; _mapas.ValidacaoEdicaoFalhou += OnMapaValidacaoEdicaoFalhou; _mapas.EstadoMapaAlterado += OnEstadoMapaAlterado; _mapas.EstadoEdicaoAlterado += OnEstadoMapaAlterado; // Commands BackCommand = new RelayCommand(_ => GoBack(), _ => CanGoBack); NextCommand = new RelayCommand(_ => GoNext(), _ => CanGoNext); // Estado inicial ConfigConexaoOk = _viewConfig._vm.TudoOk; CurrentStep = DockStep.ConfigConexao; } private void OnTudoOkChanged(bool ok) { ConfigConexaoOk = ok; OnPropertyChanged(nameof(CanGoNext)); CommandManager.InvalidateRequerySuggested(); } // ====== ETAPA ATUAL ====== public ModoOperacao ModoOperacaoAtual => _viewConfig._vm.TipoOperacaoSelecionado; public DockStep CurrentStep { get => _currentStep; set { if (_currentStep != value) { _currentStep = value; OnPropertyChanged(nameof(CurrentStep)); OnPropertyChanged(nameof(CanGoBack)); OnPropertyChanged(nameof(CanGoNext)); OnPropertyChanged(nameof(MostrarPainelEsquerdo)); OnPropertyChanged(nameof(MostrarBotaoVoltarEsquerda)); OnPropertyChanged(nameof(RightPanelWidth)); OnPropertyChanged(nameof(LeftPanelWidth)); AtualizarConteudo(); CommandManager.InvalidateRequerySuggested(); } } } // ====== ESTADO ====== private bool _configConexaoOk; public bool ConfigConexaoOk { get => _configConexaoOk; set { if (_configConexaoOk != value) { _configConexaoOk = value; OnPropertyChanged(nameof(ConfigConexaoOk)); OnPropertyChanged(nameof(CanGoNext)); CommandManager.InvalidateRequerySuggested(); } } } private bool _mapaCarregado; public bool MapaCarregado { get => _mapaCarregado; set { if ( _mapaCarregado == value ) { return; } _mapaCarregado = value; OnPropertyChanged( nameof(MapaCarregado) ); OnPropertyChanged( nameof(CanGoNext) ); _viewPreparacaoMapaBottom? ._vm? .DefinirMapaCarregado( value ); CommandManager .InvalidateRequerySuggested(); } } private bool _baseFixada = AppShell.Mock; public bool BaseFixada { get => _baseFixada; set { if (_baseFixada != value) { _baseFixada = value; OnPropertyChanged(nameof(BaseFixada)); OnPropertyChanged(nameof(CanGoNext)); CommandManager.InvalidateRequerySuggested(); } } } public bool CanGoBack => CurrentStep != DockStep.ConfigConexao; public bool CanGoNext { get { return CurrentStep switch { DockStep.ConfigConexao => PodeAvancarConfigConexao(), DockStep.PreparacaoMapa => MapaCarregado, _ => false }; } } private bool PodeAvancarConfigConexao() { return ModoOperacaoAtual switch { ModoOperacao.Automatico => _viewConfig._vm.RedeOk && _viewConfig._vm.GnssOk, ModoOperacao.Manual => _viewConfig._vm.RedeOk, _ => false }; } public bool MostrarPainelEsquerdo => CurrentStep == DockStep.ParametrizacaoRover; public bool MostrarBotaoVoltarEsquerda => CurrentStep != DockStep.ParametrizacaoRover && CurrentStep != DockStep.PreparacaoMapa; public GridLength LeftPanelWidth { get { return CurrentStep switch { /* * A preparação agora possui sua própria * navegação dentro do painel direito. */ DockStep.PreparacaoMapa => new GridLength(0), DockStep.ParametrizacaoRover => new GridLength(280), _ => new GridLength(60) }; } } public GridLength RightPanelWidth { get { return CurrentStep switch { DockStep.PreparacaoMapa => new GridLength(360), DockStep.ParametrizacaoRover => _viewOperacaoRight?._vm?.IsCollapsed == true ? new GridLength(44) : new GridLength(320), _ => new GridLength(60) }; } } private void OperacaoRightVm_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(OperacaoRightViewModel.IsCollapsed)) { OnPropertyChanged(nameof(RightPanelWidth)); } } // ====== CONTEÚDOS ====== private object _centerContent; public object CenterContent { get => _centerContent; set { _centerContent = value; OnPropertyChanged(nameof(CenterContent)); } } private object _topContent; public object TopContent { get => _topContent; set { _topContent = value; OnPropertyChanged(nameof(TopContent)); } } private object _bottomContent; public object BottomContent { get => _bottomContent; set { _bottomContent = value; OnPropertyChanged(nameof(BottomContent)); } } private object _leftContent; public object LeftContent { get => _leftContent; set { _leftContent = value; OnPropertyChanged(nameof(LeftContent)); } } private object _rightContent; public object RightContent { get => _rightContent; set { _rightContent = value; OnPropertyChanged(nameof(RightContent)); } } // ====== COMMANDS ====== public ICommand BackCommand { get; } public ICommand NextCommand { get; } private void GoBack() { switch (CurrentStep) { case DockStep.PreparacaoMapa: CurrentStep = DockStep.ConfigConexao; break; } } private void GoNext() { if (!CanGoNext) return; switch (CurrentStep) { case DockStep.ConfigConexao: if (ModoOperacaoAtual == ModoOperacao.Manual) CurrentStep = DockStep.ParametrizacaoRover; else CurrentStep = DockStep.PreparacaoMapa; break; case DockStep.PreparacaoMapa: CurrentStep = DockStep.ParametrizacaoRover; break; } } private FrameworkElement CriarBotaoNextSimples() { return new System.Windows.Controls.Button { Content = "⮞", FontSize = 24, Width = 40, Height = 40, HorizontalAlignment = System.Windows.HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, Command = NextCommand }; } private void AtualizarConteudo() { TopContent = null; BottomContent = null; LeftContent = null; RightContent = null; CenterContent = null; switch (CurrentStep) { case DockStep.ConfigConexao: CenterContent = _viewConfig; RightContent = CriarBotaoNextSimples(); break; case DockStep.PreparacaoMapa: TopContent = _viewPreparacaoMapaTop; CenterContent = _viewPreparacaoMapa; /* * Status enxuto na faixa inferior. */ BottomContent = _viewPreparacaoMapaStatus; /* * Controles principais agora possuem * uma lateral própria. */ RightContent = _viewPreparacaoMapaBottom; _mapas.PrepararMapa( _viewPreparacaoMapa? .MapaPreparacao, modoInteracao: MapaModoInteracao .Visualizacao, enquadrarDocumento: true ); _viewPreparacaoMapaBottom? ._vm? .DefinirMapaCarregado( MapaCarregado ); SincronizarEstadoGnssPreparacao(); break; case DockStep.ParametrizacaoRover: TopContent = _viewOperacaoTop; LeftContent = _viewOperacaoLeft; CenterContent = _viewOperacaoCenter; RightContent = _viewOperacaoRight; BottomContent = _viewOperacaoBottom; _mapas.PrepararMapa( _viewOperacaoCenter?.Mapa, modoInteracao: ObterModoInteracaoMapaOperacao(), enquadrarDocumento: true ); AtualizarBarraSuperior( StatusModulo.Conectado, "BASE", "VISÃO GERAL", "Selecione um equipamento na lista à direita para configurá-lo", "NORMAL", "Aguardando" ); break; } OnPropertyChanged(nameof(MostrarPainelEsquerdo)); OnPropertyChanged(nameof(MostrarBotaoVoltarEsquerda)); } // ====== INotifyPropertyChanged ====== public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string nome) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nome)); } public void AtualizarBarraSuperior(OperacaoParametrosModel rover) { var obj = rover?.DadosLeitura; if (rover == null || obj == null) return; if (rover.RoverId != VariaveisControleOperacao.SelectedRoverId) return; var eventos = new List(); double tempoSemResposta = (DateTime.Now - rover.UltimoContato).TotalSeconds; bool semComunicacao = !AppShell.Mock && tempoSemResposta >= VariaveisControleOperacao.TempoRoverVivo; // ============================ // 0) Perda de comunicação // ============================ if (semComunicacao) { eventos.Add(new MotivoTopBarModel { Prioridade = 2000, Fonte = "Comunicacao", Titulo = "PERDA DE COMUNICAÇÃO COM O ROVER", Descricao = $"Sem resposta do rover há {tempoSemResposta:F1} s. Tentando reconectar automaticamente.", StatusVisual = StatusModulo.Desconectado }); } // ============================ // 0) NCP desconectado // ============================ if (obj.ModulosSaude?.All(x => (x?.status ?? StatusModulo.Desconectado) == StatusModulo.Desconectado) ?? false) { eventos.Add(new MotivoTopBarModel { Prioridade = 1100, Fonte = "NCP", Titulo = "NÚCLEO CENTRAL DE PROCESSAMENTO DESCONECTADO", Descricao = "O núcleo central de processamento foi desconectado, tentando reconectar...", StatusVisual = StatusModulo.Falha }); } // ============================ // 1) Emergência // ============================ if (obj.Operacao?.Emergencia == true) { eventos.Add(new MotivoTopBarModel { Prioridade = 1000, Fonte = "Emergencia", Titulo = "OPERAÇÃO BLOQUEADA POR EMERGÊNCIA", Descricao = "Parada de emergência solicitada.", StatusVisual = StatusModulo.Falha }); } // ============================ // 2) Pausa // ============================ if (obj.Operacao?.Pausa == true) { eventos.Add(new MotivoTopBarModel { Prioridade = 900, Fonte = "Pausa", Titulo = "OPERAÇÃO PAUSADA", Descricao = "Pausa operacional solicitada.", StatusVisual = StatusModulo.Alerta }); } // ============================ // 3) Controle // ============================ var motivosControle = obj.Controle?.Motivos? .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct() .ToList() ?? new List(); foreach (var motivo in motivosControle) { eventos.Add(new MotivoTopBarModel { Prioridade = 800, Fonte = "Controle", Titulo = "CONTROLE SEM LIBERAÇÃO", Descricao = motivo, StatusVisual = StatusModulo.Alerta }); } // ============================ // 4) Trajetória / autonomia // ============================ var motivosTrajetoria = obj.Trajetoria?.AutonomiaCorredor?.Motivos? .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct() .ToList() ?? new List(); foreach (var motivo in motivosTrajetoria) { eventos.Add(new MotivoTopBarModel { Prioridade = 700, Fonte = "Trajetoria", Titulo = "TRAJETÓRIA BLOQUEADA", Descricao = motivo, StatusVisual = StatusModulo.Alerta }); } // ============================ // 5) Módulos com condições operacionais críticas // ============================ var modulosCriticos = obj.ModulosSaude? .Where(mod => (mod?.condicoes_operacionais?.Any(cond => cond?.severidade > 90) ?? false) || ( !new List() { StatusModulo.Operante, StatusModulo.Alerta }.Contains(mod?.status ?? StatusModulo.Desconectado) && (rover?.ModulosMandatorios?.Any(x => (x?.Dispositivo ?? T_Code.Vzo) == (mod?.modulo ?? T_Code.Vzo) && (x?.Utilizar ?? false) && (x?.Mandatorio ?? false) ) ?? false) ) || ( (mod?.status ?? StatusModulo.Desconectado) == StatusModulo.Alerta && (rover?.ModulosMandatorios?.Any(x => (x?.Dispositivo ?? T_Code.Vzo) == (mod?.modulo ?? T_Code.Vzo) && (x?.Utilizar ?? false) && (x?.Mandatorio ?? false) ) ?? false) ) ) .ToList() ?? new List(); foreach (var modulo in modulosCriticos) { var nomeModulo = (modulo?.modulo ?? T_Code.Vzo).ToString(); bool temCondicaoSevera = modulo?.condicoes_operacionais?.Any(cond => cond?.severidade > 90) ?? false; bool moduloMandatorioNaoOperante = !new List() { StatusModulo.Operante, StatusModulo.Alerta }.Contains(modulo?.status ?? StatusModulo.Desconectado) && (rover?.ModulosMandatorios?.Any(x => (x?.Dispositivo ?? T_Code.Vzo) == (modulo?.modulo ?? T_Code.Vzo) && (x?.Utilizar ?? false) && (x?.Mandatorio ?? false) ) ?? false); bool moduloMandatorioAlerta = (modulo?.status ?? StatusModulo.Desconectado) == StatusModulo.Alerta && (rover?.ModulosMandatorios?.Any(x => (x?.Dispositivo ?? T_Code.Vzo) == (modulo?.modulo ?? T_Code.Vzo) && (x?.Utilizar ?? false) && (x?.Mandatorio ?? false) ) ?? false); // Caso 1: módulo mandatório não operante if (moduloMandatorioNaoOperante) { var statusTexto = (modulo?.status ?? StatusModulo.Desconectado).ToString(); string motivos = string.Join("; ", modulo?.motivos ?? new List() { "Desconhecido" }); eventos.Add(new MotivoTopBarModel { Prioridade = 620, Fonte = "ModuloCritico", Titulo = "MÓDULO MANDATÓRIO NÃO OPERACIONAL", Descricao = $"{nomeModulo}: módulo mandatório em estado '{statusTexto}': {motivos}", StatusVisual = StatusModulo.Falha, Modulo = modulo?.modulo }); } // Caso 2: condições operacionais severas if (temCondicaoSevera) { var descricoesCriticas = modulo?.condicoes_operacionais? .Where(cond => cond?.severidade > 90) .Select(cond => cond?.descricao ?? "") .Where(desc => !string.IsNullOrWhiteSpace(desc)) .Distinct() .ToList() ?? new List(); foreach (var desc in descricoesCriticas) { eventos.Add(new MotivoTopBarModel { Prioridade = 610, Fonte = "ModuloCritico", Titulo = "CONDIÇÕES OPERACIONAIS CRÍTICAS", Descricao = $"{nomeModulo}: {desc}", StatusVisual = StatusModulo.Alerta, Modulo = modulo?.modulo }); } } // Caso 3: módulo mandatório em alerta if (moduloMandatorioAlerta) { var statusTexto = (modulo?.status ?? StatusModulo.Desconectado).ToString(); string motivos = string.Join("; ", modulo?.motivos ?? new List() { "Desconhecido" }); eventos.Add(new MotivoTopBarModel { Prioridade = 600, Fonte = "ModuloCritico", Titulo = "MÓDULO MANDATÓRIO EM ALERTA", Descricao = $"{nomeModulo}: módulo mandatório em estado '{statusTexto}': {motivos}", StatusVisual = StatusModulo.Alerta, Modulo = modulo?.modulo }); } } var ordemModulos = new Dictionary { { T_Code.Ipb, 1 }, { T_Code.Npc, 2 }, { T_Code.Can, 3 }, { T_Code.Bat, 4 }, { T_Code.Gps, 5 }, { T_Code.Atu, 6 }, { T_Code.Sen, 7 } }; // ============================ // 6) Remove vazios / repetidos // ============================ var eventosOrdenados = eventos .Where(x => !string.IsNullOrWhiteSpace(x.Titulo) && !string.IsNullOrWhiteSpace(x.Descricao)) .GroupBy(x => new { x.Fonte, x.Titulo, x.Descricao, x.Prioridade, x.StatusVisual }) .Select(g => g.First()) .OrderByDescending(x => x.Prioridade) .ThenBy(x => x.Modulo.HasValue && ordemModulos.ContainsKey(x.Modulo.Value)? ordemModulos[x.Modulo.Value] : int.MaxValue) .ThenBy(x => x.Fonte) .ToList(); if (!eventosOrdenados.Any()) { eventosOrdenados.Add(new MotivoTopBarModel { Prioridade = 0, Fonte = "Normal", Titulo = "ROVER OPERANDO NORMALMENTE", Descricao = "Trajetória, controle e operação em condição normal.", StatusVisual = obj.StatusRover == StatusModulo.Desconectado ? StatusModulo.Desconectado : StatusModulo.Operante }); } // ============================ // 7) Monta visual final // ============================ StatusModulo statusVisual; string linha1; string linha2; string badge; string resumo; if (eventosOrdenados.Any()) { var principal = eventosOrdenados.First(); statusVisual = principal.StatusVisual; linha1 = principal.Titulo; // Pega até 3 descrições distintas, por prioridade var descricoes = eventosOrdenados .Select(x => x.Descricao?.Trim()) .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct() .Take(3) .ToList(); linha2 = string.Join(" • ", descricoes); // Badge curto badge = principal.Fonte switch { "Comunicacao" => "DESCONECTADO", "Emergencia" => "EMERGÊNCIA", "Pausa" => "PAUSADO", "Trajetoria" => "TRAJETÓRIA", "Controle" => "CONTROLE", "ModuloCritico" => "ALERTA", _ => statusVisual.ToString().ToUpper() }; // Resumo curto resumo = principal.Fonte switch { "Comunicacao" => "Sem telemetria", "Emergencia" => "Parada imediata", "Pausa" => "Aguardando retomada", "Trajetoria" => "Operação bloqueada", "Controle" => "Controle bloqueado", "ModuloCritico" => "Atenção operacional", _ => (obj.Operacao?.Status ?? StatusOperacao.NaoIniciado).ToString() }; } else { statusVisual = obj.StatusRover == StatusModulo.Desconectado ? StatusModulo.Desconectado : StatusModulo.Operante; linha1 = "ROVER OPERANDO NORMALMENTE"; linha2 = "Trajetória, controle e operação em condição normal."; badge = statusVisual.ToString().ToUpper(); resumo = (obj.Operacao?.Status ?? StatusOperacao.NaoIniciado).ToString(); } _viewOperacaoCenter?.Historico?._vm?.CompararERegistrarMudancasEventos(rover.RoverId, eventosOrdenados); // Segurança extra: evita linha vazia if (string.IsNullOrWhiteSpace(linha2)) linha2 = "Sem detalhes adicionais."; // ============================ // 8) Atualiza UI // ============================ System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => { _viewOperacaoTop?._vm?.AtualizarStatus( statusVisual, rover.RoverId, obj?.Operacao?.Status ?? StatusOperacao.NaoIniciado, linha1, linha2, badge, resumo ); })); } public void AtualizarBarraSuperior(StatusModulo status, string titulo, string linha1, string linha2, string status_str, string resumo) { System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => { _viewOperacaoTop?._vm?.AtualizarStatus(status, titulo, StatusOperacao.Parametrizando, linha1, linha2.Replace("\n\n", " • "), status_str, resumo); })); } public void AtualizarDadosGnss(AgroBase.Models.GPSModel dados) { var bf = Models.Variaveis.GpsService.BaseFix; if (bf.CorrecaoEmAndamento) { AtualizarProgressoFixacaoBase( bf.Progresso, bf.ProgressoStr ); if (bf.Progresso >= 100) { FinalizarFixacaoBase( true, bf.ProgressoStr ); } } MapViewControl? mapaAtual = CurrentStep == DockStep.PreparacaoMapa ? _viewPreparacaoMapa?.MapaPreparacao : CurrentStep == DockStep.ParametrizacaoRover ? _viewOperacaoCenter?.Mapa : null; if (CurrentStep == DockStep.PreparacaoMapa) { _viewPreparacaoMapaTop? ._vm? .AtualizarDados( dados ); var gps = Models.Variaveis.GpsService; if (gps != null) { var papel = gps.PapelEstacaoAtual; if ( _viewPreparacaoMapaBottom? ._vm? .PapelGnssAtual != papel ) { BaseFixada = papel == GnssPapelEstacao .BaseFixa; _viewPreparacaoMapaBottom? ._vm? .AtualizarPapelGnss( papel ); } } } _mapas.AtualizarBase( dados, mapaAtual ); } public void AtualizarParametrosRover(OperacaoParametrosModel? rover) { if (rover == null) return; bool roverEmFoco = string.Equals(rover.RoverId, VariaveisControleOperacao.SelectedRoverId, StringComparison.OrdinalIgnoreCase); if (roverEmFoco) { _viewOperacaoRight?.areaParametrizacao?._vm?.AtualizarParametros(rover); bool respostaSolicitada = string.Equals(rover.RoverId, _roverAguardandoParametrosId, StringComparison.OrdinalIgnoreCase); if (respostaSolicitada) { _roverAguardandoParametrosId = null; AtualizarDadosMapaDoRover(rover); } } AtualizarDadosTela(rover); } private void SincronizarEstadoGnssPreparacao() { var gps = Models.Variaveis.GpsService; if (gps == null) { BaseFixada = false; _viewPreparacaoMapaBottom? ._vm? .AtualizarPapelGnss( GnssPapelEstacao .Indefinido ); return; } GnssPapelEstacao papel = gps.PapelEstacaoAtual; BaseFixada = papel == GnssPapelEstacao.BaseFixa; _viewPreparacaoMapaBottom? ._vm? .AtualizarPapelGnss( papel ); } #region MAPAS /* * O Dock conhece somente a fachada do subsistema de mapas. * Workspace, services, renderers, layers, movimento e edição * ficam encapsulados dentro do MapaOperacaoController. */ public readonly MapaOperacaoController _mapas = new(); /// /// Política da aplicação para a interação do mapa central. /// /// O MapViewControl não consulta mais Dock, Rover ou modo /// operacional por conta própria. Toda essa decisão fica aqui. /// private MapaModoInteracao ObterModoInteracaoMapaOperacao() { if (CurrentStep != DockStep.ParametrizacaoRover) { return MapaModoInteracao.Visualizacao; } var rover = VariaveisControleOperacao.RoverEmFoco; /* * Sem Rover selecionado, o mapa central serve * para escolher Base/Rover pelas entidades. */ if (rover == null) { return MapaModoInteracao.SelecaoEntidade; } if (_viewOperacaoRight?._vm?.SecaoAtual != SecaoRightBar.Parametrizacao) { return MapaModoInteracao.Visualizacao; } var operacao = rover.DadosLeitura?.Operacao; /* * Preserva a mesma trava que antes estava * escondida dentro do MapViewControl. */ if ( (operacao?.Iniciada ?? false) || (operacao?.Status ?? StatusOperacao.NaoIniciado) != StatusOperacao.Parametrizando ) { return MapaModoInteracao.Visualizacao; } AgroBase.Models.Enums.ModoOperacao modo = _viewOperacaoRight?.areaParametrizacao?._vm?.Parametros?.Modo ?? operacao.Modo; return modo switch { AgroBase.Models.Enums.ModoOperacao.MapaGPS => MapaModoInteracao.SelecaoOperacao, AgroBase.Models.Enums.ModoOperacao.RetornoBase => MapaModoInteracao.RetornoManual, _ => MapaModoInteracao.Visualizacao }; } private void AtualizarModoInteracaoMapaOperacao() { _mapas.DefinirModoInteracao( _viewOperacaoCenter?.Mapa, ObterModoInteracaoMapaOperacao() ); } private void OnModoOperacaoParametrizacaoAlterado(AgroBase.Models.Enums.ModoOperacao modo) { /* * Mantém o estado desejado do Rover sincronizado * antes de recalcular a política de interação. */ var rover = VariaveisControleOperacao.RoverEmFoco; if (rover != null) { rover.Modo = modo; } AtualizarModoInteracaoMapaOperacao(); } private void OnMapaLogGerado(string mensagem) { Models.Variaveis.MostrarLog(mensagem); } private void OnMapaEntidadeClicada(string entidadeId) { try { var entidade = _mapas.ObterEntidade(entidadeId); if (entidade == null) return; if (entidade.Tipo == MapaTipoEntidadeDinamica.Base) { OnRoverSelecionadoChanged((RoverCardModel)null); return; } _viewOperacaoRight?._vm?.SelecionarRover(entidade.Id); } catch (Exception ex) { Models.Variaveis.MostrarLog("[MAPA][ENTIDADE][DOCK][ERRO] " + ex.Message); } } private void OnMapaValidacaoEdicaoFalhou(MapaResultadoValidacao resultado) { if (resultado == null) return; const int maxErrosExibidos = 8; List erros = resultado .Erros .Take( maxErrosExibidos ) .ToList(); string detalhes = string.Join( Environment.NewLine, erros.Select( x => "• " + x ) ); int restantes = resultado.QuantidadeErros - erros.Count; if (restantes > 0) { detalhes += Environment.NewLine + $"• ... e mais {restantes} " + $"{(restantes == 1 ? "erro" : "erros")}."; } string mensagem = "A ação foi cancelada porque " + "o mapa editado não passou na validação." + Environment.NewLine + Environment.NewLine + detalhes + Environment.NewLine + Environment.NewLine + "O mapa oficial não foi alterado. " + "A sessão de edição continua aberta " + "para que o problema possa ser corrigido."; System.Windows.MessageBox.Show( mensagem, "Mapa inválido", MessageBoxButton.OK, MessageBoxImage.Warning ); } private void OnSalvarMapaEditadoSolicitado() { try { if (!_mapas.EdicaoAtiva) return; string nomeSugerido = _mapas .ObterNomeArquivoSugeridoMapaEditado(); string? diretorioOrigem = _mapas .ObterDiretorioOrigemMapaEdicao(); var dialog = new Microsoft.Win32.SaveFileDialog { Title = "Salvar versão editada do mapa", Filter = "GeoJSON (*.geojson)|*.geojson", DefaultExt = ".geojson", AddExtension = true, FileName = nomeSugerido, OverwritePrompt = true }; if ( !string.IsNullOrWhiteSpace( diretorioOrigem ) && System.IO.Directory.Exists( diretorioOrigem ) ) { dialog.InitialDirectory = diretorioOrigem; } bool? resultado = dialog.ShowDialog(); if (resultado != true) return; bool salvo = _mapas .SalvarMapaEditadoGeoJson( dialog.FileName ); if (!salvo) return; Models.Variaveis.MostrarLog( "[MAPA][EDICAO][SALVAR][UI] " + dialog.FileName ); MessageBox.Show( "Mapa operacional salvo com sucesso." + Environment.NewLine + Environment.NewLine + dialog.FileName + Environment.NewLine + Environment.NewLine + "A edição continua aberta e o histórico " + "Desfazer/Refazer foi preservado.", "Mapa salvo", MessageBoxButton.OK, MessageBoxImage.Information ); } catch (Exception ex) { Models.Variaveis.MostrarLog( "[MAPA][EDICAO][SALVAR][ERRO] " + ex ); MessageBox.Show( "Não foi possível salvar o mapa editado." + Environment.NewLine + Environment.NewLine + ex.Message, "Erro ao salvar mapa", MessageBoxButton.OK, MessageBoxImage.Error ); } } /* * Wrappers públicos mantidos porque existem consumidores * externos ao Dock que já chamam essas APIs. * A implementação real agora pertence ao Controller. */ public void LimparMovimentoRover(string? roverId, bool limparPredicao = true) { _mapas.LimparMovimentoRover( roverId, limparPredicao ); } public void AtualizarStatusRoverMapa(string? roverId, StatusOperacao status) { _mapas.AtualizarStatusRoverMapa( roverId, status, _viewOperacaoCenter?.Mapa ); } public bool MostrarReferenciasGnss => _mapas.MostrarReferenciasGnss; public void DefinirExibicaoReferenciasGnss(bool mostrar) { _mapas.DefinirExibicaoReferenciasGnss(mostrar); } private void OnEstadoMapaAlterado() { AtualizarEstadoMapaParametrizacao(); } private void AtualizarEstadoMapaParametrizacao() { void Atualizar() { var vm = _viewOperacaoRight? .areaParametrizacao? ._vm; if (vm == null) return; double? velocidadeSemErvasMs = null; double? velocidadeComErvasMs = null; var controle = vm .Parametros? .Controle; if (controle != null) { double vSem = AgroBase.Models .FuncoesMatematicas .CalculaVelocidadeMsPercentual( controle .MovVelocidadeSErvasPercent ) * 0.8; double vCom = AgroBase.Models .FuncoesMatematicas .CalculaVelocidadeMsPercentual( controle .MovVelocidadeCErvasPercent ); if ( double.IsFinite(vSem) && vSem > 0 ) { velocidadeSemErvasMs = vSem; } if ( double.IsFinite(vCom) && vCom > 0 ) { velocidadeComErvasMs = vCom; } } /* * Durante parametrização podemos ainda não ter * informação real de ervas. * * Nesse caso usamos 0%, exatamente de forma * explícita e previsível. */ double percentualErvas = AgroBase.Models .Variaveis .OperacaoEmAndamento? .Sensoriamento? .Atuador? .PercentualErvasTerreno ?? 0; var estimativa = _mapas .CalcularEstimativaOperacao( velocidadeSemErvasMs, velocidadeComErvasMs, percentualErvas ); vm.AtualizarEstadoMapa( quantidadeRuas: _mapas.QuantidadeRuas, quantidadeRuasPercurso: _mapas .QuantidadeRuasSelecionadas, edicaoAtiva: _mapas.EdicaoAtiva, quantidadeSelecionadasEdicao: _mapas .QuantidadeRuasSelecionadasEdicao, edicaoModificada: _mapas.EdicaoModificada, quantidadeCorredores: estimativa .QuantidadeCorredores, distanciaOperacaoMetros: estimativa .DistanciaMetros, areaOperacaoHectares: estimativa .AreaHectares, larguraMediaCorredorMetros: estimativa .LarguraMediaMetros, tempoEstimadoOperacaoSegundos: estimativa .TempoEstimadoSegundos ); } var dispatcher = System.Windows.Application .Current? .Dispatcher; if ( dispatcher == null || dispatcher.CheckAccess() ) { Atualizar(); return; } dispatcher.BeginInvoke( new Action( Atualizar ) ); } private void OnEditarMapaSolicitado() { try { if (!_mapas.PossuiMapa) { System.Windows.MessageBox.Show( "Não existe um mapa operacional carregado para edição.", "Editar mapa", MessageBoxButton.OK, MessageBoxImage.Information ); return; } var mapa = _viewOperacaoCenter? .Mapa; if (mapa == null) return; var operacao = VariaveisControleOperacao .RoverEmFoco? .DadosLeitura? .Operacao; if (operacao?.Iniciada == true) { System.Windows.MessageBox.Show( "O mapa não pode ser editado enquanto a operação estiver em andamento.", "Editar mapa", MessageBoxButton.OK, MessageBoxImage.Warning ); return; } if ( operacao != null && operacao.Status != StatusOperacao.Parametrizando ) { System.Windows.MessageBox.Show( "O mapa só pode ser editado durante a parametrização da operação.", "Editar mapa", MessageBoxButton.OK, MessageBoxImage.Warning ); return; } _mapas.IniciarEdicao( mapa ); } catch (Exception ex) { Models.Variaveis.MostrarLog( "[MAPA][EDICAO][INICIAR][ERRO] " + ex ); System.Windows.MessageBox.Show( $"Não foi possível iniciar a edição do mapa.\n\n{ex.Message}", "Editar mapa", MessageBoxButton.OK, MessageBoxImage.Error ); } } private void OnCancelarEdicaoMapaSolicitado() { try { var mapa = _viewOperacaoCenter? .Mapa; _mapas.CancelarEdicao( mapa ); } catch (Exception ex) { Models.Variaveis.MostrarLog( "[MAPA][EDICAO][CANCELAR][ERRO] " + ex ); } } #endregion #region TELA 2 private void AtualizarDadosMapaDoRover(OperacaoParametrosModel? dados) { if (dados == null) return; try { var documento = _mapas.CarregarMapaDoRover( dados ); _mapas.PrepararMapa( _viewOperacaoCenter?.Mapa, modoInteracao: ObterModoInteracaoMapaOperacao(), enquadrarDocumento: documento != null ); } catch (Exception ex) { Models.Variaveis.MostrarLog("[MAPA][ROVER][ERRO] " + ex); } } private void OnCarregarMapaSolicitado(string caminhoArquivo, TipoMapaOperacao tipo) { try { _mapas.CarregarMapaArquivo(caminhoArquivo, tipo); _mapas.PrepararMapa( _viewPreparacaoMapa?.MapaPreparacao, modoInteracao: MapaModoInteracao.Visualizacao, enquadrarDocumento: true ); MapaCarregado = true; } catch (Exception ex) { Models.Variaveis.MostrarLog($"[MAPA] Falha: {ex}"); MapaCarregado = false; } } private async void OnFixarBaseSolicitado(MetodoFixacaoBase metodoFix, double? lat, double? lon, double? alt) { try { if (!(Models.Variaveis.GpsService.BaseFix?.FixLiberado ?? false)) { bool fixar = false; if (Models.Variaveis.GpsService.BaseFix?.PosicaoBaseFixada ?? false) { var res = System.Windows.MessageBox.Show( "Mudar posição da base?", "Tem certeza que deseja fixar a posição da base novamente?", MessageBoxButton.YesNo, MessageBoxImage.Question); if (res == MessageBoxResult.Yes) fixar = true; } else { fixar = true; } if (fixar) { _viewPreparacaoMapaBottom?._vm?.IniciarFixacao(); await Models.Variaveis.GpsService.ConfigurarModulo(true, metodoFix, lat, lon, alt); } } else { Models.Variaveis .GpsService .BaseFix .ReiniciarFix(); BaseFixada = false; _viewPreparacaoMapaBottom? ._vm? .CancelarFixacao( "Fixação da base cancelada." ); _viewPreparacaoMapaBottom? ._vm? .AtualizarPapelGnss( Models.Variaveis .GpsService .PapelEstacaoAtual ); } } catch (Exception ex) { _viewPreparacaoMapaBottom?._vm.FinalizarFixacao(false, $"Erro na fixação da base: {ex.Message}"); } } public void AtualizarProgressoFixacaoBase(double progresso, string status) { System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => { _viewPreparacaoMapaBottom?._vm.AtualizarProgresso(progresso, status); })); } public void FinalizarFixacaoBase(bool sucesso, string mensagem) { System.Windows.Application .Current .Dispatcher .BeginInvoke( new Action( () => { BaseFixada = sucesso; _viewPreparacaoMapaBottom? ._vm .FinalizarFixacao( sucesso, mensagem ); var papel = Models.Variaveis .GpsService? .PapelEstacaoAtual ?? GnssPapelEstacao .Indefinido; _viewPreparacaoMapaBottom? ._vm .AtualizarPapelGnss( papel ); } ) ); } public void CancelarFixacaoBase(string mensagem) { System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => { BaseFixada = false; _viewPreparacaoMapaBottom?._vm.CancelarFixacao(mensagem); })); } private async void OnAplicarOffsetSolicitado(double? offsetFrontalCm, double? offsetLateralCm) { if (offsetFrontalCm == null || offsetLateralCm == null) return; _viewPreparacaoMapaBottom?._vm.IniciarFixacao(); var gps = Models.Variaveis.GpsService; gps.LeverArm = new GeoLeverArm(offsetCampoFrontalCm: offsetFrontalCm.Value, offsetCampoLateralCm: offsetLateralCm.Value); (double lat, double lon) = gps.LeverArm.FixLeverArmLatLon_Fast(gps.BaseFix.LatitudeFix, gps.BaseFix.LongitudeFix, gps.BaseFix.OrientacaoFix, 5); await Models.Variaveis.GpsService.ConfigurarModulo(true, MetodoFixacaoBase.Manual, lat, lon, gps.BaseFix.AltitudeElpsoidalFix, offset: true); } private async void OnUsarGnssMovelSolicitado() { var gps = Models.Variaveis.GpsService; if ( gps == null || !gps.IsConnected ) { MessageBox.Show( "O receptor GNSS não está conectado.", "GNSS indisponível", MessageBoxButton.OK, MessageBoxImage.Warning ); return; } /* * Não alteramos o papel físico do GNSS * durante uma captura automática da borda. */ if ( _mapas.CapturaBordaGnssAutomaticaAtiva ) { MessageBox.Show( "Existe um levantamento GNSS em andamento.\n\n" + "Finalize ou descarte a borda antes de alterar " + "o modo do receptor.", "Levantamento em andamento", MessageBoxButton.OK, MessageBoxImage.Warning ); return; } if ( gps.PapelEstacaoAtual == GnssPapelEstacao.BaseFixa ) { var resposta = MessageBox.Show( "A estação deixará de funcionar como base fixa " + "e poderá ser movimentada para levantamento e edição do mapa.\n\n" + "Deseja continuar?", "Liberar GNSS para levantamento", MessageBoxButton.YesNo, MessageBoxImage.Question ); if ( resposta != MessageBoxResult.Yes ) { return; } } try { _viewPreparacaoMapaBottom? ._vm? .IniciarAlteracaoPapelGnss(); await gps .ConfigurarComoMovelParaLevantamentoAsync( usarNtrip: true ); BaseFixada = false; _viewPreparacaoMapaBottom? ._vm? .FinalizarAlteracaoPapelGnss( true, GnssPapelEstacao.Movel, "GNSS liberado para levantamento. " + "A antena pode ser movimentada." ); } catch (Exception ex) { _viewPreparacaoMapaBottom? ._vm? .FinalizarAlteracaoPapelGnss( false, gps.PapelEstacaoAtual, "Não foi possível alterar o modo do GNSS: " + ex.Message ); Models.Variaveis.MostrarLog( "[GNSS][PAPEL][ERRO] " + ex ); } } private void OnContinuarPreparacaoSolicitado() { /* * A decisão de navegação continua * pertencendo ao Dock. */ if ( CurrentStep != DockStep.PreparacaoMapa ) { return; } if (!CanGoNext) return; GoNext(); } private void OnVoltarPreparacaoSolicitado() { if ( CurrentStep != DockStep.PreparacaoMapa ) { return; } GoBack(); } #endregion #region TELA 3 public void AtualizarListaRovers(List rovers_atual) { var cards = rovers_atual .Select( x => new RoverCardModel() { EquipamentoId = x.IP, NumeroSerie = x.RoverId, Status = x.DadosLeitura?.StatusRover ?? StatusModulo.Desconectado } ) .ToArray(); _viewOperacaoRight?._vm?.AtualizarListaRovers(cards); _mapas.SincronizarRovers(rovers_atual, _viewOperacaoCenter?.Mapa); } public void AtualizarDadosTela(OperacaoParametrosModel rover) { if (rover == null) return; var obj = rover.DadosLeitura; if (obj == null) return; // MAPA _mapas.AtualizarRover( rover, _viewOperacaoCenter?.Mapa ); /* * Acompanhar rover continua sendo função * de navegação da view. */ if (_viewOperacaoCenter?.Mapa?.chbAcompanhar.IsChecked ?? false) { _viewOperacaoCenter?.Mapa?.SetView(obj.Gnss?.Latitude, obj.Gnss?.Longitude); } // ALERTAS _viewOperacaoBottom?._vm?.AtualizarListaAlertasTodosRovers(); if ( rover.RoverId != VariaveisControleOperacao.SelectedRoverId ) { return; } /* * Status/Iniciada chegam por telemetria. * * Se a operação deixar de estar Parametrizando, * a interação é desligada imediatamente. */ AtualizarModoInteracaoMapaOperacao(); AtualizarBarraSuperior(rover); // ESQUERDA var conexao = obj.ModulosSaude?.FirstOrDefault(x => x.modulo == T_Code.Ipb); double saude_rede = conexao?.saude ?? 0; double conexao_latencia = conexao?.detalhes?["avg_rtt"]?.Value() ?? 0.0; double conexao_perda = conexao?.detalhes?["loss_pct"]?.Value() ?? 0.0; double bwTot = conexao?.detalhes?["bw_total_mbps"]?.Value() ?? 0.0; string comunicacao = $"{conexao_latencia:0.00} ms {bwTot:0.00} Mbps"; string comunicacao_extended = $"{conexao_latencia:0.00} ms {conexao_perda:F2}% {bwTot:0.00} Mbps"; _viewOperacaoLeft?._vm?.AtualizarRover( rover?.Descricao ?? "", obj.Controle, obj.Refrigeracao?.Temperatura ?? 0, obj.Bateria?.PercentualBateria ?? 0, obj.Atuador?.PercentualReservatorio ?? 0, $"{saude_rede}%", $"{obj.Gnss?.QualidadeFix ?? TiposCorrecaoGPS.SemCorrecao} {obj.Gnss?.PrecisaoCm:F2} cm", AgroBase.Models.GPSUtils.DistanciaEntrePontos(new AgroBase.Models.GPSModel() { Latitude = obj.Gnss?.Latitude ?? 0, Longitude = obj.Gnss?.Longitude ?? 0 }, Models.Variaveis.GpsService.UltimaLeitura), obj.Operacao?.Iniciada ?? false ); // DIREITA (RESUMO) _viewOperacaoRight?.areaResumo?._vm?.AtualizarResumo(r => { r.Operacao = (obj.Operacao?.Liberada ?? false) ? "🔓 Liberada" : "🔒 Bloqueada"; r.Modo = obj.Operacao == null ? "-" : obj.Operacao.Modo.ToString(); r.Status = obj.Operacao == null ? "-" : $"{obj.Operacao.Status}" + (obj.Operacao.Status == AgroBase.Models.Enums.StatusOperacao.Aguardando ? $" ({obj.Operacao.TempoAguardandoSegs:0}s)" : ""); r.Carro = obj.Trajetoria == null ? "-" : obj.Trajetoria.StatusCarro.ToString(); r.Temperatura = $"{(obj.Refrigeracao?.Temperatura ?? 0):F2} °C"; r.RuaAtual = obj.Trajetoria == null ? "-" : $"{obj.Trajetoria.CorredorAtualDistanciaPercorrida:F2} m / " + $"{obj.Trajetoria.CorredorAtualDistanciaTotal:0.00} m " + $"({obj.Trajetoria.CorredorAtualIdx + 1})"; r.AreaTotal = obj.Trajetoria == null ? "-" : $"{obj.Trajetoria.DistanciaPercorrida:0.00} m / " + $"{obj.Trajetoria.DistanciaTotal:0.00} m"; r.Bateria = obj.Bateria == null ? "-" : $"{obj.Bateria.TensaoInstantanea:0.00} V " + $"({TimeSpan.FromMinutes(obj.Bateria.TempoEstimadoRestanteMinutos):dd\\.hh\\:mm\\:ss} " + $"{obj.Bateria.DistanciaEstimadaRestanteMetros:0.00} m)"; r.Herbicida = obj.Atuador == null ? "-" : $"{obj.Atuador.VolumeReservatorioL:0.00} L " + $"({TimeSpan.FromMinutes(obj.Atuador.TempoEstimadoRestanteMinutos):hh\\:mm\\:ss} " + $"{obj.Atuador.DistanciaEstimadaRestanteMetros:0.00} m)"; r.Infestacao = obj.Atuador == null ? "-" : $"{obj.Atuador.HerbicidaPorAtuacaoMl:0.00} mL/atuação"; r.Conexao = comunicacao == null ? "-" : $"{comunicacao}"; r.Duracao = obj.Operacao == null ? "00:00:00 / 00:00:00" : $"{TimeSpan.FromSeconds(obj.Operacao.TempoDecorridoSegs):hh\\:mm\\:ss} / " + $"{(string.IsNullOrWhiteSpace(obj.Trajetoria?.TempoEstimadoOperacao) ? "00:00:00" : obj.Trajetoria.TempoEstimadoOperacao)}"; // Opcionais para progress bars, caso queira evoluir o resumo r.RuaAtualPercentual = obj.Trajetoria?.PercentualRuaAtual ?? 0; r.AreaTotalPercentual = obj.Trajetoria?.PercentualOperacao ?? 0; r.BateriaPercentual = obj.Bateria?.PercentualBateria ?? 0; r.HerbicidaPercentual = obj.Atuador?.PercentualReservatorio ?? 0; r.InfestacaoPercentual = obj.Atuador?.PercentualErvasTerreno ?? 0; }); // DIAGNOSTICO AtualizarDadosDiagnostico(rover); // CAMERAS var snr = obj?.Cameras?.FirstOrDefault(x => x.dispositivo == AgroBase.Models.Enums.T_Code.Snr); var cam = obj?.Cameras?.FirstOrDefault(x => x.dispositivo == AgroBase.Models.Enums.T_Code.Cam); _viewOperacaoCenter?.Monitoramento?._vm?.AtualizarDadosCameras(snr, cam, obj?.OperadorVisual?.StatusCarro, obj?.Atuador?.PercentualErvasNoRadar); // PULVERIZADOR bool telaPulverizador = _viewOperacaoCenter?._vm?.ConteudoAtual == OperacaoCenterConteudo.Pulverizador; var bomba = obj.Atuador?.Bombas?.FirstOrDefault(x => x.ID == "BMBLN"); var agitador = obj.Atuador?.Bombas?.FirstOrDefault(x => x.ID == "BMBAGT"); var bicosAtivos = obj.Atuador?.Bicos?.Where(x => x.ComandoEstado == true).ToArray(); int quantidadeBicosAtivos = bicosAtivos?.Length ?? 0; int quantidadeTotalBicos = obj.Atuador?.Bicos?.Count ?? 0; double vazaoMediaBicosAtivos = bicosAtivos is { Length: > 0 } ? bicosAtivos.Average(x => x.VazaoInstantaneaMLs) : 0.0; if (telaPulverizador) { var moduloAtu = obj.ModulosSaude?.FirstOrDefault(x => x.modulo == T_Code.Atu); _viewOperacaoCenter?.Pulverizador?._vm?.AtualizarParametrosTela(new Views.Operacao.Monitoramento.PulverizadorSnapshot { StatusGeralPulverizador = (moduloAtu?.status ?? StatusModulo.Desconectado).ToString(), CameraErvasStatus = _viewOperacaoCenter?.Monitoramento?._vm?.CameraErvasStatus ?? "", ModoPulverizacaoTexto = (rover.Controle?.PulverizadorAutomatico ?? false) ? "Automático" : "Manual", StatusBombaTexto = (bomba?.ComandoEstado ?? false) ? "Ligado" : "Desligado", StatusBombaTextoLido = (bomba?.LeituraEstado ?? false) ? "Ligado" : "Desligado", PotenciaBombaValor = $"{(bomba?.ComandoPotencia ?? 0):F2} %", PotenciaBombaValorLido = $"{(bomba?.LeituraPotencia ?? 0):F2} %", ModoAgitadorTexto = (rover.Controle?.AtuAgitadorModo ?? ModoAgitadorCalda.SemAgitacao).ToString(), PotenciaAgitadorValor = $"{agitador?.ComandoPotencia ?? 0:F2} %", PotenciaAgitadorValorLido = $"{agitador?.LeituraPotencia ?? 0:F2} %", NivelTanqueValor = $"{(obj.Atuador?.PercentualReservatorio ?? 0):F2} %", NivelTanqueLitrosValor = $"{(obj.Atuador?.VolumeReservatorioL ?? 0):F2} L", PressaoLinhaValor = $"{(rover.Controle?.AtuPressaoLinha ?? 0):F2} psi", PressaoLinhaPsiValor = $"{(obj.Atuador?.PressaoLinhaPsi ?? 0):F2} psi", VazaoInstantaneaValor = $"{(obj.Atuador?.VazaoInstantaneaMLs ?? 0):F2} mL/s", VazaoMediaValor = $"média {(obj.Atuador?.VazaoMediaMLs ?? 0):F2} mL/s", AutonomiaDistanciaValor = $"{(obj.Atuador?.DistanciaEstimadaRestanteMetros ?? 0):F2} m", AutonomiaTempoValor = FuncoesGlobais.ConverterSegundosParaHHmmss((int)Math.Round((obj.Atuador?.TempoEstimadoRestanteMinutos ?? 0) * 60.0)), VolumeVazadoValor = $"{(obj.Atuador?.VolumeVazaoMl ?? 0):F2} mL", TempoLigadoPulverizacaoValor = FuncoesGlobais.ConverterSegundosParaHHmmss((int)((bomba?.TempoAtuado ?? 0) / 1000.0)), BicosAtivosValor = $"{quantidadeBicosAtivos} / {quantidadeTotalBicos}", InfoComplementarBicosValor = $"vazão média: {vazaoMediaBicosAtivos:F2} mL/s", PercentualErvasRadar = $"radar: {(obj.Atuador?.PercentualErvasNoRadar ?? 0):F2} %", PercentualErvasTerreno = $"terreno: {(obj.Atuador?.PercentualErvasTerreno ?? 0):F2} %" }); } var ATU = telaPulverizador ? _viewOperacaoCenter.Pulverizador.areaPulverizador : _viewOperacaoCenter.Monitoramento.Pulverizador; ATU.ComprimentoBarraCm = AgroBase.Models.VariaveisEquipamento.ComprimentoBarraPulverizadoraCm; ATU.DistanciaEntreBicosCm = AgroBase.Models.VariaveisEquipamento.DistanciaEntreBicosCm; //ATU.AlturaBarra = obj?.Controle?.AlturaBarra ?? AgroBase.Models.VariaveisEquipamento.AlturaBarraPulverizadoraCm; ATU.IsBombaOn = bomba?.LeituraEstado ?? false; ATU.PressaoLinha = obj?.Atuador?.PressaoLinhaPsi ?? 0; ATU.VazaoInstantanea = obj?.Atuador?.VazaoInstantaneaMLs ?? 0; ATU.TempoAtuado = (bomba?.TempoAtuado ?? 0) / 1000.0; double erroLateral = ((obj?.Trajetoria?.DistanciaEsquerda ?? 0) - (obj?.Trajetoria?.DistanciaDireita ?? 0)) / 2.0 * 100.0; ATU.LarguraRuaCm = 100.0; ATU.ErroLateralCm = erroLateral; if (ATU.QtdBicos != obj?.Atuador?.Bicos?.Count) { ATU.QtdBicos = obj?.Atuador?.Bicos?.Count ?? AgroBase.Models.VariaveisEquipamento.QuantidadeBicosPulverizadores; } foreach (var bico in ATU.Bicos) { var _bc = obj?.Atuador?.Bicos?.FirstOrDefault(x => (x.Posicao - 1) == bico.Index); if (_bc == null) continue; bico.OpeningAngle = _bc.AnguloAbertura; bico.AnguloControle = _bc.ComandoAngulo; bico.IsCommandOn = _bc.ComandoEstado; bico.IsSpraying = _bc.LeituraEstado; bico.Vazao = _bc.VazaoInstantaneaMLs; bico.Atuacoes = _bc.QtdAtuacoes; bico.TempoAtuado = _bc.TempoAtuado / 1000.0; } // HEADING var HDG = _viewOperacaoCenter.Monitoramento.Heading; HDG.Heading = obj?.Gnss?.OrientacaoReal ?? double.NaN; HDG.CourseHeading = obj?.Trajetoria?.AnguloCaminho ?? double.NaN; // ATTITUDE var IMU = _viewOperacaoCenter.Monitoramento.Attitude; if (obj?.Imu?.InclinacaoLateral != null) IMU.RollDeg = (double)obj.Imu.InclinacaoLateral; if (obj?.Imu?.InclinacaoFrontal != null) IMU.PitchDeg = (double)obj.Imu.InclinacaoFrontal; IMU.LateralError = erroLateral; } private void AbrirDiagnosticoPeloAlerta(AlertaModel alerta) { if (alerta == null) return; _viewOperacaoRight?._vm?.SelecionarRover(alerta.Rover_ID); _viewOperacaoRight?._vm?.SelecionarSecao(SecaoRightBar.Diagnostico); _viewOperacaoCenter?.Diagnostico?._vm?.AbrirModuloPorAlerta(alerta.Modulo, alerta.Mod_ID); } public void AtualizarDadosDiagnostico(OperacaoParametrosModel? rover) { _viewOperacaoCenter?.Diagnostico?._vm?.AtualizarDados(rover); _viewOperacaoCenter?.Diagnostico?.AtualizarGraficos(); } public void AdicionarAlerta(string roverId, AgroBase.Models.Enums.T_Code modulo, SeveridadeAlerta severidade, string mensagem, string modId = null) { _viewOperacaoBottom?._vm?.AdicionarAlerta(roverId, modulo, severidade, mensagem, modId); } public void RemoverAlerta(string roverId, AgroBase.Models.Enums.T_Code modulo, SeveridadeAlerta? severidade = null, string modId = null) { _viewOperacaoBottom?._vm?.RemoverAlerta(roverId, modulo, severidade, modId); } private void OnRoverSelecionadoChanged(string roverId) { _viewOperacaoRight?._vm?.DestacarRoverNoMapa(roverId); } private void OnRoverSelecionadoChanged(RoverCardModel rover) { bool dados_base = rover == null; if (dados_base) { if (!VariaveisControleOperacao.BaseEmFoco) { _viewOperacaoCenter?.Monitoramento?.PararStreamCameraFrontal(); _viewOperacaoCenter?.Monitoramento?.PararStreamCameraErvas(); VariaveisControleOperacao.EnviarComandoIniciarUDP(VariaveisControleOperacao.RoverEmFoco?.IP, false); } VariaveisControleOperacao.SelectedRoverId = VariaveisControleOperacao.BaseMarkerID; _mapas.DefinirFocoEntidade(VariaveisControleOperacao.BaseMarkerID, _viewOperacaoCenter?.Mapa); _viewOperacaoCenter?._vm?.SelecionarRoverLista(rover); AtualizarBarraSuperior( StatusModulo.Conectado, "BASE", "VISÃO GERAL", "Selecione um equipamento na lista à direita para configurá-lo", "NORMAL", "Aguardando" ); _viewOperacaoLeft?._vm?.DefinirSemRover(); } else { _roverAguardandoParametrosId = null; VariaveisControleOperacao.SelectedRoverId = (rover.NumeroSerie ?? "").ToString(); /* * Normalmente a entidade já foi criada por * SincronizarRovers(). Esta guarda cobre seleção * antes da primeira atualização da lista. */ if (_mapas.ObterEntidade(VariaveisControleOperacao.SelectedRoverId) == null) { _mapas.AtualizarRover(VariaveisControleOperacao.RoverEmFoco, _viewOperacaoCenter?.Mapa); } _mapas.DefinirFocoEntidade(VariaveisControleOperacao.SelectedRoverId, _viewOperacaoCenter?.Mapa); AtualizarDadosTela(VariaveisControleOperacao.RoverEmFoco); _viewOperacaoCenter?.Monitoramento?.IniciarStreamCameraFrontal(); _viewOperacaoCenter?.Monitoramento?.IniciarStreamCameraErvas(); VariaveisControleOperacao.EnviarComandoIniciarUDP(VariaveisControleOperacao.RoverEmFoco?.IP, true); _viewOperacaoCenter?._vm?.SelecionarRoverLista(rover); AtualizarBarraSuperior(VariaveisControleOperacao.RoverEmFoco); VariaveisControleOperacao.RequisitarParametrosOperacao(); } _mapas.PrepararMapa( _viewOperacaoCenter?.Mapa, modoInteracao: ObterModoInteracaoMapaOperacao(), enquadrarDocumento: true ); } private void OnSecaoSelecionadaChanged(SecaoRightBar secao) { switch (secao) { case SecaoRightBar.Parametrizacao: // VariaveisControleOperacao.RequisitarParametrosOperacao(); break; case SecaoRightBar.Pulverizador: if (_viewOperacaoCenter.Monitoramento.videoWeed.imageName != _viewOperacaoCenter.Pulverizador.imgVideo.Name) { _viewOperacaoCenter.Monitoramento.videoWeed.Parar(); _viewOperacaoCenter.Monitoramento.videoWeed = new TcpVideoReceiver( 5002, _viewOperacaoCenter.Pulverizador.imgVideo ); _viewOperacaoCenter.Monitoramento.videoWeed.Iniciar(); } break; default: if (_viewOperacaoCenter.Monitoramento.videoWeed.imageName != _viewOperacaoCenter.Monitoramento.imgVideoWeed.Name) { _viewOperacaoCenter.Monitoramento.videoWeed.Parar(); _viewOperacaoCenter.Monitoramento.videoWeed = new TcpVideoReceiver( 5002, _viewOperacaoCenter.Monitoramento.imgVideoWeed ); _viewOperacaoCenter.Monitoramento.videoWeed.Iniciar(); } break; } _viewOperacaoCenter?._vm?.DefinirSecao(secao); _mapas.PrepararMapa( _viewOperacaoCenter?.Mapa, modoInteracao: ObterModoInteracaoMapaOperacao(), enquadrarDocumento: false ); } private string? _roverAguardandoParametrosId; private void OnLerParametrosSolicitado() { var rover = VariaveisControleOperacao.RoverEmFoco; if (rover == null || string.IsNullOrWhiteSpace(rover.RoverId)) return; _roverAguardandoParametrosId = rover.RoverId; VariaveisControleOperacao.RequisitarParametrosOperacao(); } private void OnSalvarParametrosSolicitado(OperacaoParametrosModel parametros) { var rover = VariaveisControleOperacao.RoverEmFoco; if (rover == null) return; if (parametros.Modo == AgroBase.Models.Enums.ModoOperacao.RetornoBase && !(rover.DadosLeitura?.Operacao?.Iniciada ?? false)) { /* * ===================================================== * RETORNO GUIADO PELO OPERADOR * ===================================================== */ if (_mapas.PossuiPontosRetorno) { double distanciaMetros = _mapas.ObterDistanciaRotaRetornoMetros(); var resposta = System.Windows.MessageBox.Show( $"Deseja iniciar o retorno seguindo " + $"{_mapas.QuantidadePontosRetorno} pontos marcados no mapa, " + $"somando {distanciaMetros:F2} metros?", "Retorno guiado", MessageBoxButton.YesNo, MessageBoxImage.Question ); if (resposta != MessageBoxResult.Yes) return; parametros.PontosRetorno = _mapas.ExportarRotaRetorno(); /* * Manual: * * lat = null * lon = null * pontosRetorno = rota */ VariaveisControleOperacao .EnviarComandoRetornoBase( pontosRetorno: parametros.PontosRetorno ); rover.PontosRetorno = parametros.PontosRetorno; Models.Variaveis.MostrarLog( "[RETORNO] " + $"Modo=GUIADO | " + $"Pontos={_mapas.QuantidadePontosRetorno} | " + $"Distancia={distanciaMetros:F2} m" ); } /* * ===================================================== * RETORNO AUTOMÁTICO * ===================================================== */ else { var baseFix = Models.Variaveis .GpsService? .BaseFix; if ( baseFix == null || !baseFix.PosicaoBaseFixada ) { System.Windows.MessageBox.Show( "A posição da base não está fixada. " + "Não é possível calcular o retorno automático.", "Base não fixada", MessageBoxButton.OK, MessageBoxImage.Warning ); return; } double latitudeBase = baseFix.LatitudeFix; double longitudeBase = baseFix.LongitudeFix; bool coordenadaValida = double.IsFinite(latitudeBase) && double.IsFinite(longitudeBase) && latitudeBase >= -90.0 && latitudeBase <= 90.0 && longitudeBase >= -180.0 && longitudeBase <= 180.0; if (!coordenadaValida) { System.Windows.MessageBox.Show( "A coordenada fixada da base é inválida.", "Base inválida", MessageBoxButton.OK, MessageBoxImage.Warning ); return; } var resposta = System.Windows.MessageBox.Show( "Nenhum ponto manual foi marcado.\n\n" + "Deseja solicitar que o rover calcule " + "automaticamente uma rota segura de retorno à base?", "Retorno automático", MessageBoxButton.YesNo, MessageBoxImage.Question ); if (resposta != MessageBoxResult.Yes) return; parametros.PontosRetorno = null; /* * Automático: * * lat = base * lon = base * pontosRetorno = null */ VariaveisControleOperacao .EnviarComandoRetornoBase( lat: latitudeBase, lon: longitudeBase, pontosRetorno: null ); rover.PontosRetorno = null; Models.Variaveis.MostrarLog( "[RETORNO] " + $"Modo=AUTOMATICO | " + $"Base={latitudeBase:F8}," + $"{longitudeBase:F8}" ); } Task.Run( async () => { await Task.Delay( 500 ); _viewOperacaoRight? ._vm? .SelecionarSecao( SecaoRightBar.Resumo ); } ); return; } var tipoMapa = _mapas.TipoMapa; MapaFeatureCollectionModel? dadosMapa = _mapas.CriarMapaOperacional(); var ruasPercorrer = _mapas.ObterRuasPercorrer(); if (rover?.DadosLeitura?.Operacao?.Modo != parametros.Modo && (rover?.DadosLeitura?.Operacao?.Iniciada ?? false)) { System.Windows.MessageBox.Show($"Já existe uma operação {rover?.DadosLeitura?.Operacao?.Modo ?? AgroBase.Models.Enums.ModoOperacao.NaoDefinido} em andamento. Finalize a operação atual para salvar os novos parametros.", "Mudança de Operação", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (!(rover?.DadosLeitura?.Operacao?.Iniciada ?? false) && parametros.Modo == AgroBase.Models.Enums.ModoOperacao.MapaGPS && ((dadosMapa?.features?.Count ?? 0) == 0 || !ruasPercorrer.Any())) { System.Windows.MessageBox.Show("Selecione as ruas no mapa onde o equipamento irá operar", "Ruas não selecionadas", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (rover == null) return; if (parametros?.Controle?.MovimentoAutomatico ?? false) { var _mov = parametros?.ModulosMandatorios?.FirstOrDefault(x => x.Dispositivo == T_Code.Mov); if (_mov == null) { _mov = new AgroBase.Models.OperacaoModulosMandatoriosModel() { Dispositivo = T_Code.Mov, Utilizar = true, Mandatorio = true, ComponentesEmUso = new Dictionary() { { "ET", (true, true) }, { "DT", (true, true) }, { "EF", (true, true) }, { "DF", (true, true) }, } }; } else { foreach (var key in _mov.ComponentesEmUso?.Keys.ToList() ?? new List()) { _mov.ComponentesEmUso[key] = (true, true); } _mov.Utilizar = true; _mov.Mandatorio = true; } } if (parametros?.Controle?.DirecionalAutomatico ?? false) { var _dir = parametros?.ModulosMandatorios?.FirstOrDefault(x => x.Dispositivo == T_Code.Dir); if (_dir == null) { _dir = new AgroBase.Models.OperacaoModulosMandatoriosModel() { Dispositivo = T_Code.Dir, Utilizar = true, Mandatorio = true, ComponentesEmUso = new Dictionary() { { "ET", (true, true) }, { "DT", (true, true) }, { "EF", (true, true) }, { "DF", (true, true) }, } }; } else { foreach (var key in _dir.ComponentesEmUso?.Keys.ToList() ?? new List()) { _dir.ComponentesEmUso[key] = (true, true); } _dir.Utilizar = true; _dir.Mandatorio = true; } } if (parametros?.Controle?.PulverizadorAutomatico ?? false) { var _atu = parametros?.ModulosMandatorios?.FirstOrDefault(x => x.Dispositivo == T_Code.Atu); if (_atu == null) { _atu = new AgroBase.Models.OperacaoModulosMandatoriosModel() { Dispositivo = T_Code.Atu, Utilizar = true, Mandatorio = true, ComponentesEmUso = new Dictionary() { { "MASRS", (true, true) }, { "FLXLN", (true, true) }, { "PRSLN", (true, true) }, { "BOMBA", (true, true) }, { "B01", (true, true) }, { "B02", (true, true) }, { "B03", (true, true) }, { "B04", (true, true) }, { "B05", (true, true) }, { "B06", (true, true) }, { "B07", (true, true) }, } }; } else { foreach (var key in _atu.ComponentesEmUso?.Keys.ToList() ?? new List()) { _atu.ComponentesEmUso[key] = (true, true); } _atu.Utilizar = true; _atu.Mandatorio = true; } } if (parametros?.Controle?.ImuParadaPorInclinacao ?? false) { var _imu = parametros.ModulosMandatorios?.FirstOrDefault(x => x.Dispositivo == T_Code.Imu); if (_imu == null) { _imu = new AgroBase.Models.OperacaoModulosMandatoriosModel() { Dispositivo = T_Code.Imu, Utilizar = true, Mandatorio = true, }; parametros?.ModulosMandatorios?.Add(_imu); } else { _imu.Utilizar = true; _imu.Mandatorio = true; } } if (parametros?.Controle?.OakParadaPorObstaculo ?? false) { var _snr = parametros.ModulosMandatorios?.FirstOrDefault(x => x.Dispositivo == T_Code.Snr); if (_snr == null) { _snr = new AgroBase.Models.OperacaoModulosMandatoriosModel() { Dispositivo = T_Code.Snr, Utilizar = true, Mandatorio = true, }; parametros?.ModulosMandatorios?.Add(_snr); } else { _snr.Utilizar = true; _snr.Mandatorio = true; } } var novosParametros = new OperacaoParametrosModel() { Modo = parametros.Modo, Descricao = parametros.Descricao, QtdCamerasSolo = parametros.QtdCamerasSolo, QtdBicos = parametros.QtdBicos, CapacidadeReservatorio = parametros.CapacidadeReservatorio, Controle = parametros.Controle, ModulosMandatorios = parametros?.ModulosMandatorios, ParametrosMandatorios = parametros?.ParametrosMandatorios, TipoMapa = tipoMapa, RuasPercorrer = ruasPercorrer, Mapa = dadosMapa }; VariaveisControleOperacao.EnviarParametrosOperacao(novosParametros); rover.Modo = novosParametros.Modo; rover.Descricao = novosParametros.Descricao; rover.QtdCamerasSolo = novosParametros.QtdCamerasSolo; rover.QtdBicos = novosParametros.QtdBicos; rover.CapacidadeReservatorio = novosParametros.CapacidadeReservatorio; rover.Controle = novosParametros.Controle; rover.ModulosMandatorios = novosParametros.ModulosMandatorios; rover.ParametrosMandatorios = novosParametros.ParametrosMandatorios; rover.PontosRetorno = novosParametros.PontosRetorno; rover.TipoMapa = novosParametros.TipoMapa; rover.RuasPercorrer = novosParametros.RuasPercorrer; rover.Mapa = novosParametros.Mapa; AtualizarDadosDiagnostico(rover); Task.Run(async () => { await Task.Delay(500); _viewOperacaoRight?._vm?.SelecionarSecao(SecaoRightBar.Diagnostico); }); } private bool ValidarGnssAntesDeIniciarOperacao() { /* * Simulador não depende da base física. */ if ( AppShell.Mock || AppShell.AddRover ) { return true; } /* * Operação manual não exige estação base. */ if ( ModoOperacaoAtual != ModoOperacao.Automatico ) { return true; } var gps = Models.Variaveis.GpsService; bool conectado = gps?.IsConnected == true; bool papelBase = gps?.PapelEstacaoAtual == GnssPapelEstacao.BaseFixa; bool baseRealmenteFixada = gps?.BaseFix? .PosicaoBaseFixada == true; if ( conectado && papelBase && baseRealmenteFixada ) { return true; } string estado = !conectado ? "GNSS desconectado" : gps?.PapelEstacaoAtual switch { GnssPapelEstacao.Movel => "Levantamento móvel", GnssPapelEstacao.BaseFixa => "Base fixa sem confirmação", _ => "Modo não definido" }; var resposta = System.Windows.MessageBox.Show( "A operação automática não pode ser iniciada " + "porque a estação GNSS ainda não está pronta como base fixa.\n\n" + $"Estado atual: {estado}\n\n" + "Configure o GNSS como Base Fixa antes de iniciar a operação.\n\n" + "Deseja ir agora para a configuração do GNSS?", "Base GNSS necessária", MessageBoxButton.YesNo, MessageBoxImage.Warning ); if ( resposta == MessageBoxResult.Yes ) { IrParaConfiguracaoGnss(); } return false; } public bool AutorizarInicioOperacao() { return ValidarGnssAntesDeIniciarOperacao(); } public void IrParaConfiguracaoGnss() { /* * A View e seus ViewModels permanecem vivos. * * Estamos apenas trocando a etapa visual, * então a parametrização do rover não é * recriada nem descartada. */ CurrentStep = DockStep.PreparacaoMapa; } #endregion } public enum ModoOperacao { Automatico = 1, Manual = 2 } public enum DockStep { ConfigConexao = 1, PreparacaoMapa = 2, ParametrizacaoRover = 3 } }