Merge branch 'op_control' of http://zendioninc.com.br:3000/Zendion/agrobot_base into op_control

This commit is contained in:
Diego Freitas 2026-03-24 19:20:51 -03:00
commit fdb71b03bf
13 changed files with 411 additions and 314 deletions

View File

@ -1582,12 +1582,8 @@ namespace AgroBase.Models
if (controleBase.Dispositivo == T_Code.Gps)
{
var (la_frontal, la_lateral) = ((JObject)controleBase._comp_value).ToObject<(double?, double?)>();
if (la_frontal != null) VariaveisEquipamento.LeverArmFrontal = (double)la_frontal;
if (la_lateral != null) VariaveisEquipamento.LeverArmLateral = (double)la_lateral;
GPSService.UltimaLeitura.LeverArmFrontal = VariaveisEquipamento.LeverArmFrontal;
GPSService.UltimaLeitura.LeverArmLateral = VariaveisEquipamento.LeverArmLateral;
if (la_lateral != null || la_frontal != null)
GPSService.LeverArm = new GeoLeverArm(VariaveisEquipamento.LeverArmFrontal, VariaveisEquipamento.LeverArmLateral);
GPSService.LeverArm = new GeoLeverArm(la_frontal ?? 0, la_lateral ?? 0);
return;
}
@ -1702,8 +1698,8 @@ namespace AgroBase.Models
var cmdRover = GeneralJoystick.DeParaComandos.FirstOrDefault(x => x.botaoJoy == controleBase.Tecla);
if (cmdRover?.Dispositivo == T_Code.Mov && Variaveis.OperacaoEmAndamento.Parametros.Controle.MovimentoAutomatico && Variaveis.OperacaoEmAndamento.Iniciado) return;
if (cmdRover?.Dispositivo == T_Code.Dir && Variaveis.OperacaoEmAndamento.Parametros.Controle.DirecionalAutomatico && Variaveis.OperacaoEmAndamento.Iniciado) return;
if (cmdRover?.Dispositivo == T_Code.Mov && (Variaveis.OperacaoEmAndamento.Parametros?.Controle?.MovimentoAutomatico ?? false) && Variaveis.OperacaoEmAndamento.Iniciado) return;
if (cmdRover?.Dispositivo == T_Code.Dir && (Variaveis.OperacaoEmAndamento.Parametros?.Controle?.DirecionalAutomatico ?? false) && Variaveis.OperacaoEmAndamento.Iniciado) return;
var _Controle = Variaveis.OperacaoEmAndamento.Controle;
if (controleBase.Controle.PercentualVelocidadeSP != null) _Controle.PercentualVelocidadeSP = (double)controleBase.Controle.PercentualVelocidadeSP;
@ -1716,10 +1712,10 @@ namespace AgroBase.Models
switch (cmdRover?.Dispositivo)
{
case T_Code.Mov:
GeneralJoystick.ProcessarDadosControle(cmdRover.botaoJoy, controleBase.Solto, valorDesejado: _Controle.PercentualVelocidadeSP);
GeneralJoystick.ProcessarDadosControle(cmdRover.botaoJoy, controleBase.Solto, valorDesejado: _Controle.PercentualVelocidadeSP, ForcarComando: true);
break;
case T_Code.Dir:
GeneralJoystick.ProcessarDadosControle(cmdRover.botaoJoy, controleBase.Solto, valorDesejado: _Controle.Angulo);
GeneralJoystick.ProcessarDadosControle(cmdRover.botaoJoy, controleBase.Solto, valorDesejado: _Controle.Angulo, ForcarComando: true);
break;
}
}

View File

@ -449,8 +449,8 @@ namespace AgroBase.Models
public static double ComprimentoFrente { get; } = 7.0; // 7
public static double ComprimentoTras { get; } = 107.0; // 107
public static double DistanciaEntreEixos { get; } = 92.0;
public static double LeverArmFrontal { get; set; } = 100.0; // cm
public static double LeverArmLateral { get; set; } = 0.0; // cm
public static double LeverArmFrontalCm { get; } = 100.0; // cm
public static double LeverArmLateralCm { get; } = 0.0; // cm
public static double LarguraEquipamentoMm
{
get

View File

@ -49,7 +49,7 @@ namespace AgroBase.Services
private static bool LoopRTK_Ntrip = false;
public static bool CorrecaoRTK_Ntrip = false;
public static DateTime UltimoEnvioCorrecaoRTK = DateTime.MinValue;
public static GeoLeverArm LeverArm = new GeoLeverArm(VariaveisEquipamento.LeverArmFrontal, VariaveisEquipamento.LeverArmLateral);
public static GeoLeverArm LeverArm = new GeoLeverArm();
public static void AtualizarPortaCOM(SerialPort Porta)
{
@ -969,8 +969,8 @@ namespace AgroBase.Services
UltimaLeitura.Ntrip_ativado = CorrecaoRTK_Ntrip;
UltimaLeitura.Heartbeat = (UltimaLeitura.Heartbeat + 1) % 10;
UltimaLeitura.LeverArmFrontal = VariaveisEquipamento.LeverArmFrontal;
UltimaLeitura.LeverArmLateral = VariaveisEquipamento.LeverArmLateral;
UltimaLeitura.LeverArmFrontal = LeverArm?.FrontalTotalCm ?? 0;
UltimaLeitura.LeverArmLateral = LeverArm?.LateralTotalCm ?? 0;
if (Variaveis.IsAgroMonitor)
{
@ -1214,7 +1214,7 @@ namespace AgroBase.Services
double headingDeg, // OrientacaoReal (antena/corpo)
double angMovDeg, // OrientacaoMovimento (calculada acima)
double distLinearJanela, // UltimaLeitura.Distancia (Δpos linear entre 1ª e última amostra)
double velPercent = 0.0, // se tiver velocidade filtrada, passe aqui; senão 0
double velPercent = 0.0, // se tiver velocidade filtrada, passe aqui; senão 0
bool rtkFix = true, // se tiver essa info
double angFundidoAnterior = double.NaN, // para low-pass; passe double.NaN para desabilitar
double alfaLowPass = 0.25 // 0→sem low-pass; 0.20.35 costuma ser bom
@ -1359,26 +1359,82 @@ namespace AgroBase.Services
}
}
public class GeoLeverArm
{
public GeoLeverArm(double _xbCm = 0, double _ybCm = 0, bool _invH = false, bool _mirrL = false)
// Lever arm físico fixo do equipamento
private readonly double xFisico; // +frente (m)
private readonly double yFisico; // +direita (m)
// Offset operacional/de campo
private readonly double xCampo; // +frente (m)
private readonly double yCampo; // +direita (m)
private readonly bool invertHeading;
private readonly bool mirrorLateral;
public GeoLeverArm(double offsetCampoFrontalCm = 0, double offsetCampoLateralCm = 0, bool invH = false, bool mirrL = false)
{
xB = _xbCm / 100.0;
yB = _ybCm / 100.0;
invertHeading = _invH;
mirrorLateral = _mirrL;
// fixos do equipamento
xFisico = VariaveisEquipamento.LeverArmFrontalCm / 100.0;
yFisico = VariaveisEquipamento.LeverArmLateralCm / 100.0;
// ajustes de campo
xCampo = offsetCampoFrontalCm / 100.0;
yCampo = offsetCampoLateralCm / 100.0;
invertHeading = invH;
mirrorLateral = mirrL;
}
const double R = 6378137.0; // WGS84
private readonly double xB; // +frente (m)
private readonly double yB; // +direita (m)
private readonly bool invertHeading = false;
private readonly bool mirrorLateral = false;
private double ToRad(double deg) => deg * Math.PI / 180.0;
private double ToDeg(double rad) => rad * 180.0 / Math.PI;
public double FrontalTotalCm => (xFisico + xCampo) * 100.0;
public double LateralTotalCm => (yFisico + yCampo) * 100.0;
/// <summary>
/// Convenção:
/// - Entrada = posição da antena GNSS
/// - Saída = posição corrigida do centro/VRP
/// - heading: 0° = Norte, 90° = Leste, sentido horário
/// - frontal positivo = antena à frente do centro
/// - lateral positivo = antena à direita do centro
/// Consequência:
/// - frontal positivo desloca a posição corrigida para trás do robô
/// - lateral positivo desloca a posição corrigida para a esquerda do robô
/// </summary>
public (double lat, double lon) FixLeverArmLatLon_Fast(double latAnt_deg, double lonAnt_deg, double headingDeg)
{
double xTotal = xFisico + xCampo;
double yTotal = yFisico + yCampo;
if (mirrorLateral)
yTotal = -yTotal;
double th = ToRad(headingDeg);
if (invertHeading)
th = -th;
// Convenção NAV
double fE = Math.Sin(th); // forward east
double fN = Math.Cos(th); // forward north
double rE = fN; // right east
double rN = -fE; // right north
// deslocamento da antena no mundo
double dE = xTotal * fE + yTotal * rE;
double dN = xTotal * fN + yTotal * rN;
double latRad = ToRad(latAnt_deg);
double dLat_deg = (dN / GPSUtils.RaioDaTerra) * 180.0 / Math.PI;
double dLon_deg = (dE / (GPSUtils.RaioDaTerra * Math.Cos(latRad))) * 180.0 / Math.PI;
// retorna o centro/VRP
return (latAnt_deg - dLat_deg, lonAnt_deg - dLon_deg);
}
// lat/lon -> ENU (aproximação local, boa para ~km)
public (double x, double y) GeodeticToENU(double lat, double lon, double lat0, double lon0)
@ -1386,58 +1442,11 @@ namespace AgroBase.Services
double latR = ToRad(lat), lonR = ToRad(lon);
double lat0R = ToRad(lat0), lon0R = ToRad(lon0);
double dLat = latR - lat0R, dLon = lonR - lon0R;
double xEast = dLon * Math.Cos(lat0R) * R;
double yNorth = dLat * R;
double xEast = dLon * Math.Cos(lat0R) * GPSUtils.RaioDaTerra;
double yNorth = dLat * GPSUtils.RaioDaTerra;
return (xEast, yNorth);
}
public (double lat, double lon) ENUToGeodetic(double x, double y, double lat0, double lon0)
{
double lat0R = ToRad(lat0);
double dLat = y / R;
double dLon = x / (R * Math.Cos(lat0R));
double lat = lat0 + ToDeg(dLat);
double lon = lon0 + ToDeg(dLon);
return (lat, lon);
}
// Aplica lever arm: retorna o CENTRO dado ANTENA + heading.
// Convenção: θ=0 norte; +horário→leste (igual ao seu modelo: x+=sinθ, y+=cosθ).
public (double xCtr, double yCtr) ApplyLeverArmENU(double xAnt, double yAnt, double thetaRad, double lFwd, double lLeft)
{
double s = Math.Sin(thetaRad), c = Math.Cos(thetaRad);
// vetores no mundo
double fx = s, fy = c; // frente
double lx = -c, ly = s; // esquerda
// vetor centro->antena no mundo
double rx = lFwd * fx + lLeft * lx;
double ry = lFwd * fy + lLeft * ly;
// centro = antena - (centro->antena)
return (xAnt - rx, yAnt - ry);
}
public (double lat, double lon) FixLeverArmLatLon_Fast(double latAnt_deg, double lonAnt_deg, double headingDeg)
{
double _yB = (mirrorLateral) ? -yB : yB; // espelha lateral (D<->E)
double th = headingDeg * Math.PI / 180.0;
if (invertHeading) th = -th; // inverte sentido do ângulo
// Convenção NAV: 0°=Norte, cresce horário
double fE = Math.Sin(th), fN = Math.Cos(th); // forward (E,N)
double rE = fN, rN = -fE; // right (E,N)
double dE = xB * fE + _yB * rE; // body -> ENU
double dN = xB * fN + _yB * rN;
double latRad = latAnt_deg * Math.PI / 180.0;
double dLat_deg = (dN / R) * 180.0 / Math.PI;
double dLon_deg = (dE / (R * Math.Cos(latRad))) * 180.0 / Math.PI;
// antena -> VRP (subtrai o offset da antena)
return (latAnt_deg - dLat_deg, lonAnt_deg - dLon_deg);
}
}
}

View File

@ -246,9 +246,7 @@ namespace OperationControl.Services
{
Models.Variaveis.MostrarLog("Iniciando configuração do módulo GPS...");
LeverArm = new GeoLeverArm(
VariaveisControleOperacao.LeverArmFrontal,
VariaveisControleOperacao.LeverArmLateral);
LeverArm = new GeoLeverArm(VariaveisControleOperacao.LeverArmFrontal, VariaveisControleOperacao.LeverArmLateral);
BaseFix = new BaseFixService(PortaGps, this);
BaseFix.FixLiberado = fixar;

View File

@ -147,7 +147,7 @@ public sealed class ManualControlSender : IDisposable
}
private static bool IsNeutral(UdpCtrlMessage msg)
=> msg.Key == AgroBase.Models.Enums.BotoesJoystick.Vazio;
=> msg.Key == AgroBase.Models.Enums.BotoesJoystick.Vazio || msg.released;
private UdpCtrlMessage BuildStopMsg()
{
@ -156,6 +156,7 @@ public sealed class ManualControlSender : IDisposable
Device = _device,
Flags = UdpCtrlFlags.HasKey | UdpCtrlFlags.HasPayload,
Key = AgroBase.Models.Enums.BotoesJoystick.Vazio,
released = false,
P1 = 0,
P2 = 0
};
@ -169,4 +170,5 @@ public sealed class ManualControlSender : IDisposable
_cts = null;
_loop = null;
}
}

View File

@ -197,14 +197,10 @@ namespace OperationControl.ViewModels
TudoOkChanged?.Invoke(TudoOk);
}
// ====== STATUS INICIAL (fake por enquanto) ======
// ====== STATUS INICIAL ======
public void CarregarStatusInicial()
{
// aqui você pluga de verdade nos seus serviços:
// var statusRede = NetworkService.Get();
// var statusGnss = GnssService.Get();
AtualizarStatusRede();
AtualizarStatusGnss();
}

View File

@ -140,30 +140,35 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento.Diagnostico
// ATUADOR / SISTEMA DE PULVERIZAÇÃO
// =============================
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_BMBAGT", Modulo = T_Code.Atu, LabelModulo = "BMBAGT", SCode = S_Code.sBMB, TextoBase = "AGIT", Left = 315, Top = 570 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_PRSLN", Modulo = T_Code.Atu, LabelModulo = "PRSLN", SCode = S_Code.sPRS, TextoBase = "PRS", Left = 230, Top = 610 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU", Modulo = T_Code.Atu, LabelModulo = "", SCode = S_Code.sMOD, TextoBase = "ATU", Left = 315, Top = 610 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_FLXLN", Modulo = T_Code.Atu, LabelModulo = "FLXLN", SCode = S_Code.sFLX, TextoBase = "FLX", Left = 400, Top = 610 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_BOMBA", Modulo = T_Code.Atu, LabelModulo = "BOMBA", SCode = S_Code.sBMB, TextoBase = "BMB", Left = 230, Top = 650 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_BMBLN", Modulo = T_Code.Atu, LabelModulo = "BOMBA", SCode = S_Code.sBMB, TextoBase = "BMB", Left = 230, Top = 650 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_MASRS", Modulo = T_Code.Atu, LabelModulo = "MASRS", SCode = S_Code.sMAS, TextoBase = "MAS", Left = 400, Top = 650 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "CAM", Modulo = T_Code.Cam, LabelModulo = "", SCode = S_Code.sVZO, TextoBase = "CAM", Left = 315, Top = 650 });
// =============================
// SENSORES BARRA PULVERIZADORA
// =============================
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_MAGESQ", Modulo = T_Code.Atu, LabelModulo = "MAGESQ", SCode = S_Code.sMAG, TextoBase = "MAG", Left = 165, Top = 705 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_MAGALT", Modulo = T_Code.Atu, LabelModulo = "MAGALT", SCode = S_Code.sMAG, TextoBase = "MAG", Left = 315, Top = 705 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_MAGDIR", Modulo = T_Code.Atu, LabelModulo = "MAGDIR", SCode = S_Code.sMAG, TextoBase = "MAG", Left = 465, Top = 705 });
// =============================
// BICOS
// =============================
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_SRVB1", Modulo = T_Code.Atu, LabelModulo = "SRVB1", SCode = S_Code.sSRV, TextoBase = "SVB1", Left = 110, Top = 705 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_B01", Modulo = T_Code.Atu, LabelModulo = "B01", SCode = S_Code.sBIC, TextoBase = "B01", Left = 110, Top = 745 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_MAGESQ", Modulo = T_Code.Atu, LabelModulo = "MAGESQ", SCode = S_Code.sMAG, TextoBase = "MAG", Left = 215, Top = 730 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_MAGALT", Modulo = T_Code.Atu, LabelModulo = "MAGALT", SCode = S_Code.sMAG, TextoBase = "MAG", Left = 315, Top = 705 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_MAGDIR", Modulo = T_Code.Atu, LabelModulo = "MAGDIR", SCode = S_Code.sMAG, TextoBase = "MAG", Left = 415, Top = 730 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_B02", Modulo = T_Code.Atu, LabelModulo = "B02", SCode = S_Code.sBIC, TextoBase = "B02", Left = 315, Top = 745 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_SRVB3", Modulo = T_Code.Atu, LabelModulo = "SRVB3", SCode = S_Code.sSRV, TextoBase = "SVB3", Left = 520, Top = 705 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_B03", Modulo = T_Code.Atu, LabelModulo = "B03", SCode = S_Code.sBIC, TextoBase = "B03", Left = 520, Top = 745 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_B01", Modulo = T_Code.Atu, LabelModulo = "B01", SCode = S_Code.sBIC, TextoBase = "B01", Left = 90, Top = 745 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_B02", Modulo = T_Code.Atu, LabelModulo = "B02", SCode = S_Code.sBIC, TextoBase = "B02", Left = 165, Top = 745 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_B03", Modulo = T_Code.Atu, LabelModulo = "B03", SCode = S_Code.sBIC, TextoBase = "B03", Left = 240, Top = 745 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_B04", Modulo = T_Code.Atu, LabelModulo = "B04", SCode = S_Code.sBIC, TextoBase = "B04", Left = 315, Top = 745 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_B05", Modulo = T_Code.Atu, LabelModulo = "B05", SCode = S_Code.sBIC, TextoBase = "B05", Left = 390, Top = 745 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_B06", Modulo = T_Code.Atu, LabelModulo = "B06", SCode = S_Code.sBIC, TextoBase = "B06", Left = 465, Top = 745 });
DiagnosticoModulos.Add(new DiagnosticoModuloItemViewModel { IdVisual = "ATU_B07", Modulo = T_Code.Atu, LabelModulo = "B07", SCode = S_Code.sBIC, TextoBase = "B07", Left = 540, Top = 745 });
}
@ -586,6 +591,14 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento.Diagnostico
{
mod.Mandatorio = MandatorioSelecionado;
mod.Utilizar = EmUsoSelecionado;
if (mod.ComponentesEmUso != null)
{
var chaves = mod.ComponentesEmUso.Keys.ToList();
foreach (var key in chaves)
{
mod.ComponentesEmUso[key] = (MandatorioSelecionado, EmUsoSelecionado);
}
}
}
else
{

View File

@ -160,9 +160,9 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento
Task.Run(async () =>
{
await VariaveisControleOperacao.EnviarComandoParadaUDP(pausa: true);
await Task.Delay(500);
await Task.Delay(2000);
VariaveisControleOperacao.EnviarComandoRetornoBase(lat, lon);
await Task.Delay(1000);
await Task.Delay(2000);
await VariaveisControleOperacao.EnviarComandoParadaUDP(pausa: false);
});

View File

@ -17,6 +17,7 @@ namespace OperationControl.ViewModels.Views.Operacao
public ICommand AlternarOperacaoCommand { get; }
public ICommand ParadaEmergenciaCommand { get; }
public ICommand CancelarOperacaoCommand { get; }
public ICommand ReconfigurarGnssCommand { get; }
private bool _operacaoPausada => (VariaveisControleOperacao.RoverEmFoco?.DadosLeitura?.Operacao?.Pausa ?? false);
private bool _operacaoEmExecucao;
public bool OperacaoEmExecucao
@ -42,10 +43,10 @@ namespace OperationControl.ViewModels.Views.Operacao
? new SolidColorBrush(Color.FromRgb(120, 88, 20))
: new SolidColorBrush(Color.FromRgb(30, 90, 58));
public System.Windows.Media.Brush BordaBotaoOperacao =>
public Brush BordaBotaoOperacao =>
OperacaoEmExecucao && !_operacaoPausada
? new SolidColorBrush(System.Windows.Media.Color.FromRgb(170, 130, 40))
: new SolidColorBrush(System.Windows.Media.Color.FromRgb(74, 138, 91));
? new SolidColorBrush(Color.FromRgb(170, 130, 40))
: new SolidColorBrush(Color.FromRgb(74, 138, 91));
private bool _roverSelecionado;
@ -60,10 +61,19 @@ namespace OperationControl.ViewModels.Views.Operacao
OnPropertyChanged(nameof(RoverSelecionado));
OnPropertyChanged(nameof(NomeRover));
OnPropertyChanged(nameof(OpacityPainel));
OnPropertyChanged(nameof(RoverNaoSelecionado));
OnPropertyChanged(nameof(VisibilidadeAcoesRover));
OnPropertyChanged(nameof(VisibilidadeConfiguracaoGnss));
}
}
}
public bool RoverNaoSelecionado => !RoverSelecionado;
public Visibility VisibilidadeAcoesRover => RoverSelecionado ? Visibility.Visible : Visibility.Collapsed;
public Visibility VisibilidadeConfiguracaoGnss => !RoverSelecionado ? Visibility.Visible : Visibility.Collapsed;
private string _nomeRover = "Nenhum rover selecionado";
public string NomeRover
{
@ -252,6 +262,7 @@ namespace OperationControl.ViewModels.Views.Operacao
AlternarOperacaoCommand = new RelayCommand(_ => AlternarOperacao());
ParadaEmergenciaCommand = new RelayCommand(_ => ExecutarParadaEmergencia());
CancelarOperacaoCommand = new RelayCommand(_ => CancelarOperacao());
ReconfigurarGnssCommand = new RelayCommand(_ => ReconfigurarGnss());
DefinirSemRover();
}
@ -342,9 +353,9 @@ namespace OperationControl.ViewModels.Views.Operacao
if ((dadosRover?.Operacao?.Status ?? StatusOperacao.NaoIniciado) == StatusOperacao.Concluido)
{
VariaveisControleOperacao.EnviarComandoIniciarOperacao(false, false);
await Task.Delay(500);
await Task.Delay(2000);
}
bool simulador = VariaveisControleOperacao.RoverEmFoco?.DadosLeitura?.Gnss?.Iniciado != true;
bool simulador = AppShell.Mock || AppShell.AddRover; // || VariaveisControleOperacao.RoverEmFoco?.DadosLeitura?.Gnss?.Iniciado != true;
VariaveisControleOperacao.EnviarComandoIniciarOperacao(true, simulador);
await Task.Delay(1000);
if (!(dadosRover?.Operacao?.Liberada ?? false))
@ -372,6 +383,10 @@ namespace OperationControl.ViewModels.Views.Operacao
}
}
private void ReconfigurarGnss()
{
Variaveis.Dock._vm.CurrentStep = DockStep.PreparacaoMapa;
}
private string ValorOuTracos(string valor) => RoverSelecionado ? valor : "--";

View File

@ -1,4 +1,5 @@
using OperationControl.Helpers;
using Microsoft.Win32;
using OperationControl.Helpers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@ -26,7 +27,9 @@ namespace OperationControl.ViewModels
public event PropertyChangedEventHandler? PropertyChanged;
public event Action<string>? CarregarMapaSolicitado;
public event Action<MetodoFixacaoBase, double?, double?, double?>? FixarBaseSolicitado;
// Método, latitude, longitude, altitude, offset frontal cm, offset lateral cm
public event Action<MetodoFixacaoBase, double?, double?, double?, double?, double?>? FixarBaseSolicitado;
public List<MetodoFixacaoItem> MetodosFixacaoDisponiveis { get; } = new()
{
@ -35,45 +38,18 @@ namespace OperationControl.ViewModels
new MetodoFixacaoItem { Valor = MetodoFixacaoBase.Manual, Nome = "Preenchimento manual" }
};
private bool _fixacaoEmAndamento;
public bool FixacaoEmAndamento
public ICommand CarregarMapaCommand { get; }
public ICommand FixarBaseCommand { get; }
public PreparacaoMapaBottomViewModel()
{
get => _fixacaoEmAndamento;
set
{
if (_fixacaoEmAndamento != value)
{
_fixacaoEmAndamento = value;
OnPropertyChanged(nameof(FixacaoEmAndamento));
OnPropertyChanged(nameof(PodeCarregarMapa));
OnPropertyChanged(nameof(PodeFixarBase));
OnPropertyChanged(nameof(PodeEditarMetodoFixacao));
OnPropertyChanged(nameof(PodeEditarCoordenadasManuais));
}
}
}
CarregarMapaCommand = new RelayCommand(_ => ExecutarCarregarMapa(), _ => PodeCarregarMapa);
FixarBaseCommand = new RelayCommand(_ => ExecutarFixarBase(), _ => PodeFixarBase);
public bool PodeCarregarMapa => !FixacaoEmAndamento;
public bool PodeEditarMetodoFixacao => !FixacaoEmAndamento;
public bool PodeEditarCoordenadasManuais =>
!FixacaoEmAndamento && MetodoFixacaoSelecionado == MetodoFixacaoBase.Manual;
public bool PodeFixarBase
{
get
{
if (FixacaoEmAndamento)
return true; // permite "Parar"
if (MetodoFixacaoSelecionado != MetodoFixacaoBase.Manual)
return true;
return LatitudeManual.HasValue &&
LongitudeManual.HasValue &&
AltitudeManual.HasValue;
}
MetodoFixacaoSelecionado = MetodoFixacaoBase.Ntrip;
OffsetFrontalBaseTexto = "0";
OffsetLateralBaseTexto = "0";
StatusTexto = "Pronto para carregar o mapa e fixar a base.";
}
private MetodoFixacaoBase _metodoFixacaoSelecionado = MetodoFixacaoBase.Ntrip;
@ -88,7 +64,6 @@ namespace OperationControl.ViewModels
OnPropertyChanged(nameof(MetodoFixacaoSelecionado));
OnPropertyChanged(nameof(ExibirCamposManuais));
OnPropertyChanged(nameof(ExibirStatusNormal));
OnPropertyChanged(nameof(ExibirProgresso));
OnPropertyChanged(nameof(PodeEditarCoordenadasManuais));
OnPropertyChanged(nameof(PodeFixarBase));
@ -112,10 +87,69 @@ namespace OperationControl.ViewModels
}
}
private bool _fixacaoEmAndamento;
public bool FixacaoEmAndamento
{
get => _fixacaoEmAndamento;
set
{
if (_fixacaoEmAndamento != value)
{
_fixacaoEmAndamento = value;
OnPropertyChanged(nameof(FixacaoEmAndamento));
OnPropertyChanged(nameof(PodeCarregarMapa));
OnPropertyChanged(nameof(PodeEditarMetodoFixacao));
OnPropertyChanged(nameof(PodeEditarCoordenadasManuais));
OnPropertyChanged(nameof(PodeEditarOffsetBase));
OnPropertyChanged(nameof(PodeFixarBase));
OnPropertyChanged(nameof(TextoBotaoFixarBase));
}
}
}
public bool PodeCarregarMapa => !FixacaoEmAndamento;
public bool PodeEditarMetodoFixacao => !FixacaoEmAndamento;
public bool PodeEditarCoordenadasManuais =>
!FixacaoEmAndamento && MetodoFixacaoSelecionado == MetodoFixacaoBase.Manual;
public bool PodeEditarOffsetBase => !FixacaoEmAndamento;
public bool ExibirCamposManuais => MetodoFixacaoSelecionado == MetodoFixacaoBase.Manual;
public bool ExibirStatusNormal => MetodoFixacaoSelecionado != MetodoFixacaoBase.Manual;
public bool ExibirProgresso => MetodoFixacaoSelecionado != MetodoFixacaoBase.Manual;
public bool PodeFixarBase
{
get
{
if (FixacaoEmAndamento)
return true; // botão vira "Parar"
if (MetodoFixacaoSelecionado != MetodoFixacaoBase.Manual)
return true;
return LatitudeManual.HasValue &&
LongitudeManual.HasValue &&
AltitudeManual.HasValue;
}
}
public string TextoBotaoFixarBase
{
get
{
if (FixacaoEmAndamento)
return "Parar";
return MetodoFixacaoSelecionado == MetodoFixacaoBase.Manual
? "Aplicar posição manual"
: "Fixar posição da base";
}
}
private double? _latitudeManual;
public double? LatitudeManual
{
@ -171,6 +205,7 @@ namespace OperationControl.ViewModels
{
_latitudeManualTexto = value;
LatitudeManual = TryParseDouble(value);
OnPropertyChanged(nameof(LatitudeManualTexto));
OnPropertyChanged(nameof(PodeFixarBase));
}
@ -187,6 +222,7 @@ namespace OperationControl.ViewModels
{
_longitudeManualTexto = value;
LongitudeManual = TryParseDouble(value);
OnPropertyChanged(nameof(LongitudeManualTexto));
OnPropertyChanged(nameof(PodeFixarBase));
}
@ -203,12 +239,73 @@ namespace OperationControl.ViewModels
{
_altitudeManualTexto = value;
AltitudeManual = TryParseDouble(value);
OnPropertyChanged(nameof(AltitudeManualTexto));
OnPropertyChanged(nameof(PodeFixarBase));
}
}
}
private double? _offsetFrontalBaseCm;
public double? OffsetFrontalBaseCm
{
get => _offsetFrontalBaseCm;
private set
{
if (_offsetFrontalBaseCm != value)
{
_offsetFrontalBaseCm = value;
OnPropertyChanged(nameof(OffsetFrontalBaseCm));
}
}
}
private double? _offsetLateralBaseCm;
public double? OffsetLateralBaseCm
{
get => _offsetLateralBaseCm;
private set
{
if (_offsetLateralBaseCm != value)
{
_offsetLateralBaseCm = value;
OnPropertyChanged(nameof(OffsetLateralBaseCm));
}
}
}
private string _offsetFrontalBaseTexto = "0";
public string OffsetFrontalBaseTexto
{
get => _offsetFrontalBaseTexto;
set
{
if (_offsetFrontalBaseTexto != value)
{
_offsetFrontalBaseTexto = value;
OffsetFrontalBaseCm = TryParseDouble(value);
OnPropertyChanged(nameof(OffsetFrontalBaseTexto));
}
}
}
private string _offsetLateralBaseTexto = "0";
public string OffsetLateralBaseTexto
{
get => _offsetLateralBaseTexto;
set
{
if (_offsetLateralBaseTexto != value)
{
_offsetLateralBaseTexto = value;
OffsetLateralBaseCm = TryParseDouble(value);
OnPropertyChanged(nameof(OffsetLateralBaseTexto));
}
}
}
private double _progressoFixacao;
public double ProgressoFixacao
{
@ -226,7 +323,7 @@ namespace OperationControl.ViewModels
public string ProgressoFixacaoTexto => $"{ProgressoFixacao:F0}%";
private string _statusTexto = "Pronto para carregar o mapa e fixar a base.";
private string _statusTexto = string.Empty;
public string StatusTexto
{
get => _statusTexto;
@ -240,37 +337,15 @@ namespace OperationControl.ViewModels
}
}
public string TextoBotaoFixarBase
{
get
{
if (FixacaoEmAndamento)
return "Parar";
return MetodoFixacaoSelecionado == MetodoFixacaoBase.Manual
? "Aplicar posição manual"
: "Fixar posição da base";
}
}
public ICommand CarregarMapaCommand { get; }
public ICommand FixarBaseCommand { get; }
public PreparacaoMapaBottomViewModel()
{
CarregarMapaCommand = new RelayCommand(_ => ExecutarCarregarMapa(), _ => PodeCarregarMapa);
FixarBaseCommand = new RelayCommand(_ => ExecutarFixarBase(), _ => PodeFixarBase);
}
private void ExecutarCarregarMapa()
{
var dlg = new OpenFileDialog
var dlg = new Microsoft.Win32.OpenFileDialog
{
Filter = "Arquivos de mapa|*.geojson;*.json;*.shp",
Title = "Selecione o arquivo de mapa"
};
if (dlg.ShowDialog() == DialogResult.OK)
if (dlg.ShowDialog() == true)
{
StatusTexto = $"Mapa selecionado: {System.IO.Path.GetFileName(dlg.FileName)}";
CarregarMapaSolicitado?.Invoke(dlg.FileName);
@ -285,7 +360,9 @@ namespace OperationControl.ViewModels
MetodoFixacaoSelecionado,
LatitudeManual,
LongitudeManual,
AltitudeManual);
AltitudeManual,
OffsetFrontalBaseCm,
OffsetLateralBaseCm);
return;
}
@ -308,19 +385,14 @@ namespace OperationControl.ViewModels
MetodoFixacaoSelecionado,
LatitudeManual,
LongitudeManual,
AltitudeManual);
}
public void AtualizarProgresso(double progresso, string status)
{
ProgressoFixacao = progresso;
StatusTexto = status;
AltitudeManual,
OffsetFrontalBaseCm,
OffsetLateralBaseCm);
}
public void IniciarFixacao()
{
FixacaoEmAndamento = true;
OnPropertyChanged(nameof(TextoBotaoFixarBase));
ProgressoFixacao = 0;
StatusTexto = MetodoFixacaoSelecionado switch
@ -332,31 +404,29 @@ namespace OperationControl.ViewModels
};
}
public void AtualizarProgresso(double progresso, string status)
{
ProgressoFixacao = progresso;
StatusTexto = status;
}
public void FinalizarFixacao(bool sucesso, string? mensagem = null)
{
FixacaoEmAndamento = false;
OnPropertyChanged(nameof(TextoBotaoFixarBase));
if (MetodoFixacaoSelecionado != MetodoFixacaoBase.Manual)
ProgressoFixacao = sucesso ? 100 : ProgressoFixacao;
if (MetodoFixacaoSelecionado != MetodoFixacaoBase.Manual && sucesso)
ProgressoFixacao = 100;
if (!string.IsNullOrWhiteSpace(mensagem))
{
StatusTexto = mensagem;
}
else
{
StatusTexto = sucesso
StatusTexto = !string.IsNullOrWhiteSpace(mensagem)
? mensagem
: sucesso
? "Fixação da base concluída com sucesso."
: "Fixação da base encerrada.";
}
}
public void CancelarFixacao(string? mensagem = null)
{
FixacaoEmAndamento = false;
OnPropertyChanged(nameof(TextoBotaoFixarBase));
StatusTexto = mensagem ?? "Fixação da base cancelada.";
}
@ -378,14 +448,8 @@ namespace OperationControl.ViewModels
texto = texto.Trim().Replace(",", ".");
if (double.TryParse(
texto,
NumberStyles.Any,
CultureInfo.InvariantCulture,
out var valor))
{
if (double.TryParse(texto, NumberStyles.Any, CultureInfo.InvariantCulture, out var valor))
return valor;
}
return null;
}

View File

@ -776,7 +776,7 @@ namespace OperationControl.ViewModels
}
}
private async void OnFixarBaseSolicitado(MetodoFixacaoBase metodoFix, double? lat, double? lon, double? alt)
private async void OnFixarBaseSolicitado(MetodoFixacaoBase metodoFix, double? lat, double? lon, double? alt, double? offsetFrontalCm, double? offsetLateralCm)
{
try
{

View File

@ -26,12 +26,8 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="8">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="8">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
@ -40,12 +36,7 @@
</UserControl.Resources>
<Grid Background="#181818">
<Border Margin="10"
Padding="12"
CornerRadius="10"
Background="#202020"
BorderBrush="#343434"
BorderThickness="1">
<Border Margin="10" Padding="12" CornerRadius="10" Background="#202020" BorderBrush="#343434" BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
@ -54,24 +45,11 @@
</Grid.RowDefinitions>
<!-- CONTEÚDO SUPERIOR -->
<ScrollViewer Grid.Row="0"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Opacity="{Binding OpacityPainel}">
<TextBlock Text="Operação em Andamento"
FontSize="18"
FontWeight="Bold"
Foreground="White"
HorizontalAlignment="Center"
Margin="0,0,0,6"/>
<TextBlock Text="Operação em Andamento" FontSize="18" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center" Margin="0,0,0,6"/>
<TextBlock Text="{Binding NomeRover}"
FontSize="14"
FontWeight="SemiBold"
Foreground="#D0D0D0"
HorizontalAlignment="Center"
TextAlignment="Center"
Margin="0,0,0,14"/>
<TextBlock Text="{Binding NomeRover}" FontSize="14" FontWeight="SemiBold" Foreground="#D0D0D0" HorizontalAlignment="Center" TextAlignment="Center" Margin="0,0,0,14"/>
<Border Style="{StaticResource LeftInfoCardStyle}">
<Grid>
@ -193,41 +171,25 @@
</ScrollViewer>
<!-- RODAPÉ FIXO -->
<Border Grid.Row="1"
Margin="0,12,0,0"
Padding="10"
CornerRadius="10"
Background="#1A1A1A"
BorderBrush="#343434"
BorderThickness="1"
IsEnabled="{Binding RoverSelecionado}"
Opacity="{Binding OpacityPainel}">
<StackPanel>
<TextBlock Text="Ações principais"
Foreground="#CFCFCF"
FontSize="13"
FontWeight="SemiBold"
Margin="0,0,0,10"/>
<Border Grid.Row="1" Margin="0,12,0,0" Padding="10" CornerRadius="10" Background="#1A1A1A" BorderBrush="#343434" BorderThickness="1">
<Button Content="{Binding TextoBotaoOperacao}"
Command="{Binding AlternarOperacaoCommand}"
Style="{StaticResource LeftActionButtonStyle}"
Background="{Binding CorBotaoOperacao}"
BorderBrush="{Binding BordaBotaoOperacao}"/>
<Grid>
<StackPanel Visibility="{Binding VisibilidadeAcoesRover}">
<TextBlock Text="Ações principais" Foreground="#CFCFCF" FontSize="13" FontWeight="SemiBold" Margin="0,0,0,10"/>
<Button Content="⛔ Parada de Emergência"
Command="{Binding ParadaEmergenciaCommand}"
Style="{StaticResource LeftActionButtonStyle}"
Background="#7A1E1E"
BorderBrush="#B83A3A"/>
<Button Content="{Binding TextoBotaoOperacao}" Command="{Binding AlternarOperacaoCommand}" Style="{StaticResource LeftActionButtonStyle}" Background="{Binding CorBotaoOperacao}" BorderBrush="{Binding BordaBotaoOperacao}"/>
<Button Content="✖ Finalizar Operação"
Command="{Binding CancelarOperacaoCommand}"
Style="{StaticResource LeftActionButtonStyle}"
Background="#3A3A3A"
BorderBrush="#666666"
Margin="0,0,0,0"/>
</StackPanel>
<Button Content="⛔ Parada de Emergência" Command="{Binding ParadaEmergenciaCommand}" Style="{StaticResource LeftActionButtonStyle}" Background="#7A1E1E" BorderBrush="#B83A3A"/>
<Button Content="✖ Finalizar Operação" Command="{Binding CancelarOperacaoCommand}" Style="{StaticResource LeftActionButtonStyle}" Background="#3A3A3A" BorderBrush="#666666" Margin="0,0,0,0"/>
</StackPanel>
<StackPanel Visibility="{Binding VisibilidadeConfiguracaoGnss}">
<TextBlock Text="Configuração" Foreground="#CFCFCF" FontSize="13" FontWeight="SemiBold" Margin="0,0,0,10"/>
<Button Content="📡 Reconfigurar GNSS" Command="{Binding ReconfigurarGnssCommand}" Style="{StaticResource LeftActionButtonStyle}" Background="#1F3A5F" BorderBrush="#4F7DB8"/>
</StackPanel>
</Grid>
</Border>
</Grid>

View File

@ -37,68 +37,88 @@
<ColumnDefinition Width="240"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="12"/>
<!-- Área dinâmica -->
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Botão carregar mapa -->
<Button Grid.Column="0"
Content="Carregar mapa"
Margin="12,12,8,6"
Padding="18,10"
MinWidth="150"
Command="{Binding CarregarMapaCommand}"
IsEnabled="{Binding PodeCarregarMapa}"/>
<!-- Botão fixar -->
<Button Grid.Column="2"
Content="{Binding TextoBotaoFixarBase}"
Margin="8,12,12,6"
Padding="18,10"
MinWidth="180"
Command="{Binding FixarBaseCommand}"
IsEnabled="{Binding PodeFixarBase}"/>
Content="Carregar mapa"
Margin="12,12,8,6"
Padding="18,10"
MinWidth="150"
Command="{Binding CarregarMapaCommand}"
IsEnabled="{Binding PodeCarregarMapa}"/>
<!-- Combo método -->
<ComboBox Grid.Column="1"
Margin="8,12,8,6"
VerticalContentAlignment="Center"
ItemsSource="{Binding MetodosFixacaoDisponiveis}"
DisplayMemberPath="Nome"
SelectedValuePath="Valor"
SelectedValue="{Binding MetodoFixacaoSelecionado, Mode=TwoWay}"
IsEnabled="{Binding PodeEditarMetodoFixacao}"/>
Margin="8,12,8,6"
VerticalContentAlignment="Center"
ItemsSource="{Binding MetodosFixacaoDisponiveis}"
DisplayMemberPath="Nome"
SelectedValuePath="Valor"
SelectedValue="{Binding MetodoFixacaoSelecionado, Mode=TwoWay}"
IsEnabled="{Binding PodeEditarMetodoFixacao}"/>
<!-- Botão fixar -->
<Button Grid.Column="2"
Content="{Binding TextoBotaoFixarBase}"
Margin="8,12,12,6"
Padding="18,10"
MinWidth="180"
Command="{Binding FixarBaseCommand}"
IsEnabled="{Binding PodeFixarBase}"/>
<!-- ÁREA DINÂMICA -->
<Grid Grid.Column="4" Margin="0,12,12,6">
<!-- Progresso normal -->
<!-- Progresso normal + offsets -->
<Grid Visibility="{Binding ExibirProgresso, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="320"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ProgressBar Grid.Column="0"
Margin="0,8,8,0"
Height="22"
Minimum="0"
Maximum="100"
Value="{Binding ProgressoFixacao}"/>
Margin="0,8,8,0"
Height="22"
Minimum="0"
Maximum="100"
Value="{Binding ProgressoFixacao}"/>
<TextBlock Grid.Column="1"
Text="{Binding ProgressoFixacaoTexto}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Foreground="White"
FontWeight="SemiBold"
FontSize="14"
Margin="0,0,8,0"/>
Text="{Binding ProgressoFixacaoTexto}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Foreground="White"
FontWeight="SemiBold"
FontSize="14"
Margin="0,0,8,0"/>
<TextBlock Grid.Column="2"
Text="Offset frontal (cm):"
Style="{StaticResource BottomLabelStyle}"/>
<TextBox Grid.Column="3"
Text="{Binding OffsetFrontalBaseTexto, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource BottomTextBoxStyle}"
IsEnabled="{Binding PodeEditarOffsetBase}"/>
<TextBlock Grid.Column="4"
Text="Offset lateral (cm):"
Style="{StaticResource BottomLabelStyle}"/>
<TextBox Grid.Column="5"
Text="{Binding OffsetLateralBaseTexto, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource BottomTextBoxStyle}"
IsEnabled="{Binding PodeEditarOffsetBase}"/>
</Grid>
<!-- Campos manuais -->
<!-- Campos manuais + offsets -->
<Grid Visibility="{Binding ExibirCamposManuais, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@ -107,35 +127,57 @@
<ColumnDefinition Width="125"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="Latitude (º):"
Style="{StaticResource BottomLabelStyle}"/>
Text="Latitude (º):"
Style="{StaticResource BottomLabelStyle}"/>
<TextBox Grid.Column="1"
Text="{Binding LatitudeManualTexto, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource BottomTextBoxStyle}"
IsEnabled="{Binding PodeEditarCoordenadasManuais}"/>
Text="{Binding LatitudeManualTexto, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource BottomTextBoxStyle}"
IsEnabled="{Binding PodeEditarCoordenadasManuais}"/>
<TextBlock Grid.Column="2"
Text="Longitude (º):"
Style="{StaticResource BottomLabelStyle}"/>
Text="Longitude (º):"
Style="{StaticResource BottomLabelStyle}"/>
<TextBox Grid.Column="3"
Text="{Binding LongitudeManualTexto, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource BottomTextBoxStyle}"
IsEnabled="{Binding PodeEditarCoordenadasManuais}"/>
Text="{Binding LongitudeManualTexto, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource BottomTextBoxStyle}"
IsEnabled="{Binding PodeEditarCoordenadasManuais}"/>
<TextBlock Grid.Column="4"
Text="Altitude elipsoidal (m)"
Style="{StaticResource BottomLabelStyle}"/>
Text="Altitude elipsoidal (m):"
Style="{StaticResource BottomLabelStyle}"/>
<TextBox Grid.Column="5"
Text="{Binding AltitudeManualTexto, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource BottomTextBoxStyle}"
IsEnabled="{Binding PodeEditarCoordenadasManuais}"/>
Text="{Binding AltitudeManualTexto, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource BottomTextBoxStyle}"
IsEnabled="{Binding PodeEditarCoordenadasManuais}"/>
<TextBlock Grid.Column="6"
Text="Offset frontal (cm):"
Style="{StaticResource BottomLabelStyle}"/>
<TextBox Grid.Column="7"
Text="{Binding OffsetFrontalBaseTexto, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource BottomTextBoxStyle}"
IsEnabled="{Binding PodeEditarOffsetBase}"/>
<TextBlock Grid.Column="8"
Text="Offset lateral (cm):"
Style="{StaticResource BottomLabelStyle}"/>
<TextBox Grid.Column="9"
Text="{Binding OffsetLateralBaseTexto, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource BottomTextBoxStyle}"
IsEnabled="{Binding PodeEditarOffsetBase}"/>
</Grid>
</Grid>
</Grid>