otimizado o simulador

This commit is contained in:
Diego Freitas 2026-09-04 18:43:34 -03:00
parent 024eceef60
commit 1e7b725422
3 changed files with 2013 additions and 427 deletions

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms;
using static AgroBase.Models.Enums; using static AgroBase.Models.Enums;
namespace AgroBase.Services namespace AgroBase.Services
@ -171,6 +172,94 @@ namespace AgroBase.Services
private static string _lastError; private static string _lastError;
private static readonly object _metricsTextLock = new object(); private static readonly object _metricsTextLock = new object();
private const int IntervaloTrajetoriaMapaMs = 1_000;
private const int MaxPontosTrajetoriaMapa = 800;
private static int _publicacaoMapaAutomaticaHabilitada = 1;
private static long _ultimaTrajetoriaMapaMono;
/// <summary>
/// Permite que uma tela seja dona da publicação visual sem interromper o
/// processamento GNSS, Redis, trajetória ou histórico da operação.
/// O valor padrão preserva o comportamento legado.
/// </summary>
public static bool PublicacaoMapaAutomaticaHabilitada
{
get
{
return Volatile.Read(
ref _publicacaoMapaAutomaticaHabilitada
) == 1;
}
set
{
Volatile.Write(
ref _publicacaoMapaAutomaticaHabilitada,
value ? 1 : 0
);
}
}
public static void AtualizarLeituraSimulada(GPSModel leitura, int atrasoPosicaoPassos = 0)
{
if (leitura == null)
throw new ArgumentNullException(nameof(leitura));
lock (_stateLock)
{
double orientacaoReal =
GPSUtils.NormalizarAngulo(leitura.OrientacaoReal);
UltimaLeitura.Momento = leitura.Momento;
UltimaLeitura.DataHora = leitura.DataHora;
UltimaLeitura.UltimoComandoRespondido =
leitura.UltimoComandoRespondido;
UltimaLeitura.Latitude = leitura.Latitude;
UltimaLeitura.Longitude = leitura.Longitude;
UltimaLeitura.LatitudeAnt = leitura.LatitudeAnt;
UltimaLeitura.LongitudeAnt = leitura.LongitudeAnt;
UltimaLeitura.OrientacaoReal = orientacaoReal;
UltimaLeitura.AnguloCarroDefinido = orientacaoReal;
UltimaLeitura.TipoOrientacao = "A";
UltimaLeitura.OrientacaoMovimento =
double.IsNaN(leitura.OrientacaoMovimento) ||
double.IsInfinity(leitura.OrientacaoMovimento)
? orientacaoReal
: GPSUtils.NormalizarAngulo(
leitura.OrientacaoMovimento
);
UltimaLeitura.Distancia = leitura.Distancia;
UltimaLeitura.Velocidade = leitura.Velocidade;
if (leitura.TimestampOri != null)
UltimaLeitura.TimestampOri =
leitura.TimestampOri.Clone();
if (leitura.TimestampPos != null)
UltimaLeitura.TimestampPos =
leitura.TimestampPos.Clone();
UltimaLeitura.Heartbeat = leitura.Heartbeat;
UltimaLeitura.Inicializado = true;
// Monitor/lock do C# é reentrante; os dois métodos abaixo usam o
// mesmo lock e permanecem em uma única transação de leitura GNSS.
AtualizarAtrasoPosicoes(
Math.Max(0, atrasoPosicaoPassos)
);
AtualizarCoordenadasGPS();
}
}
// ============================================================ // ============================================================
// INICIALIZAÇÃO DA PORTA // INICIALIZAÇÃO DA PORTA
// ============================================================ // ============================================================
@ -2198,14 +2287,30 @@ namespace AgroBase.Services
op.Trajetoria.LoopAtualizaDados(); op.Trajetoria.LoopAtualizaDados();
} }
if (PublicacaoMapaAutomaticaHabilitada)
{
AtualizarTrajetoriaDinamica(); AtualizarTrajetoriaDinamica();
EnviarCoordenadasParaMapa(UltimaLeitura.Latitude, UltimaLeitura.Longitude, UltimaLeitura.AnguloCarroDefinido, op.Sensoriamento.Operacao.OperacaoIniciada, rover: true); EnviarCoordenadasParaMapa(
UltimaLeitura.Latitude,
UltimaLeitura.Longitude,
UltimaLeitura.AnguloCarroDefinido,
op.Sensoriamento.Operacao.OperacaoIniciada,
rover: true
);
GPSModel posicaoBase = VariaveisOperacao.ObterPosicaoBaseSnapshot(); GPSModel posicaoBase = VariaveisOperacao.ObterPosicaoBaseSnapshot();
if ((posicaoBase?.UltimoComandoRespondido ?? DateTime.MinValue) > DateTime.UtcNow.AddSeconds(-60)) if ((posicaoBase?.UltimoComandoRespondido ?? DateTime.MinValue) > DateTime.UtcNow.AddSeconds(-60))
{ {
EnviarCoordenadasParaMapa(posicaoBase.Latitude, posicaoBase.Longitude, posicaoBase.OrientacaoReal, false, rover: false); EnviarCoordenadasParaMapa(
posicaoBase.Latitude,
posicaoBase.Longitude,
posicaoBase.OrientacaoReal,
false,
rover: false
);
}
} }
PenultimaLeitura.Inicializado = UltimaLeitura.Inicializado; PenultimaLeitura.Inicializado = UltimaLeitura.Inicializado;
@ -2221,29 +2326,172 @@ namespace AgroBase.Services
// PUBLICAÇÃO LOCAL LATEST-ONLY // PUBLICAÇÃO LOCAL LATEST-ONLY
// ============================================================ // ============================================================
public static void EnviarCoordenadasParaMapa(double Latitude, double Longitude, double Orientacao, bool EmFoco, bool rover) public static void EnviarCoordenadasParaMapa(double latitude, double longitude, double orientacao, bool emFoco, bool rover)
{ {
int ID = rover ? MapasVariaveisModel.IDMarcadorRover : MapasVariaveisModel.IDMarcadorBase; MapasModel mapa = Variaveis.OperacaoEmAndamento?.Mapa;
Variaveis.OperacaoEmAndamento?.Mapa?.AtualizarPosicaoMapaAsync(ID, Latitude, Longitude, Orientacao, EmFoco); Panel painel = mapa?.pnlMapa;
// Produto headless: retorna antes de tocar no WebView.
if (painel == null || painel.IsDisposed || !painel.IsHandleCreated)
{
return;
}
int id = rover
? MapasVariaveisModel.IDMarcadorRover
: MapasVariaveisModel.IDMarcadorBase;
ExecutarMapaNaUi(
mapa,
delegate
{
Task tarefa = mapa.AtualizarPosicaoMapaAsync(
id,
latitude,
longitude,
orientacao,
emFoco
);
ObservarFalhaMapa(
tarefa,
"GPSService.EnviarCoordenadasParaMapa"
);
}
);
} }
public static void AtualizarTrajetoriaDinamica() public static void AtualizarTrajetoriaDinamica()
{ {
var op = Variaveis.OperacaoEmAndamento; var op = Variaveis.OperacaoEmAndamento;
var trajetoria = op?.Trajetoria;
MapasModel mapa = op?.Mapa;
Panel painel = mapa?.pnlMapa;
var dinamica = op.Trajetoria?._TrajetoriaDinamica; // No produto sem UI, não cria cópia nem serializa a trajetória.
if (trajetoria == null || painel == null || painel.IsDisposed || !painel.IsHandleCreated)
{
return;
}
if (!(dinamica?.Any() ?? false)) long anterior = Interlocked.Read(ref _ultimaTrajetoriaMapaMono);
if (GetAgeMs(anterior) < IntervaloTrajetoriaMapaMs)
return; return;
var trajetoria = op.Trajetoria.TrajetoriaDinamica.Select(x => new long agora = Stopwatch.GetTimestamp();
{
latitude = x.Latitude,
longitude = x.Longitude
})
.ToArray();
op?.Mapa?.DefinirTrajetoriaPlanejadaAsync(op.Trajetoria.TrajetoriaDinamica); // Caso duas leituras cheguem juntas, somente uma publica.
if (Interlocked.CompareExchange(ref _ultimaTrajetoriaMapaMono, agora, anterior) != anterior)
{
return;
}
List<GPSModel> pontos = CriarTrajetoriaMapaVisual(trajetoria._TrajetoriaDinamica, MaxPontosTrajetoriaMapa);
if (pontos.Count == 0)
return;
ExecutarMapaNaUi(
mapa,
delegate
{
Task tarefa = mapa.DefinirTrajetoriaPlanejadaAsync(pontos);
ObservarFalhaMapa(
tarefa,
"GPSService.AtualizarTrajetoriaDinamica"
);
}
);
}
private static List<GPSModel> CriarTrajetoriaMapaVisual(IList<PontoTrajetoriaModel> origem, int limite)
{
var resultado = new List<GPSModel>();
if (origem == null || origem.Count == 0)
return resultado;
int count = origem.Count;
int passo = Math.Max(1, (int)Math.Ceiling(count / (double)Math.Max(2, limite)));
for (int i = 0; i < count; i++)
{
PontoTrajetoriaModel ponto = origem[i];
if (ponto?.Posicao == null)
continue;
bool estrutural =
ponto.PontoBorda ||
ponto.PontoLigacao ||
ponto.Tipo == TipoPontoRua.BordaEntrada ||
ponto.Tipo == TipoPontoRua.BordaSaida ||
ponto.Tipo == TipoPontoRua.LigacaoEntrada ||
ponto.Tipo == TipoPontoRua.LigacaoSaida;
if (i != 0 && i != count - 1 && !estrutural && i % passo != 0)
{
continue;
}
resultado.Add(new GPSModel()
{
Latitude = ponto.Posicao.Latitude,
Longitude = ponto.Posicao.Longitude
});
}
return resultado;
}
private static void ExecutarMapaNaUi(MapasModel mapa, Action acao)
{
Panel painel = mapa?.pnlMapa;
if (acao == null || painel == null || painel.IsDisposed || !painel.IsHandleCreated)
{
return;
}
try
{
if (painel.InvokeRequired)
{
painel.BeginInvoke(acao);
return;
}
acao();
}
catch (ObjectDisposedException)
{
}
catch (InvalidOperationException)
{
// A janela pode estar fechando entre as validações.
}
}
private static void ObservarFalhaMapa(Task tarefa, string origem)
{
if (tarefa == null)
return;
_ = tarefa.ContinueWith(
t =>
{
Exception erro = t.Exception?.GetBaseException();
RecordError(
"[" + origem + "] " +
(erro?.Message ?? "Falha desconhecida no mapa.")
);
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted,
TaskScheduler.Default
);
} }
private static async Task LocalPublisherLoopAsync(CancellationToken cancellationToken) private static async Task LocalPublisherLoopAsync(CancellationToken cancellationToken)