agrobot_base/AgroBase/OperationControl/ViewModels/Windows/DockWindowViewModel.cs

3675 lines
124 KiB
C#

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.Creation;
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.CriarMapaSolicitado += OnCriarMapaSolicitado;
_viewPreparacaoMapaBottom._vm.EditarMapaSolicitado += OnEditarMapaPreparacaoSolicitado;
_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;
/*
* O mesmo cockpit também existe no mapa da preparação.
* Assim o botão "Salvar versão editada" funciona antes
* mesmo de existir Rover selecionado/conectado.
*/
_viewPreparacaoMapa
.MapaPreparacao
.EditorOverlay
.SalvarMapaEditadoSolicitado +=
OnSalvarMapaEditadoSolicitado;
var criacaoOverlay =
_viewPreparacaoMapa
.MapaPreparacao
.CriacaoOverlay;
criacaoOverlay.NomeMapaAlterado +=
nome => _mapas.DefinirNomeCriacaoMapa(nome);
criacaoOverlay.ModoCapturaAlterado +=
modo => _mapas.DefinirModoCapturaCriacao(modo);
criacaoOverlay.RegistrarPontoSolicitado +=
() => _mapas.RegistrarPontoCriacaoManual();
criacaoOverlay.RemoverUltimoPontoSolicitado +=
() => _mapas.RemoverUltimoPontoCriacao();
criacaoOverlay.IniciarCapturaAutomaticaSolicitado +=
() => _mapas.IniciarGravacaoAutomaticaCriacao();
criacaoOverlay.FinalizarCapturaAutomaticaSolicitado +=
() => _mapas.FinalizarGravacaoAutomaticaCriacao();
criacaoOverlay.NovaRuaSolicitada +=
() => _mapas.CriarNovaRuaMapa();
criacaoOverlay.RuaSelecionada +=
numeroRua => _mapas.SelecionarRuaCriacao(numeroRua);
criacaoOverlay.ExcluirRuaSolicitada +=
OnExcluirRuaCriacaoSolicitado;
criacaoOverlay.LimparSolicitado +=
OnLimparCriacaoSolicitado;
criacaoOverlay.SalvarSolicitado +=
OnSalvarMapaCriadoSolicitado;
criacaoOverlay.CancelarSolicitado +=
OnCancelarCriacaoMapaSolicitado;
_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;
_mapas.EstadoCriacaoAlterado += OnEstadoCriacaoAlterado;
// 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 &&
!(
CurrentStep == DockStep.PreparacaoMapa &&
(
_mapas.EdicaoAtiva ||
_mapas.CriacaoMapaAtiva
)
);
public bool CanGoNext
{
get
{
return CurrentStep switch
{
DockStep.ConfigConexao =>
PodeAvancarConfigConexao(),
DockStep.PreparacaoMapa =>
MapaCarregado &&
!_mapas.EdicaoAtiva &&
!_mapas.CriacaoMapaAtiva,
_ =>
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
);
_viewPreparacaoMapaBottom?
._vm?
.DefinirEdicaoMapaAtiva(
_mapas.EdicaoAtiva
);
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<MotivoTopBarModel>();
// ============================================================
// Helpers
// ============================================================
string NormalizarTexto(string texto)
{
if (string.IsNullOrWhiteSpace(texto))
return null;
var partes = texto
.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x?.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct()
.ToList();
return partes.Count > 0
? string.Join(" • ", partes)
: null;
}
bool EhModuloMandatorio(T_Code modulo)
{
// A telemetria recebida do rover é a fonte principal.
// Só cai para a configuração local caso o snapshot não exista.
var modulosMandatorios =
obj.Operacao?.ModulosMandatorios
?? rover.ModulosMandatorios;
return modulosMandatorios?.Any(x =>
(x?.Dispositivo ?? T_Code.Vzo) == modulo &&
(x?.Utilizar ?? false) &&
(x?.Mandatorio ?? false)
) ?? false;
}
string MotivoOperacaoAtual()
{
// 1) Motivo autoritativo de NÃO liberação.
var motivo = NormalizarTexto(obj.Operacao?.ImpedimentoErro);
if (!string.IsNullOrWhiteSpace(motivo))
return motivo;
// 2) Motivo do estado/status atual.
motivo = NormalizarTexto(obj.Operacao?.MotivoStatusOperacao);
if (!string.IsNullOrWhiteSpace(motivo))
return motivo;
// 3) Último motivo de parada apenas como fallback.
motivo = NormalizarTexto(obj.Operacao?.UltimoMotivoParada);
if (!string.IsNullOrWhiteSpace(motivo))
return motivo;
return "A operação não foi liberada pelo rover.";
}
void AdicionarEventoTrajetoria(
string descricao,
int prioridade = 700,
StatusModulo status = StatusModulo.Alerta)
{
descricao = NormalizarTexto(descricao);
if (string.IsNullOrWhiteSpace(descricao))
return;
eventos.Add(new MotivoTopBarModel
{
Prioridade = prioridade,
Fonte = "Trajetoria",
Titulo = "TRAJETÓRIA BLOQUEADA",
Descricao = descricao,
StatusVisual = status,
Modulo = T_Code.Trj
});
}
// ============================================================
// 0) Comunicação
// ============================================================
double tempoSemResposta =
(DateTime.Now - rover.UltimoContato).TotalSeconds;
bool semComunicacao =
!AppShell.Mock &&
tempoSemResposta >= VariaveisControleOperacao.TempoRoverVivo;
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
});
}
// ============================================================
// 1) NPC / núcleo central
// ============================================================
bool todosModulosDesconectados =
obj.ModulosSaude?.Any() == true &&
obj.ModulosSaude.All(x =>
(x?.status ?? StatusModulo.Desconectado) ==
StatusModulo.Desconectado);
if (todosModulosDesconectados)
{
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
});
}
// ============================================================
// 2) Emergência
// ============================================================
if (obj.Operacao?.Emergencia == true)
{
var motivo =
NormalizarTexto(obj.Operacao.MotivoStatusOperacao)
?? "Parada de emergência solicitada.";
eventos.Add(new MotivoTopBarModel
{
Prioridade = 1000,
Fonte = "Emergencia",
Titulo = "OPERAÇÃO BLOQUEADA POR EMERGÊNCIA",
Descricao = motivo,
StatusVisual = StatusModulo.Falha
});
}
// ============================================================
// 3) Pausa
// ============================================================
if (obj.Operacao?.Pausa == true)
{
var motivo =
NormalizarTexto(obj.Operacao.MotivoStatusOperacao)
?? "Pausa operacional solicitada.";
eventos.Add(new MotivoTopBarModel
{
Prioridade = 900,
Fonte = "Pausa",
Titulo = "OPERAÇÃO PAUSADA",
Descricao = motivo,
StatusVisual = StatusModulo.Alerta
});
}
// ============================================================
// 4) Operação não liberada
//
// Essa é a informação autoritativa vinda do rover.
// Exemplo:
// Sen: Desconectado
// Gps: Falha
// Parâmetro obrigatório ausente
// ============================================================
//if (
// obj.Operacao != null &&
// !obj.Operacao.Liberada &&
// obj.Operacao.Pausa != true &&
// obj.Operacao.Emergencia != true
//)
//{
// eventos.Add(new MotivoTopBarModel
// {
// Prioridade = 850,
// Fonte = "Operacao",
// Titulo = "OPERAÇÃO BLOQUEADA",
// Descricao = MotivoOperacaoAtual(),
// StatusVisual = StatusModulo.Falha
// });
//}
// ============================================================
// 5) Controle
// ============================================================
var motivosControle =
obj.Controle?.Motivos?
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(NormalizarTexto)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct()
.ToList()
?? new List<string>();
foreach (var motivo in motivosControle)
{
eventos.Add(new MotivoTopBarModel
{
Prioridade = 800,
Fonte = "Controle",
Titulo = "CONTROLE SEM LIBERAÇÃO",
Descricao = motivo,
StatusVisual = StatusModulo.Alerta
});
}
// ============================================================
// 6) Trajetória / autonomia
// ============================================================
var motivosTrajetoria =
obj.Trajetoria?.AutonomiaCorredor?.Motivos?
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(NormalizarTexto)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct()
.ToList()
?? new List<string>();
foreach (var motivo in motivosTrajetoria)
{
AdicionarEventoTrajetoria(motivo);
}
var impedimentoOperacao = obj.Operacao?.ImpedimentoErro ?? "";
var linhasImpedimento =
impedimentoOperacao
.Split(
new[] { "\r\n", "\n", "\r" },
StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct()
.ToList();
foreach (var linha in linhasImpedimento)
{
bool relacionadoTrajetoria =
linha.Contains(
"Mapa/MPC",
StringComparison.OrdinalIgnoreCase)
||
linha.Contains(
"trajetória",
StringComparison.OrdinalIgnoreCase)
||
linha.Contains(
"trajetoria",
StringComparison.OrdinalIgnoreCase);
if (relacionadoTrajetoria)
{
AdicionarEventoTrajetoria(
linha,
prioridade: 710);
}
}
// ============================================================
// 7) Estado operacional
//
// Aqui pegamos estados legítimos em que a operação está
// liberada, porém ainda não está executando.
//
// Exemplos:
// Aguardando retomada segura
// Parametrizando
// Finalizando
// ============================================================
if (
obj.Operacao != null &&
obj.Operacao.Liberada &&
obj.Operacao.Pausa != true &&
obj.Operacao.Emergencia != true &&
obj.Operacao.Status != StatusOperacao.EmAndamento &&
!string.IsNullOrWhiteSpace(obj.Operacao.MotivoStatusOperacao)
)
{
var motivo =
NormalizarTexto(obj.Operacao.MotivoStatusOperacao);
eventos.Add(new MotivoTopBarModel
{
Prioridade = 650,
Fonte = "StatusOperacao",
Titulo =
$"OPERAÇÃO {obj.Operacao.Status.ToString().ToUpper()}",
Descricao = motivo,
StatusVisual =
obj.Operacao.Status == StatusOperacao.Erro
? StatusModulo.Falha
: StatusModulo.Alerta
});
}
// ============================================================
// 8) Módulos críticos
// ============================================================
var modulosCriticos =
obj.ModulosSaude?
.Where(mod =>
{
if (mod == null)
return false;
var status =
mod?.status ?? StatusModulo.Desconectado;
bool temCondicaoSevera =
mod?.condicoes_operacionais?.Any(
cond => cond?.severidade > 90
) ?? false;
bool mandatorio =
EhModuloMandatorio(mod?.modulo ?? T_Code.Vzo);
bool naoOperante =
!new[]
{
StatusModulo.Operante,
StatusModulo.Alerta
}.Contains(status);
bool emAlerta =
status == StatusModulo.Alerta;
return
temCondicaoSevera ||
(mandatorio && naoOperante) ||
(mandatorio && emAlerta);
})
.ToList()
?? new List<AgroBase.Models.Operadores.ManagerWorkerMessageResponseModulosPendentesModel>();
foreach (var modulo in modulosCriticos)
{
var codigoModulo =
modulo?.modulo ?? T_Code.Vzo;
var nomeModulo =
codigoModulo.ToString();
var status =
modulo?.status ?? StatusModulo.Desconectado;
bool mandatorio =
EhModuloMandatorio(codigoModulo);
bool temCondicaoSevera =
modulo?.condicoes_operacionais?.Any(
cond => cond?.severidade > 90
) ?? false;
bool moduloMandatorioNaoOperante =
mandatorio &&
status != StatusModulo.Operante &&
status != StatusModulo.Alerta;
bool moduloMandatorioAlerta =
mandatorio &&
status == StatusModulo.Alerta;
// --------------------------------------------------------
// 8.1) Mandatório não operacional
// --------------------------------------------------------
if (moduloMandatorioNaoOperante)
{
List<string> motivos =
modulo?.motivos?
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(NormalizarTexto)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct()
.ToList()
?? new List<string>();
string motivoTexto =
motivos.Count > 0
? string.Join("; ", motivos)
: "Sem motivo detalhado informado.";
eventos.Add(new MotivoTopBarModel
{
Prioridade = 620,
Fonte = "ModuloCritico",
Titulo = "MÓDULO MANDATÓRIO NÃO OPERACIONAL",
Descricao =
$"{nomeModulo}: módulo mandatório em estado " +
$"'{status}': {motivoTexto}",
StatusVisual = StatusModulo.Falha,
Modulo = codigoModulo
});
}
// --------------------------------------------------------
// 8.2) Condições operacionais severas
// --------------------------------------------------------
if (temCondicaoSevera)
{
var descricoesCriticas =
modulo?.condicoes_operacionais?
.Where(cond => cond?.severidade > 90)
.Select(cond =>
NormalizarTexto(cond?.descricao))
.Where(desc =>
!string.IsNullOrWhiteSpace(desc))
.Distinct()
.ToList()
?? new List<string>();
foreach (var descricao in descricoesCriticas)
{
eventos.Add(new MotivoTopBarModel
{
Prioridade = 610,
Fonte = "ModuloCritico",
Titulo = "CONDIÇÕES OPERACIONAIS CRÍTICAS",
Descricao =
$"{nomeModulo}: {descricao}",
StatusVisual = StatusModulo.Alerta,
Modulo = codigoModulo
});
}
}
// --------------------------------------------------------
// 8.3) Mandatório em alerta
// --------------------------------------------------------
if (moduloMandatorioAlerta)
{
List<string> motivos =
modulo?.motivos?
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(NormalizarTexto)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct()
.ToList()
?? new List<string>();
string motivoTexto =
motivos.Count > 0
? string.Join("; ", motivos)
: "Sem motivo detalhado informado.";
eventos.Add(new MotivoTopBarModel
{
Prioridade = 600,
Fonte = "ModuloCritico",
Titulo = "MÓDULO MANDATÓRIO EM ALERTA",
Descricao =
$"{nomeModulo}: módulo mandatório em estado " +
$"'{status}': {motivoTexto}",
StatusVisual = StatusModulo.Alerta,
Modulo = codigoModulo
});
}
}
// ============================================================
// 9) Ordenação dos módulos
// ============================================================
var ordemModulos = new Dictionary<T_Code, int>
{
{ 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 }
};
// ============================================================
// 10) Limpeza / ordenação dos eventos
// ============================================================
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.TryGetValue(
x.Modulo.Value,
out var ordem)
? ordem
: int.MaxValue)
.ThenBy(x => x.Fonte)
.ToList();
// ============================================================
// 11) Estado normal
// ============================================================
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
});
}
// ============================================================
// 12) Montagem visual
// ============================================================
var principal =
eventosOrdenados.First();
StatusModulo statusVisual =
principal.StatusVisual;
string linha1 =
principal.Titulo;
string linha2;
// Se perdeu comunicação, não exibimos diagnóstico antigo
// recebido antes da queda junto com a perda de comunicação.
if (principal.Fonte == "Comunicacao")
{
linha2 = principal.Descricao;
}
else
{
var descricoes =
eventosOrdenados
.Select(x => x.Descricao?.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct()
.Take(3)
.ToList();
linha2 =
string.Join(" • ", descricoes);
}
string badge =
principal.Fonte switch
{
"Comunicacao" => "DESCONECTADO",
"NCP" => "DESCONECTADO",
"Emergencia" => "EMERGÊNCIA",
"Pausa" => "PAUSADO",
"Operacao" => "BLOQUEADO",
"Controle" => "CONTROLE",
"Trajetoria" => "TRAJETÓRIA",
"StatusOperacao"
=> obj.Operacao?.Status
.ToString()
.ToUpper() ?? "OPERAÇÃO",
"ModuloCritico" => "ALERTA",
"Normal" => statusVisual
.ToString()
.ToUpper(),
_ => statusVisual
.ToString()
.ToUpper()
};
string resumo =
principal.Fonte switch
{
"Comunicacao" =>
"Sem telemetria",
"NCP" =>
"Núcleo central desconectado",
"Emergencia" =>
"Parada imediata",
"Pausa" =>
"Aguardando retomada",
"Operacao" =>
"Aguardando liberação",
"Controle" =>
"Controle bloqueado",
"Trajetoria" =>
"Trajetória bloqueada",
"StatusOperacao" =>
NormalizarTexto(
obj.Operacao?.MotivoStatusOperacao)
?? obj.Operacao?.Status.ToString()
?? "Estado operacional",
"ModuloCritico" =>
"Atenção operacional",
"Normal" =>
obj.Operacao?.Status.ToString()
?? StatusOperacao.NaoIniciado.ToString(),
_ =>
obj.Operacao?.Status.ToString()
?? StatusOperacao.NaoIniciado.ToString()
};
// ============================================================
// 13) Histórico
// ============================================================
_viewOperacaoCenter?
.Historico?
._vm?
.CompararERegistrarMudancasEventos(
rover.RoverId,
eventosOrdenados);
// ============================================================
// 14) Segurança visual
// ============================================================
if (string.IsNullOrWhiteSpace(linha1))
linha1 = "ESTADO DO ROVER";
if (string.IsNullOrWhiteSpace(linha2))
linha2 = "Sem detalhes adicionais.";
if (string.IsNullOrWhiteSpace(badge))
badge = "STATUS";
if (string.IsNullOrWhiteSpace(resumo))
resumo =
obj.Operacao?.Status.ToString()
?? StatusOperacao.NaoIniciado.ToString();
// ============================================================
// 15) Atualização da 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
);
}
}
}
/*
* Snapshot GNSS compartilhado pelas ferramentas de mapa
* que fazem levantamento em campo:
*
* - borda GNSS do editor;
* - criação de mapa por ruas.
*
* O Controller faz Clone() internamente e aplica a mesma
* validação de freshness já usada pela borda.
*/
_mapas.AtualizarGnssLocal(
dados
);
_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();
/// <summary>
/// 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.
/// </summary>
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<string> 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()
{
MapaCarregado = _mapas.PossuiMapa;
_viewPreparacaoMapaBottom?
._vm?
.DefinirEdicaoMapaAtiva(
_mapas.EdicaoAtiva
);
OnPropertyChanged(
nameof(CanGoBack)
);
OnPropertyChanged(
nameof(CanGoNext)
);
CommandManager
.InvalidateRequerySuggested();
AtualizarEstadoMapaParametrizacao();
}
private void OnEstadoCriacaoAlterado()
{
bool ativa = _mapas.CriacaoMapaAtiva;
_viewPreparacaoMapaBottom?
._vm?
.DefinirCriacaoMapaAtiva(ativa);
_viewPreparacaoMapa?
.MapaPreparacao?
.CriacaoOverlay?
.AtualizarEstado(
_mapas.ObterResumoCriacaoMapa()
);
OnPropertyChanged(nameof(CanGoBack));
OnPropertyChanged(nameof(CanGoNext));
CommandManager.InvalidateRequerySuggested();
}
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 OnCriarMapaSolicitado()
{
try
{
if (CurrentStep != DockStep.PreparacaoMapa)
return;
if (_mapas.EdicaoAtiva || _mapas.CriacaoMapaAtiva)
return;
var dlgTipo = new OperationControl.Windows.SelecaoTipoMapa
{
Owner = Models.Variaveis.Dock
};
if (dlgTipo.ShowDialog() != true || dlgTipo.Resultado == null)
return;
MapViewControl? mapa = _viewPreparacaoMapa?.MapaPreparacao;
if (mapa == null)
return;
_mapas.IniciarCriacaoMapa(
mapa,
nome: "NovoMapa",
tipo: dlgTipo.Resultado.Value,
distanciaMinimaAutomaticaMetros: 0.50
);
_viewPreparacaoMapaBottom?._vm?.DefinirCriacaoMapaAtiva(true);
if (_viewPreparacaoMapaBottom?._vm != null)
{
_viewPreparacaoMapaBottom._vm.StatusTexto =
"Criação de mapa ativa. Registre os pontos das ruas pelo GNSS.";
}
Models.Variaveis.MostrarLog(
"[MAPA][CRIACAO][UI] Cockpit aberto na preparação."
);
}
catch (Exception ex)
{
Models.Variaveis.MostrarLog(
"[MAPA][CRIACAO][UI][ERRO] " + ex
);
MessageBox.Show(
"Não foi possível iniciar a criação do mapa." +
Environment.NewLine + Environment.NewLine + ex.Message,
"Criar mapa",
MessageBoxButton.OK,
MessageBoxImage.Error
);
}
}
private void OnExcluirRuaCriacaoSolicitado(int numeroRua)
{
var resposta = MessageBox.Show(
$"Excluir a Rua {numeroRua} do levantamento?",
"Excluir rua",
MessageBoxButton.YesNo,
MessageBoxImage.Question
);
if (resposta == MessageBoxResult.Yes)
_mapas.ExcluirRuaCriacao(numeroRua);
}
private void OnLimparCriacaoSolicitado()
{
var resposta = MessageBox.Show(
"Limpar todos os pontos e ruas levantados?",
"Limpar levantamento",
MessageBoxButton.YesNo,
MessageBoxImage.Question
);
if (resposta == MessageBoxResult.Yes)
_mapas.LimparLevantamentoCriacao();
}
private void OnCancelarCriacaoMapaSolicitado()
{
if (!_mapas.CriacaoMapaAtiva)
return;
MapaResumoCriacao resumo = _mapas.ObterResumoCriacaoMapa();
if (resumo.PontosTotal > 0)
{
var resposta = MessageBox.Show(
"Cancelar a criação do mapa?" +
Environment.NewLine + Environment.NewLine +
"Os pontos ainda não salvos serão descartados.",
"Cancelar criação",
MessageBoxButton.YesNo,
MessageBoxImage.Question
);
if (resposta != MessageBoxResult.Yes)
return;
}
_mapas.CancelarCriacaoMapa();
if (_viewPreparacaoMapaBottom?._vm != null)
{
_viewPreparacaoMapaBottom._vm.StatusTexto =
_mapas.PossuiMapa
? "Criação cancelada. O mapa anterior foi restaurado."
: "Criação cancelada.";
}
}
private void OnSalvarMapaCriadoSolicitado()
{
try
{
if (!_mapas.CriacaoMapaAtiva)
return;
MapaResumoCriacao resumo = _mapas.ObterResumoCriacaoMapa();
if (!resumo.PodeSalvar)
{
MessageBox.Show(
"Para salvar o mapa, informe um nome e garanta que " +
"todas as ruas possuam pelo menos 2 pontos.",
"Mapa incompleto",
MessageBoxButton.OK,
MessageBoxImage.Warning
);
return;
}
var dialog = new Microsoft.Win32.SaveFileDialog
{
Title = "Salvar novo mapa operacional",
Filter = "GeoJSON (*.geojson)|*.geojson",
DefaultExt = ".geojson",
AddExtension = true,
OverwritePrompt = true,
FileName = _mapas.ObterNomeArquivoSugeridoMapaCriado()
};
if (dialog.ShowDialog() != true)
return;
MapaDocumento documento =
_mapas.SalvarECarregarMapaCriado(dialog.FileName);
MapaCarregado = true;
_mapas.PrepararMapa(
_viewPreparacaoMapa?.MapaPreparacao,
modoInteracao: MapaModoInteracao.Visualizacao,
enquadrarDocumento: true
);
if (_viewPreparacaoMapaBottom?._vm != null)
{
_viewPreparacaoMapaBottom._vm.StatusTexto =
$"Mapa criado e carregado: {System.IO.Path.GetFileName(dialog.FileName)}";
}
MessageBox.Show(
"Mapa criado com sucesso." + Environment.NewLine + Environment.NewLine +
$"Ruas: {documento.QuantidadeRuas}" + Environment.NewLine +
$"Comprimento total: {documento.ComprimentoTotalMetros:F1} m" +
Environment.NewLine + Environment.NewLine +
dialog.FileName + Environment.NewLine + Environment.NewLine +
"O arquivo já está carregado como mapa oficial.",
"Mapa criado",
MessageBoxButton.OK,
MessageBoxImage.Information
);
}
catch (Exception ex)
{
Models.Variaveis.MostrarLog(
"[MAPA][CRIACAO][SALVAR][ERRO] " + ex
);
MessageBox.Show(
"Não foi possível salvar/carregar o novo mapa." +
Environment.NewLine + Environment.NewLine + ex.Message,
"Erro ao criar mapa",
MessageBoxButton.OK,
MessageBoxImage.Error
);
}
}
private void OnEditarMapaPreparacaoSolicitado()
{
try
{
if (
CurrentStep !=
DockStep.PreparacaoMapa
)
{
return;
}
if (!_mapas.PossuiMapa)
{
MessageBox.Show(
"Carregue um mapa antes de abrir o editor.",
"Editar mapa",
MessageBoxButton.OK,
MessageBoxImage.Information
);
return;
}
if (_mapas.CriacaoMapaAtiva)
{
MessageBox.Show(
"Finalize ou cancele a criação do mapa antes de abrir o editor.",
"Criação de mapa ativa",
MessageBoxButton.OK,
MessageBoxImage.Information
);
return;
}
if (_mapas.EdicaoAtiva)
{
/*
* Proteção contra uma segunda entrada enquanto
* a sessão global de edição já está aberta.
*/
_viewPreparacaoMapaBottom?
._vm?
.DefinirEdicaoMapaAtiva(
true
);
return;
}
MapViewControl? mapa =
_viewPreparacaoMapa?
.MapaPreparacao;
if (mapa == null)
return;
/*
* Intencionalmente NÃO existe qualquer requisito
* de Rover aqui.
*
* A edição atua somente no Workspace/Documento
* e no MapViewControl da tela de preparação.
*/
_mapas.IniciarEdicao(
mapa
);
_viewPreparacaoMapaBottom?
._vm?
.DefinirEdicaoMapaAtiva(
true
);
Models.Variaveis.MostrarLog(
"[MAPA][EDICAO][PREPARACAO] " +
"Editor aberto sem dependência de Rover."
);
}
catch (Exception ex)
{
Models.Variaveis.MostrarLog(
"[MAPA][EDICAO][PREPARACAO][ERRO] " +
ex
);
MessageBox.Show(
"Não foi possível iniciar a edição do mapa." +
Environment.NewLine +
Environment.NewLine +
ex.Message,
"Editar mapa",
MessageBoxButton.OK,
MessageBoxImage.Error
);
}
}
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
{
if (_mapas.EdicaoAtiva)
{
MessageBox.Show(
"Finalize ou cancele a edição atual antes de trocar o mapa.",
"Editor de mapa ativo",
MessageBoxButton.OK,
MessageBoxImage.Information
);
return;
}
if (_mapas.CriacaoMapaAtiva)
{
MessageBox.Show(
"Finalize ou cancele a criação atual antes de carregar outro mapa.",
"Criação de mapa ativa",
MessageBoxButton.OK,
MessageBoxImage.Information
);
return;
}
_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 (
_mapas.CapturaBordaGnssAutomaticaAtiva ||
_mapas.CapturaCriacaoAutomaticaAtiva
)
{
MessageBox.Show(
"Finalize o levantamento GNSS automático antes de alterar " +
"a configuração física da estação.",
"Levantamento em andamento",
MessageBoxButton.OK,
MessageBoxImage.Warning
);
return;
}
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 ||
_mapas.CapturaCriacaoAutomaticaAtiva
)
{
MessageBox.Show(
"Existe um levantamento GNSS automático em andamento.\n\n" +
"Finalize a captura 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<OperacaoParametrosModel> 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<double?>() ?? 0.0;
double conexao_perda = conexao?.detalhes?["loss_pct"]?.Value<double?>() ?? 0.0;
double bwTot = conexao?.detalhes?["bw_total_mbps"]?.Value<double?>() ?? 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<string, (bool, bool)>()
{
{ "ET", (true, true) },
{ "DT", (true, true) },
{ "EF", (true, true) },
{ "DF", (true, true) },
}
};
}
else
{
foreach (var key in _mov.ComponentesEmUso?.Keys.ToList() ?? new List<string>())
{
_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<string, (bool, bool)>()
{
{ "ET", (true, true) },
{ "DT", (true, true) },
{ "EF", (true, true) },
{ "DF", (true, true) },
}
};
}
else
{
foreach (var key in _dir.ComponentesEmUso?.Keys.ToList() ?? new List<string>())
{
_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<string, (bool, bool)>()
{
{ "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<string>())
{
_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
}
}